Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8d32b59
Bump to 0.6.0: advisory hooks + session-bound state + selector prompts
qxbyte May 18, 2026
9243efd
Bump to 0.7.0: task-swarm orchestrator + 7-hook advisory pipeline
qxbyte May 19, 2026
d3179c7
Bump to 0.7.1: selector protocol switched to AskUserQuestion tool
qxbyte May 19, 2026
64f5f34
Bump to 0.7.2: SELECTOR_PROMPTS three-section + YAML format
qxbyte May 19, 2026
9f85091
Bump to 0.7.3: unify all selector references to YAML three-section fo…
qxbyte May 19, 2026
ee9d321
Bump to 0.8.0: host-neutral wording + sessions schema rename + comman…
qxbyte May 19, 2026
4139fd6
Bump to 0.8.1: rename references/prompts.md → selectors.md + register…
qxbyte May 19, 2026
6ebf264
Bump to 0.9.0: retire acceptance-checklist.md, fold test points into …
qxbyte May 19, 2026
95d6923
Bump to 0.9.1: 测试要点 节降权为参考清单(不再作为验收硬条件 / 同 turn 铁律)
qxbyte May 19, 2026
f5a69cf
Bump to 0.9.2: 移除 DESIGN.md / IMPLEMENTATION-AUDIT.md + CONTRIBUTING …
qxbyte May 19, 2026
8eac91c
Bump to 0.9.3: Iron Rules 7/8 + doc-confirm-tasks 合并 + tasks.md 模板与 t…
qxbyte May 19, 2026
2f5a068
Bump to 0.10.0: 新增 session 日志收集(默认开启,可关,含 redact 与截断保护)
qxbyte May 19, 2026
9da69b9
Bump to 0.10.1: /specode:spec -h 帮助文本动态版本号 + 补充日志开关说明
qxbyte May 20, 2026
fb2ef14
Fix Windows hook launcher:跳过 Microsoft Store python.exe alias stub
qxbyte May 21, 2026
6b0a06f
Fix Windows hook emit UnicodeEncodeError + 测试套件跨平台支持
qxbyte May 21, 2026
5fc2fdd
CHANGELOG: 补 Unreleased 节(Windows hook 修复汇总)
qxbyte May 21, 2026
52776ac
Bump to 0.10.2: Windows hook 注入彻底失效修复(launcher alias stub + emit utf-…
qxbyte May 21, 2026
a12e413
Bump to 0.10.3: 修复 /specode:spec -h fast-path 被 commands/spec.md 引导旁路
qxbyte May 21, 2026
22d6ae8
Bump to 0.10.4: 新建 spec 前 doc_root 确认 + commands/spec.md 4 步路由
qxbyte May 21, 2026
77b4974
Bump to 0.10.5: continue / task-swarm 同源修复 + commands 薄化
qxbyte May 21, 2026
d67496f
Bump to 0.10.6: selectors.md 跟 SELECTOR_PROMPTS 对齐 + drift test 防回归
qxbyte May 21, 2026
68d82f3
Bump to 0.10.7: help 删「命令一览」节,减少重复维护成本
qxbyte May 21, 2026
1bd722a
Bump to 0.10.8: 实现 spec-in/<device>/specs 目录分段 + cmd_set 字段语义对齐
qxbyte May 21, 2026
257a8de
Bump to 0.10.9: /specode:spec 创建后 hallucinate 引导 + 漏 footer 修复
qxbyte May 21, 2026
392e6bc
Bump to 0.10.10: 10 个 selector 加「用户选定后流程」+ SKILL 加 3 条防 hallucinate 规则
qxbyte May 21, 2026
2eebb0d
CHANGELOG: 补 0.10.10 entry(之前 commit 392e6bc 漏了)
qxbyte May 21, 2026
df75032
Bump to 0.10.11: 删除 spec-writer subagent + 主代理直接写 spec 文档 + commands …
qxbyte May 21, 2026
d773ace
Bump to 0.10.12: /specode:end 后 banner 残留修复 + doc-confirm-* 措辞具体化
qxbyte May 22, 2026
f289446
Bump to 0.10.13: 修复 task-swarm v-fix round 漂移 + PreToolUse 阻断主代理直写受控路径
qxbyte May 22, 2026
a1fa0b5
Bump to 0.10.14: /specode:spec 支持 -n <slug> 显式指定 spec 目录名
qxbyte May 22, 2026
287261c
Bump to 0.10.15: project_root 字段——spec 文档目录与代码实现目录解耦
qxbyte May 22, 2026
e599e45
Bump to 0.10.16: slug 放宽允许 Unicode + 帮助文本加用法节 + 禁止 slug 静默 fallback
qxbyte May 22, 2026
a213dbe
Bump to 0.10.17: commands/task-swarm.md 顶部加强制前置阅读指引
qxbyte May 22, 2026
37716ea
Bump to 0.10.18: 修复 task-swarm 第 4 步软提示导致主代理提前 advance + team-lead 代笔…
qxbyte May 22, 2026
5b7b7a5
Bump to 0.10.19: commands/task-swarm.md 加术语区分节 reviewer vs validator
qxbyte May 22, 2026
d07c8f9
Bump to 0.10.20: --skip-validator 人工验收模式(task-swarm 跳过 validator/v-fix)
qxbyte May 22, 2026
67c65aa
Bump to 0.10.21: 修 writeback 多行 reproduce_cmd 越界 + tasks.md hook 强阻断
qxbyte May 22, 2026
247f0dc
Sync marketplace.json version 0.10.11 → 0.10.21
qxbyte May 23, 2026
2d59c51
README: 重写为产品介绍视角(项目本身 / 亮点 / 安装 / 使用) (#20)
qxbyte May 23, 2026
219b7a0
README: 加回插件升级命令小节 (#21)
qxbyte May 23, 2026
0dadd17
README: 升级命令统一加 @specode 后缀 (#22)
qxbyte May 23, 2026
f1446be
README: 修正 spec-writer 已删除的过时描述 (#23)
qxbyte May 23, 2026
f17d99b
docs: add CLAUDE.md guidance for AI assistants (#24)
qxbyte May 25, 2026
797e322
refactor(spec_session): split 2360-line module into 5 siblings
claude May 25, 2026
e4dc850
feat(catalog): description-as-trigger reference catalog hook
claude May 25, 2026
9e63af0
refactor(spec_session): subdirectory package layout
claude May 25, 2026
1ccec82
refactor(task_swarm): subdirectory package layout
claude May 25, 2026
3d40d6e
docs: reflect subdirectory package layout
claude May 25, 2026
41ac7a4
docs(phase-a): zero-risk cleanup of constraint docs
claude May 25, 2026
a4f0f8d
docs(phase-b): selectors.md de-duplication (-485 lines)
claude May 25, 2026
e522afd
docs(phase-c): templates.md trim to writing constraints (-292 lines)
claude May 25, 2026
bc34bf9
docs(phase-d): drop redundant CLI-template reminders from commands (-…
claude May 25, 2026
a8f8361
docs(phase-e): merge duplicate ❌ enumerations in SKILL.md §Selectors
claude May 25, 2026
f04f687
Revert "docs(phase-d): drop redundant CLI-template reminders from com…
claude May 25, 2026
db29fef
docs(commands): targeted slim-down (-11 lines)
claude May 25, 2026
5daddf6
Merge pull request #25 from qxbyte/claude/claude-md-docs-j3W6Y
qxbyte May 26, 2026
369272c
chore(release): bump version to 0.10.22 (#26)
qxbyte May 26, 2026
da66a83
chore(release): bump version to 0.10.22 (#27)
qxbyte May 26, 2026
f982181
fix(spec): 验收通过不再自动弹 iteration-scope + 加 Pre-requirements Clarificati…
qxbyte May 26, 2026
f8584bd
chore(release): 0.10.23 — requirements/bugfix 模板重设计 + 收录铁律修复
qxbyte May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
{
"name": "specode",
"source": "./plugins/specode",
"version": "0.4.0",
"description": "Specification-driven workflow with hard sync enforcement between code and docs (Claude Code + CodeBuddy).",
"version": "0.10.23",
"description": "Specification-driven workflow with advisory hooks, selector prompts, session-bound state, and task-swarm multi-agent orchestration.",
"homepage": "https://github.com/qxbyte/specode",
"repository": "https://github.com/qxbyte/specode",
"license": "MIT",
"keywords": ["spec", "specification", "workflow", "hooks", "code-doc-sync"]
"keywords": ["spec", "specification", "workflow"]
}
]
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ state/*
!state/.gitkeep
.vscode/
.idea/
.pytest_cache/
.claude/
DEV.md
2,024 changes: 2,018 additions & 6 deletions CHANGELOG.md

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this repo is

A single-plugin marketplace for **specode** — a specification-driven workflow plugin for Claude Code and CodeBuddy CLIs. The plugin walks a host agent through a fixed phase pipeline (requirements → design → tasks → implementation → acceptance) using five Markdown documents as the single source of truth, with advisory hooks and `AskUserQuestion` selectors at phase gates.

All implementation lives under `plugins/specode/`. The repo root only contains the marketplace manifest (`.claude-plugin/marketplace.json`), the README, the CHANGELOG, and CONTRIBUTING. Plugin internals are listed in README §Architecture — do not re-derive the file tree, read the README.

## Commands

```sh
# Run the test suite (must be from repo root; tests are hermetic and redirect $HOME)
python3 -m pytest plugins/specode/tests/ -v

# Single test file
python3 -m pytest plugins/specode/tests/test_spec_session_business.py -v

# Single test
python3 -m pytest plugins/specode/tests/test_spec_session_hooks.py::test_on_user_prompt_injects_status_footer -v

# Local plugin install (development)
claude --plugin-dir ./plugins/specode
codebuddy --plugin-dir ./plugins/specode
```

There is no lint or typecheck step configured at the repo level. Lint logic the plugin itself ships (`spec_lint.py`) is for the user's spec docs, not this codebase.

## Non-negotiable conventions

These are the rules from `CONTRIBUTING.md` that are easy to violate and expensive to fix. Read CONTRIBUTING.md in full before opening a PR.

### Runtime is stdlib-only
Code under `plugins/specode/scripts/` MUST use only the Python 3.8+ standard library. Plugin users install via host CLI `plugin install`; they do not `pip install`. Tests under `plugins/specode/tests/` MAY use `pytest` (dev dependency only).

### CLI invocation must go through `run.sh`
Every script under `plugins/specode/scripts/` is a CLI invoked from hooks or directly by the host agent via the `run.sh` wrapper with the absolute `$CLAUDE_PLUGIN_ROOT` (fallback `$CODEBUDDY_PLUGIN_ROOT`) path:

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

`run.sh` probes `python3 → python → py` (with Windows Store alias-stub skipping) so the script works on any host with Python 3.8+. Bare `python3 <name>.py` calls fail in most cwds — when adding a new entry point, match the wrapper template used everywhere in `hooks/hooks.json` and `commands/*.md`.

### Hook safety contract
Hooks in `spec_session.py` are advisory. Every handler MUST:
1. Be wrapped in `@_safe_hook` (catches all exceptions, returns 0).
2. **Never `exit 2`** — push guidance via `additionalContext` JSON to stdout and still `exit 0`. The one exception is `hook_on_pre_tool_use` for direct edits to `tasks.md`, which escalated to a hard block in 0.10.21 (see CHANGELOG); do not add other exit-2 paths without explicit need.
3. Honour `SPECODE_GUARD=off` for global bypass (early-return with no output and no state writes).
4. Tolerate non-TTY stdin (`_read_stdin_payload()` handles this).

Performance budgets (CONTRIBUTING §Performance budget): `UserPromptSubmit` <80ms (fires every user turn), `PreToolUse`/`PostToolUse Task` <100ms, `Stop` <300ms, `SessionStart`/`SessionEnd` <500ms.

### On-disk schema evolution
Two schemas the plugin owns:
- `~/.specode/sessions/<session_id>.json` — per-host-session state
- `<spec-dir>/.config.json` — per-spec config + lock holder

Rules:
- New writes use neutral names (`session_id`, not `claude_session_id`; `holder`, not host-specific names).
- Read sites MUST fall back through historical names. Auto-migrate on read so the next write lands the new key transparently — see `read_session()` and `StateMachine.load()` for the existing pattern, and `test_read_session_migrates_legacy_claude_session_id` / `test_load_migrates_legacy_claude_session_id` as test templates.
- Bump minor for renames with a read-side fallback; bump major if a rename breaks reads.
- All state writes use `tempfile + os.replace + fsync` (see `_atomic_write_json` in `spec_session/_io.py`). If one side of the dual write (sessions file + spec config) fails, roll back and exit 1 — never leave in-memory half-success.

### Test conventions
- Scripts are CLIs, not importable modules. Tests invoke them via `subprocess.run` through the `run_script` fixture in `tests/conftest.py`.
- Use the `fake_home` fixture to redirect `$HOME`, `XDG_CONFIG_HOME`, `APPDATA`, `LOCALAPPDATA`, and clear `SPECODE_ROOT` / `SPECODE_GUARD`. Tests MUST be hermetic — never touch the real `~/.specode/`.
- Use `init_spec` fixture to scaffold a spec directory the way `spec_init.py` would.
- Every persisted-schema change needs a "legacy file migration" regression test.

## Architecture — the parts that span multiple files

### scripts/ layout
Both heavyweight CLIs (`spec_session.py`, `task_swarm.py`) are subdirectory packages with a thin same-name launcher at the `scripts/` root. Python's FileFinder gives package precedence over module within a path entry, and the launcher is exec'd (not imported), so `spec_session.py` (launcher) and `spec_session/` (package) coexist safely. The launcher injects `scripts/` into `sys.path` and calls `<pkg>.cli.main()`.

| `scripts/` member | Role |
|---|---|
| `spec_session.py` | Thin launcher (~40 lines: utf-8 stdout reconfigure + sys.path + `from spec_session.cli import main`). Filename preserved because `hooks.json`, every `commands/*.md`, and `tests/conftest.py:run_script` reference it by name. |
| `spec_session/` package | `__init__.py` (re-exports `read_session` / `read_spec_config` / `_session_short` / `_is_lock_stale` for `spec_status.py:25`), `cli.py` (argparse + `COMMANDS` dispatch + main), `_io.py`, `_selectors.py`, `_reminders.py`, `_business.py`, `_hooks.py`, `_catalog.py` |
| `task_swarm.py` | Thin launcher (~25 lines: sys.path + `from task_swarm.cli import main`). |
| `task_swarm/` package | Empty `__init__.py` (no external `from task_swarm import ...` consumer), `cli.py` (main CLI), `_state.py`, `_parse_md.py`, `_outbox.py`, `_prompt.py`, `_writeback.py` |
| `spec_init.py` / `spec_lint.py` / `spec_log.py` / `spec_status.py` / `spec_vault.py` | Single-file CLIs at the top level. `spec_log.py` is also shared (defensively imported by both packages' modules for session logging). |
| `run.sh` / `run.cmd` | Python interpreter probes (`python3 → python → py`) — Windows alias-stub handling lives here. |

**Do not rename** `spec_session.py` or `task_swarm.py` (those filenames are the API surface). **Do not delete** `spec_session/__init__.py` re-exports (spec_status.py depends on them). Inside the packages, intra-package imports are absolute (`from spec_session._io import …`) for clear error messages.

### `_THIS_DIR` convention inside packages
Modules under `spec_session/` and `task_swarm/` that need to find sibling top-level scripts (e.g. `_hooks.py:_run_task_swarm_plan` calling `task_swarm.py`) define `_THIS_DIR = Path(__file__).resolve().parents[1]` — that resolves to `scripts/`, keeping `_THIS_DIR / "task_swarm.py"` and `_THIS_DIR.parent / ".claude-plugin"` semantically identical to the pre-split layout. Don't use `parents[0]` (it points inside the package).

### Hook → CLI → state-file flow
1. Host CLI fires a hook event (`SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse Task`, `Stop`, `SessionEnd`) per `hooks/hooks.json`.
2. The hook command shells into `run.sh` → `spec_session.py <hook-subcommand>` (the launcher), which imports `spec_session.cli.main` and dispatches the hook subcommand handler.
3. Handlers (wrapped in `@_safe_hook` from `spec_session/_hooks.py`) read `~/.specode/sessions/<session_id>.json` and the active spec's `.config.json`, then emit `additionalContext` JSON to stdout to inject guidance (status footer, selector reminder, doc-sync nag, B2 catalog hits) into the next agent turn.
4. The host agent, following `skills/specode/SKILL.md`, responds to the injection by calling `AskUserQuestion` for selectors or by invoking a `spec_session.py` business subcommand (`acquire` / `phase-transition` / etc.) which atomically updates both state files.

### Session as the integration boundary
Everything is keyed by the host's `session_id` (injected by `SessionStart`, re-injected every `UserPromptSubmit`). Multiple terminal windows = multiple session files = multiple parallel specs. Lock holder is the `session_id`; stale-lock window is 30 minutes (`STALE_LOCK_SECONDS` in `spec_session/_io.py`). The agent MUST NOT invent a session_id, MUST NOT parse one from user input, MUST NOT echo full IDs in chat (8-char prefix only).

### Document root resolution
Three-tier with no fallback (`spec_vault.py`):
1. `--root` flag or `SPECODE_ROOT` env (highest)
2. `~/.config/specode/config.json.obsidianRoot`
3. Auto-detected Obsidian vault → `<vault>/spec-in/<os>-<user>/specs`

If all three miss, `spec_init.py` exits 3 with a setup hint. Do NOT add a cwd or `~/specs` fallback. The `--detect-vault` / `--vault-status` / `--set-vault` / `--set-root` / `--sync-status` flags are routed in `commands/spec.md`; some of them are "fast-path" hooks where the hook pre-renders the output and the agent only prints it verbatim (see `FAST_PATH_HELP` / `FAST_PATH_VAULT` constants in `spec_session/_hooks.py`).

### Phase pipeline + selectors
Valid phases (`VALID_PHASES` in `spec_session/_io.py`): `intake → requirements/bugfix → design → tasks → implementation → acceptance → iteration`. Transitions go through `spec_session.py phase-transition`, which also sets `pending_selector` so the next hook turn knows which `AskUserQuestion` skeleton to remind about. The 7 fixed selector scenarios are defined in `SELECTOR_PROMPTS` (in `spec_session/_selectors.py`) and documented in `skills/specode/references/selectors.md`; `tests/test_selector_prompts.py` snapshots them, and `tests/test_selectors_drift.py` parses the file by regex (keep the `SELECTOR_PROMPTS: dict[str, str] = {...}` literal grep-able).

### Reference catalog (description-as-trigger)
Every `skills/specode/references/*.md` file carries a YAML frontmatter `description: Use when …` that captures *when* a reader should pick it up (superpowers style — trigger-first, not summary-first). The `on-user-prompt-catalog` hook (`spec_session/_catalog.py`) maintains a `CATALOG` dict of keyword regex → reference key (e.g. `lock|takeover|接管` → `lock-protocol`, `task-swarm|@writes|reviewer` → `task-swarm`); each `UserPromptSubmit` it scans the prompt, lists hit references with their descriptions, and emits an advisory injection. Active-only (silent for `idle`/`readonly`/`ended`). `tests/test_catalog.py` enforces: every `CATALOG` key has a real reference file, every targeted reference has a non-empty `description` field. When adding a new reference or extending keyword coverage, update both the frontmatter and `CATALOG`.

### task-swarm orchestration
`task_swarm.py` (launcher → `task_swarm.cli`) is a separate state machine for the implementation phase. The state file is the single source of truth (`<spec-dir>/.task-swarm/runs/<run_id>/state.json`). The flow is `init → plan → fork (N coders) → advance → writeback → resolve`, with reviewer (advisory, one round of P0 fixes if findings carry evidence tags) and validator (blocking pass/fail loop, deadloop guard after 3 identical failures). The four subagent role definitions live in `agents/task-swarm-{coder,planner,reviewer,validator}.md`; they are intentionally tool-restricted (reviewer/validator have no Edit/Write — physical isolation). Submodules under `task_swarm/`:
- `_state.py` — state machine load/save with legacy migration
- `_parse_md.py` — parses `tasks.md` `## 阶段 N:` sections + `@writes` / `@depends-on` tags
- `_outbox.py` — parses subagent `result.md` / `review.md` / `validation.md` per the schemas in `references/task-swarm.md` §4
- `_writeback.py` — line-safe diff back into `tasks.md` (exits 1 on out-of-bounds; see 0.10.21 CHANGELOG entry for the multi-line `reproduce_cmd` bug)
- `_prompt.py` — materializes per-agent prompts into `agents/<key>/prompt.md`

### Session logging
`spec_log.py` writes append-only JSONL events to `~/.specode/logs/<session_id>.jsonl` (hook fires, tool calls, CLI invocations, phase/lock changes). Default redaction of secret-like keys (`password`, `api_key`, `token`, `secret`, `authorization`, `cookie`) and 500-char string truncation. Disable with `SPECODE_LOG=off` or `~/.config/specode/config.json.logging=false`. Any logging exception is swallowed — logging never blocks business flow. When adding a new hook or CLI subcommand, call `_log_event("event_name", payload, session_id)` at the entry point to keep replay useful.

## Release procedure (summary)

Detailed steps are in CONTRIBUTING.md §Release. The two manifests carrying `version` MUST match or the plugin tag tooling refuses:
- `plugins/specode/.claude-plugin/plugin.json` → `"version"`
- `.claude-plugin/marketplace.json` → `plugins[0].version`

Workflow: bump both manifests → rename `## Unreleased` in CHANGELOG to `## X.Y.Z (YYYY-MM-DD)` + add a fresh `## Unreleased` above → run tests → commit + push → `claude plugin tag --dry-run plugins/specode` → `claude plugin tag plugins/specode --push`. Tag format is `specode--v{version}` (annotated). **Pushing the tag IS the release** — there is no tarball or registry artifact; host CLIs fetch the marketplace manifest from GitHub by git tag.

Semver "API surface" for this plugin = slash command set, agent names, hook event names, and persisted-state schema fields. Field renames with a read-side fallback are minor; without fallback are major.

## Where to look for what

- **README.md** — what the plugin does, install/usage, architecture map.
- **CONTRIBUTING.md** — the full version of the conventions summarised above (stdlib rule, CLI wrapper contract, hook safety, schema evolution, performance budgets, release).
- **CHANGELOG.md** — narrative history; useful when a behavior seems weird because it documents past bugs and the reasoning behind subtle fixes (e.g. 0.10.21 writeback line-safe, 0.10.13 / 0.10.17 task-swarm STATUS recovery).
- **plugins/specode/skills/specode/SKILL.md** + **references/** — the runtime behavior spec the *host agent* follows. When modifying selectors, phase order, or the lock protocol, the SKILL.md and the corresponding `references/<topic>.md` need to stay in sync with the CLI behavior; selector drift is enforced by `tests/test_selectors_drift.py`.
Loading