Skip to content

Latest commit

 

History

History
176 lines (129 loc) · 6.31 KB

File metadata and controls

176 lines (129 loc) · 6.31 KB

Contributing

Project-level conventions for changes under plugins/specode/. Read this before opening a PR or cutting a release.

Runtime is stdlib-only

Any runtime code under plugins/specode/scripts/ MUST use only the Python standard library. Plugin users install via the host CLI's plugin install; they don't pip install -r requirements.txt. Pulling third-party packages in either silently breaks for users without them or forces a heavier install path.

Tests under plugins/specode/tests/ MAY use pytest (it's a dev dependency, not runtime).

CLI invocation contract

Every script under plugins/specode/scripts/ is a CLI invoked from hook commands (hooks.json) or directly by the main agent. All invocations MUST go through the run.sh wrapper with the full $CLAUDE_PLUGIN_ROOT (fallback $CODEBUDDY_PLUGIN_ROOT) path:

sh "${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/scripts/run.sh" \
   "${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/scripts/<name>.py" \
   <verb> <args...>

Why:

  • run.sh probes python3 → python → py so it works on any host with Python 3.8+ on PATH.
  • Both CLAUDE_PLUGIN_ROOT and CODEBUDDY_PLUGIN_ROOT are platform-injected env vars; the :- fallback covers both Claude Code and CodeBuddy without forcing the user to pick one.
  • Bare python3 resolve_root.py … calls fail in most cwds because the scripts are not on PATH and the agent doesn't know where it is. See SKILL.md (Iron Rules) for the hard rule.

hooks/hooks.json and the commands/spec.md invocation sections all use this template — match them when adding new entry points.

Test conventions

Run the suite from the repo root:

python3 -m pytest plugins/specode/tests/ -v

The lite suite currently covers resolve_root.py: 3-tier specsRoot resolution priority, set-root persistence + absolute-path rejection, and list-specs behavior. There is no state machine, lock, selector-drift, or template-lint test anymore — those mechanisms were removed in 1.0.0.

When adding behavior, prefer:

  • Unit tests that call the CLI script through subprocess.run via the run_script fixture (the scripts are CLIs, not importable modules).
  • Use the fake_home fixture to redirect $HOME / XDG_CONFIG_HOME and clear SPECODE_ROOT, keeping tests isolated from the real ~/.config/specode/.
  • For hook tests, feed stdin payloads matching the host CLI hook schema and assert against the JSON additionalContext.

Hook safety contract

The single hook handler in spec_hooks.py (SessionStart) MUST:

  1. Catch all exceptions internally and return 0.
  2. Never exit 2. It is advisory only. If you need to influence the model, inject additionalContext JSON to stdout and still exit 0.
  3. Tolerate non-TTY / empty stdin (hook payload arrives via pipe). The script must not block when stdin is a TTY.

Persisted config

The plugin owns one persisted file:

  • ~/.config/specode/config.json — currently holds only specsRoot (the user's document directory, used verbatim as the specs root).

There is no per-session state file and no per-spec config/lock file anymore — spec state is inferred from the documents on disk (which fixed docs exist + - [ ] progress in design.md). Config writes use tempfile + os.replace + fsync (_atomic_write_json in resolve_root.py).

Release

Public release procedure for plugin maintainers.

Version manifests (must agree)

Two manifests carry version. They MUST match or the plugin tag tooling refuses to operate:

  • plugins/specode/.claude-plugin/plugin.json"version": "X.Y.Z"
  • .claude-plugin/marketplace.json → the specode entry's version (plugins[0]; leave the task-swarm entry untouched)

Picking the next version (semver)

"API surface" for semver purposes (1.0.0+) = the /spec subcommand set (/spec <需求> / /spec continue <slug> / /spec list), the SessionStart hook event, the persisted config.json.specsRoot field, and the 3 fixed document filenames (requirements.md / design.md / implementation-log.md) that users or future runtime code observe.

Bump When Examples
major A user feels a breaking change after a plugin update rename / remove a /spec subcommand; rename a hook event; rename config.json.specsRoot; rename a fixed document filename
minor Backwards-compatible new capability or evolution new /spec subcommand; new optional config field; new selector option
patch Bug fix / docs / internal refactor with no surface change fix a typo in a prompt; clarify a reference; CI-only; remove dev-only files from the repo

When in doubt, bump higher.

Cutting a release

# 1. Bump both manifests to the new version
$EDITOR plugins/specode/.claude-plugin/plugin.json
$EDITOR .claude-plugin/marketplace.json

# 2. Land CHANGELOG.md: rename `## Unreleased` → `## X.Y.Z (YYYY-MM-DD)`,
#    then add a fresh empty `## Unreleased` above it for the next cycle
$EDITOR CHANGELOG.md

# 3. Run the test suite one more time
python3 -m pytest plugins/specode/tests/ -q

# 4. Commit + push
git commit -am "Bump to X.Y.Z: <summary>"
git push

# 5. Dry-run the tag first
claude plugin tag --dry-run plugins/specode
# (or codebuddy plugin tag --dry-run plugins/specode — pick whichever
#  host CLI is installed; both wrap the same git operations)

# 6. Create + push the annotated tag
claude plugin tag plugins/specode --push

Tag format: specode--v{version} (annotated, message specode {version}). The plugin is not packaged into a tarball or registry artifact — host CLIs fetch the marketplace manifest directly from GitHub and resolve plugins by git tag. Pushing the tag IS the release.

Re-tagging the same version

Only safe if no user has installed it yet:

git tag -d specode--vX.Y.Z
git push --delete origin specode--vX.Y.Z
claude plugin tag plugins/specode --push      # re-create

Once a release is in user hands, prefer a new patch version.

Verifying after release

# Adjust the CLI name for whichever host you use (claude / codebuddy).
claude plugin marketplace update qxbyte
claude plugin install specode@qxbyte         # or `update`
claude plugin list | grep specode             # confirm new version

Users on a different host follow the same procedure with their host's CLI name (codebuddy plugin …).