Conversation
This release encompasses two large jumps: 0.5.0: stripped the project back to a skeleton (removed all INV-1~11, spec_guard / spec_sync / bash_guard / task_swarm_guard / spec_choice / all hooks / all scripts / all tests). 0.6.0: rebuilt the runtime on the skeleton with a strict "advisory only, never block" model: - 4 v0.6 hooks (SessionStart / UserPromptSubmit / Stop / SessionEnd), all exit 0 + additionalContext injection; never exit 2. - Session state file ~/.specode/sessions/<claude_session_id>.json with atomic write (tempfile + os.replace + fsync) and rollback on partial failure; lock holder key is also claude_session_id. - Persistent session is the only mode; --persist flag removed. - Status footer required on every active-spec turn. - Selector text generated by the model from three skeletons (A single- select, B wizard, C multi-select); hook injects only "which type, which scenario" metadata. 11 fixed scenario constants in spec_session.py SELECTOR_PROMPTS. - Document-first discipline as advisory hooks, replacing INV-1 / INV-2: input-side reminder lists the six spec docs; output-side reminder asks model to self-check whether code change needs doc backfill. - /specode:spec -h fast-path: hook intercepts and injects full help text for verbatim print. - 6 stdlib-only scripts (spec_vault, spec_init, spec_session, spec_lint, spec_status + run.sh/run.cmd). - spec-writer agent (Read/Write/Edit/Grep/Glob; no Bash — physical isolation). - SKILL.md + 6 references rewritten for the new model. - 75 pytest tests covering 3-tier vault, init+rollback, lock state machine, hooks across mode matrix, SELECTOR_PROMPTS snapshot, lint rules, and end-to-end event chain. All passing. DESIGN.md is included as the single source of truth for 0.6 → 0.8 implementation. INV-1~11 and spec_choice.py are permanently retired. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This release implements what was originally scoped as v0.7 + v0.8 and ships it as a single 0.7.0 jump. Added: - task-swarm multi-agent orchestrator: task_swarm.py CLI (init / plan / advance / writeback / heartbeat / resolve) + 5 supporting modules (state machine, tasks.md parser with @writes-based group splitting, outbox schema validators, line-safe writeback, prompt prerendering). - on-task-completed hook (PostToolUse matcher=Task) — calls task_swarm plan after each subagent returns and injects next-step advice into additionalContext per §11.6 9-state matrix. - on-heartbeat-quiet hook (UserPromptSubmit, second handler) — silently renews spec lock + sessions last_activity_at every turn when mode=active. Never injects context. - on-pre-tool-use hook (PreToolUse matcher=Edit|Write|MultiEdit) — reminds (never blocks) on direct tasks.md edits during task-swarm runs; tells the model to go through writeback CLI instead. - spec_session.py list-specs --root <path> — JSON listing of all specs in the doc root (slug / phase / lock_state / holder / mtimes), replacing the deleted spec_choice.py discovery path. Used by /specode:continue with no slug. - references/task-swarm.md + task-swarm-example.md. - spec-writer agent (no Bash; physical isolation for doc generation). - 77 new task-swarm pytest cases. Total 152 tests, all passing. Changed: - SKILL.md slimmed from ~335 to ~194 lines; detail moved to references. SKILL.md is now activation contract + dispatch table. - /specode:continue flow corrected — model is explicitly forbidden from Grep'ing the project directory; must use spec_vault status + spec_session list-specs. See references/obsidian.md §5.1. - Multi-vault selection UI now follows the §3.7.1 Type A selector skeleton (sentinel + Type something + Chat about this reserved). - All version-difference wording removed from runtime docs. Removed: nothing further. INV-1~11, spec_choice.py, spec_guard.py, spec_sync.py, bash_guard.py, task_swarm_guard.py, sentinel short-circuit, audit log, telemetry remain permanently retired. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 11 entries in spec_session.py SELECTOR_PROMPTS have been rewritten to instruct the model to call Claude Code's built-in AskUserQuestion tool instead of emitting a markdown list with "AWAITING_USER_CHOICE" sentinel and asking the user to reply with a number. Type A → questions=[1 q] + multiSelect=false Type B (wizard) → questions=[2-4 q], each multiSelect=false (chip-tabs) Type C (multi) → questions=[1 q] + multiSelect=true The user navigates with arrow keys, presses Enter to submit, and gets a tool-provided "Other" for free-text. The historical reserved positions (Type something / Chat about this / Submit) are removed — they're provided by the tool itself. AWAITING_USER_CHOICE is gone everywhere — the tool call is itself the turn terminator. Files updated: - spec_session.py SELECTOR_PROMPTS 11 entries: workflow-choice, clarification-wizard, clarification-done, doc-confirm-*, tasks-execution, takeover-options, acceptance-gate, iteration-scope. - skills/specode/SKILL.md §Selectors: rewrote to enforce AskUserQuestion tool usage, listed forbidden patterns (markdown list + reply-with-number, Type something / Chat about this saved positions, AWAITING_USER_CHOICE sentinel). - skills/specode/references/prompts.md: full rewrite as AskUserQuestion call manual with Python-style parameter blocks for all 8 scenarios + forbidden-phrasing compat section. - skills/specode/references/obsidian.md §3 (multi-vault selector) and §5.1 (list-specs UI): switched to AskUserQuestion form. - skills/specode/references/workflow.md §9.1 (/specode:continue with no slug) and §10 (phase-gate output order): rewrote to call AskUserQuestion instead of emitting numbered list. - tests/test_selector_prompts.py: workflow-choice snapshot now asserts AskUserQuestion / multiSelect / "label" presence. Also includes IMPLEMENTATION-AUDIT.md (327 lines) covering the 80+ requirements from the design discussion across 19 sections. All 152 tests passing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All 11 SELECTOR_PROMPTS entries rewritten to a paste-and-fire prompt
style matching the test prompts the maintainer validated in another
window. Each constant now has identical structure:
## 选择器节点:<scenario>
**目的**:why this selector now
**上下文**:active spec / phase / other dynamic fields
**前置动作(chat 简报,≤N 行)**:what to write to chat before tool call
**调用 AskUserQuestion 工具**:
questions:
- question: "..."
header: "..."
multiSelect: true|false
options:
- label: "..."
description: "..."
**约束**:dos and don'ts
The YAML block is now directly liftable into AskUserQuestion tool
arguments (no mental translation step). doc-confirm-* now explicitly
require a 3-8 bullet briefing before tool call; takeover-options
forbids the "(推荐)" marker; acceptance-gate moves the marker
conditionally on n_fail=0; iteration-scope explicitly flags
multiSelect=true.
references/prompts.md updated to note that the Python-call form
(AskUserQuestion(questions=[...])) and the YAML-indented form (used
by the hook) are semantically equivalent.
Tests:
- test_selector_prompts.py + test_spec_session_hooks.py snapshot
assertions updated to match new format.
- All 152 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rmat
Following 0.7.2 (which rewrote SELECTOR_PROMPTS to the three-section +
YAML format), this release brings the two dynamic selectors in
references/obsidian.md and the 8 static scenarios in
references/prompts.md to the same format so every selector reference
across the plugin is visually identical.
obsidian.md §3 (multi-vault selection) and §5.1 (/specode:continue no
slug spec picker) — both were Python-call form
(AskUserQuestion(questions=[{...}])); now three-section YAML matching
SELECTOR_PROMPTS. Both remain "dynamic selectors" (not in the 11 fixed
constants library), driven by spec_vault.py detect and
spec_session.py list-specs CLI output at runtime.
prompts.md 8 static scenarios (workflow-choice, clarification-{wizard,
done}, doc-confirm-{requirements,bugfix,design,tasks}, tasks-execution,
takeover-options, acceptance-gate, iteration-scope) rewritten from
Python-call form to byte-for-byte the same YAML three-section format
as SELECTOR_PROMPTS. The previous "Python-call form and YAML form are
equivalent" caveat is removed — there is now only one format.
Added a concrete wizard example with 3 clarification points (login UX
scenario) to §B1 to give the model a worked reference.
All 152 tests pass without changes (snapshots already match after
0.7.2).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…d md CLI templates
Three coordinated changes:
1. Host-neutral wording. All user-facing descriptions in docs and
code drop singling out one host CLI; use "host CLI" / "宿主" /
"CLI agent" instead. Technical contracts retained verbatim:
CLAUDE_PLUGIN_ROOT env var (with :-${CODEBUDDY_PLUGIN_ROOT}
fallback), .claude-plugin/ directory name, claude plugin …
install commands.
2. Sessions/state.json schema field rename. claude_session_id →
session_id at all write sites (spec_session.py, spec_init.py,
task_swarm.py, task_swarm_state.py). Reads auto-migrate legacy
files in-memory; lock holder reads fall back through
holder → session_id → claude_session_id. No manual migration.
3. Command md files now embed copy-pasteable CLI templates that use
the full run.sh + $CLAUDE_PLUGIN_ROOT wrapper, so the model sees
the correct invocation immediately on /specode:* and won't loop
on bare python3 spec_session.py … against the wrong cwd
(observed failure mode this release fixes).
Plus: two new compat regression tests pinning the legacy-field
migration behavior; migrate-from-spec-mode.sh removed (0.1.0 spec-
mode → specode migration script long past its usefulness window).
154 tests pass (152 previous + 2 new).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… A+ selector variant Two coordinated docs-only changes: 1. Rename references/prompts.md → references/selectors.md. "prompts.md" was too generic for a file whose content is "AskUserQuestion selector spec + 8 fixed scenario constants". "selectors.md" matches the file's own title and the single-word naming convention of sibling references. Renamed via git mv so history follows; all 23 in-repo references updated (SKILL.md, 5 cross-references in references/*.md, DESIGN.md, IMPLEMENTATION-AUDIT.md). Earlier CHANGELOG entries that mention prompts.md were intentionally left untouched (they reflect the actual file name at the time of those releases). 2. Register type variant A+ (single-select + preview side-by-side). When an option carries a `preview` field, the host UI auto- switches to a side-by-side layout. Single-select only. Template-only for now; no fixed scenario uses A+ yet. 154 tests pass; no test changes required. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tasks.md ## 测试要点 Spec document count drops from 6 to 5. Acceptance phase now decides "passed" based on tasks.md fully [x] + every test-point line in ## 测试要点 being crossed, instead of a separate checklist file. Why: the standalone checklist duplicated information tasks.md already carried via _需求:x.y_ traceability tags. Two parallel docs encouraged drift; spec_lint's checklist-lag rule papered over a symptom rather than fixing the duplication. Folding test points into tasks.md keeps the QA-facing artifact next to the engineering-facing tasks it validates, so changes propagate in one Edit. Code changes: - spec_init.py drops acceptance-checklist.md template + write step; tasks.md template now ships with ## 测试要点 section. - spec_session.py rewrites SELECTOR_PROMPTS["acceptance-gate"] (uses n_done/n_total + n_fail, prompts model to run spec_lint first); 6-doc list in list-specs shrinks to 5; document-first reminder drops checklist line. - spec_lint.py drops rule_checklist_lag (3 rules remain: trace / log / EARS). - agents/spec-writer.md: "same-turn rewrite of acceptance-checklist" becomes "same-turn update of tasks.md ## 测试要点". Doc changes (all 7 references + SKILL.md + DESIGN.md + README × 2 + IMPLEMENTATION-AUDIT updated): 6→5 doc list everywhere; §3.2 in workflow.md retitled and rewritten; templates.md §5 deleted and subsequent sections renumbered (§6→§5, …); iteration.md accumulation rules now describe appending to ## 测试要点 instead of rewriting a checklist table. spec_lint.py now wired in: SKILL.md §Phase Order and the acceptance-gate selector text both instruct the main agent to call spec_lint.py once when entering acceptance (advisory; exit 0 always; never blocking). spec_lint existed since 0.6.0 but no hook/command/agent ever invoked it. Migration: existing pre-0.9.0 specs keep their acceptance-checklist.md on disk — no auto-delete. Copy still-useful lines into the new ## 测试要点 section in tasks.md and delete manually. New specs from 0.9.0 onward never get the file. 153 tests pass (down from 154; the deleted test_lint_checklist_lag_warns case is the only loss). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
0.9.0 把 acceptance-checklist.md 折叠进 tasks.md ## 测试要点 时过度 强调了这个节——50+ 处提及分布在 12 个文件,"跟随式更新铁律" / acceptance-gate 验收硬条件 / 每轮 hook 注入 / spec-writer 5 处 "同 turn 更新" / workflow.md §3.2 整章等,与节点本身只是"给测试 人员一份验证清单参考"的实际地位不匹配。 本版降权: - acceptance-gate selector 验收只看 tasks.md 全 [x],测试要点降为 chat 简报的参考信息,不参与判定 - DOC_PRIORITY_REMINDER_ACTIVE 删"同 turn 更新测试要点"那一行 - SKILL.md 不再标硬纪律;tasks.md 行改为"spec-writer tasks phase 按 SHALL 顺手补几行供测试人员参考" - references/workflow.md 删 §3.2 整章;§3.1/§4/§5 各处"同 turn" 步骤删;§7 acceptance phase 简化;§9.2 持续沟通模式不再强调; 流程图去掉那一列;后续 §3.3/§3.4 重编号 - references/templates.md 删 §4.2 整节填充规则;§4 tasks.md 模板 的 ## 测试要点 示例从 [ ] checkbox 改为纯 bullets;验收节删 "测试要点全部跨过"行;增设短版 §4.2 填充提示 - agents/spec-writer.md 删整个"## tasks.md 测试要点同 turn 更新" section;phase=requirements/bugfix 流程删"同 turn 更新"步骤; phase=tasks 加一句"填末尾 ## 测试要点 节,按 SHALL 补几行" - references/iteration.md iteration 期间不要求改测试要点;累积 规则降为"按需追加";ASCII 示例从 checkbox 改纯 bullets - README/README.zh-CN/DESIGN/IMPLEMENTATION-AUDIT 顶层描述去掉 "跟随式""同 turn"措辞 不变(保留): - assets/templates/tasks.md 和 spec_init FALLBACK_TEMPLATES 里的 ## 测试要点 节保留(格式调成纯 bullets) - obsidian.md 目录树里的信息性说明保留 - spec-writer 仍在 tasks phase 顺手补充测试要点行,但不再是独立铁律 migration: 无需迁移;验收门只会变得更容易通过(不会出现"以前能过 现在卡住"的情形)。 153 tests pass(仅 docs/prompts 文字调整,无代码逻辑变化)。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…规范化 + .gitignore 加强 两份文档不再作为项目产物维护: - DESIGN.md (1515 行) 是 v0.5→v0.8 重建期设计文档,0.8.x/0.9.x 演进 后部分章节与代码漂移(sessions schema 字段名 / spec_lint 规则数等), 全文同步代价过高 - IMPLEMENTATION-AUDIT.md (327 行) 是 v0.7.0 时点的一次性对账,行号 引用大多失效 历史信息保留在 git log + CHANGELOG.md;当前真实代码状态看 SKILL.md + references/。两份文档已在 Obsidian 笔记本地备份并加索引导读说明 其漂移现状。 随之清理代码内所有指向 DESIGN.md 的 §X.Y 章节引用: - 11 个 .py 脚本 docstring / 注释里的 (§3.X) (§11.X) 引用改指 references/*.md 的对应章节号或简化为 SKILL.md - commands/status.md / commands/task-swarm.md / references/task-swarm.md / references/selectors.md 各处 §X 引用对齐 CONTRIBUTING.md 规范化重写: - 删头部过时 0.6.0 note (75 tests / 4 hooks / 6 references 全不对了) - 测试数字 75 → 153, 覆盖范围描述同步 - 新增 "CLI invocation contract" 节: 明示 run.sh + $CLAUDE_PLUGIN_ROOT 完整路径模板是 0.8.0 起的硬要求 - 新增 "On-disk schema fields" 节: session_id 命名 + 三键 fallback 约定; schema rename 是否走 minor 取决于是否带 read-side fallback - semver 表的 API surface 加 hook event names + schema fields - release 流程加 pytest step, 明示双宿主 CLI 命令等价 - 去 v0.6 字样, 去 host-specific 措辞 .gitignore 补 .pytest_cache/ 与 .claude/ (防御性, 目前未追踪). 153 tests pass (无净变化, 本版仅 docs 删除 + docstring 调整 + .gitignore 更新, 无代码逻辑变化). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ask-swarm 解析器对齐
四个协调改动,全部源于 0.9.2 真实跑测时观察到的主代理违纪 / 流程
卡点:
1. SKILL.md Iron Rules 加 7+8 条
- Rule 7: 4 份核心文档(requirements/bugfix/design/tasks)必须
fork spec-writer 写;主代理直接 Write/Edit 视为违规。
implementation-log 例外。
- Rule 8: 文档头 Status/Review Status 字段不允许主代理手改;
由 phase-transition + selector 流程驱动。
0.9.2 截图主代理自己 Write requirements.md 再 Edit 改 Status:
Draft→Complete 是这两条要堵的典型违规。
2. doc-confirm-tasks 合并入 tasks-execution(8→7 个 selector)
0.9.2 截图主代理把 doc-confirm-tasks 标准 3 选项简化成自由发挥
的 2 选项漏掉 task-swarm。本版合并为一步出图:tasks-execution
4 选项 = task-swarm 多 agent 并发 / 顺序执行(同时处理 optional)
/ 需要调整 tasks.md / 暂不 coding。不再区分 required-only vs
+optional——默认都跑 optional,要只跑 required 用 Other 输入。
spec_session.py L920 phase=tasks 推 selector 改 tasks-execution;
SKILL/selectors/workflow 全文同步;测试 fixture 同步。
3. tasks.md 模板统一为 task-swarm 兼容格式
0.9.2 截图主代理选 task-swarm 后 init 报 "tasks.md 中未解析出
任何 ## 阶段 N: 段"——根因是 spec-writer 写的 tasks.md 用
"- [ ] 1. 标题/- [ ] 1.1 子任务" 嵌套格式,但 task_swarm parse_md
期望 "## 阶段 N: 标题" 顶层段 + "- [ ] N.M ... @writes:... _需求:x.y_"。
两边对不齐导致主代理被迫自己 Write 覆盖(违反 Iron Rule 7)。
assets/templates/tasks.md + spec_init FALLBACK + templates.md §4
+ spec-writer.md phase=tasks 全部统一为新格式(顺序执行也兼容)。
4. commands/task-swarm.md 立即调用段澄清
<abs> 改成 <spec_dir>/tasks.md;明示 init 子命令必传 / --tasks
是 tasks.md 路径而非 spec 目录 / 解析失败时回到 selector 让
spec-writer 重写。
152 tests pass (down from 153; 删 test_doc_confirm_tasks_snapshot
随 selector 合并; 其余 fixture/断言已同步).
migration: 无需迁移. tasks-execution 推荐项变 task-swarm; 仍保留
顺序/调整/暂不 coding 三个出口. 旧格式 tasks.md 在选 task-swarm
时会报错, 按 Iron Rule 7 让 spec-writer 重写, 不要主代理手改.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
新功能(minor)。spec 模式期间全程收集事件流到 ~/.specode/logs/<session_id>.jsonl,用于排查"主代理为什么走偏" 类问题。 收集事件: - hook_invoked / hook_exception:每个 hook 触发 + 异常 trace - tool_pre / tool_post:主代理工具调用(Bash/Read/Write/Edit/Task 等)的 tool_input + tool_response_head(通过 PreToolUse * + PostToolUse * 全通配新 hook 抓) - cli_call / cli_exit:spec_session/spec_init/spec_status/task_swarm 的 cmd + argv + exit_code 新增组件: - plugins/specode/scripts/spec_log.py:核心模块(write_event / replay / status / enable / disable 5 个子命令) - plugins/specode/tests/test_spec_log.py:13 个测试 集成: - hooks.json 加 PostToolUse * + PreToolUse * 两条全通配 hook,调 spec_session.py on-log-pre-tool-use / on-log-post-tool-use - spec_session.py:_safe_hook 装饰器加 hook_invoked event;main() 加 cli_call/cli_exit;新增 hook_on_log_pre_tool_use / hook_on_log_post_tool_use 两个 hook handler + argparse + COMMANDS 映射 - spec_init.py / spec_status.py / task_swarm.py:顶部 defensive import spec_log,主入口包 _log_wrap_main 记 cli_call/cli_exit 双开关(默认开启): - 临时关:export SPECODE_LOG=off - 永久关:编辑 ~/.config/specode/config.json 加 logging: false - env 优先;env 取 off/false/0/no 关,on/true/1/yes 开 隐私默认: - redact 黑名单 14 个字段(password / api_key / token / secret / authorization / cookie / private_key / ssh_key 等),命中→<redacted> - 字符串字段超 500 字符自动截断(后缀 ...<truncated>) - 递归深度 >8 → <deep_truncated> - config 可加 redact_keys 列表扩展黑名单 存储 + rotation: - 不自动 rotation;session-bound 控制大小 - spec_log.py status 输出占用,超 100MB 提示手动清 异常隔离: - spec_log 内部 try/except 吞所有异常 - 各集成点用 contextlib.suppress 包裹 - 日志失败绝不阻断 hook/CLI 业务流程 165 tests pass (152 previous + 13 new in test_spec_log.py). 原 152 个测试 0 破——日志收集完全 backward-compatible. 文档:SKILL.md / CONTRIBUTING.md / README × 2 全部加 session logging 节。 migration: 无需迁移. 0.10.0 启动自动开始写日志;不想要的按 SPECODE_LOG=off 或 config logging:false 关. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- HELP_OUTPUT_TEXT 改为 HELP_OUTPUT_TEMPLATE + _render_help_text(), 版本号从 .claude-plugin/plugin.json 动态读取,避免每次 bump 漏改。 - 新增「会话日志(v0.10.0+)」段,覆盖 logs/ 默认行为、SPECODE_LOG env 与 config.json 双开关优先级、spec_log.py status / replay 用法。 - 纯文档/渲染层改动,无业务行为变化。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
run.sh / run.cmd 之前的探测顺序 (python3 → python → py) 在 Windows 上会被
%LOCALAPPDATA%\Microsoft\WindowsApps\python{,3}.exe(App Execution Alias 0 字节
stub)拦截:stub 跑起来打印 "Python was not found" 并 exit 49 → CodeBuddy 启动
时出现 "Hook SessionStart [warning]",后续所有 hook 实际也是跑 stub 而不是
Python,spec_session.py 根本没有机会执行。
- run.sh:新增 _specode_is_alias_stub 路径检测,跳过 .../WindowsApps/python{,3}{,.exe}
这种 alias stub,继续 fallback 到真 python 或 py launcher。macOS / Linux 路径
里不会有 WindowsApps 段,行为不变。
- run.cmd:调整探测顺序为 py → python3 → python。py.exe 来自 python.org 安装器,
不受 Microsoft Store alias 影响。
- 失败提示补充关闭 App Execution Aliases 的操作指引(设置 > 应用 > 高级应用设置)。
后续 emit 阶段的 UnicodeEncodeError 修复留作另一个 commit。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
承接 fb2ef14(launcher alias stub fix),处理 Windows hook 注入的第二个根因。 emit 阶段 UnicodeEncodeError 被 _safe_hook 吞并 ───────────────────────────────────────────────── 中文 Windows pipe stdout 默认 cp936/gbk 编码,无法编码 emoji 📝/🪧/⛔(来自 DOC_PRIORITY_REMINDER_ACTIVE / STATUS_FOOTER_TEMPLATE / SPEC_MODE_CONTINUE_REMINDER 三个模板)。_emit_hook_additional_context 写入时抛 UnicodeEncodeError 被 _safe_hook 装饰器的 except BaseException 吞掉 → hook exit 0 / stdout 空 / 主 代理收不到 fast-path / session_id / selector / footer / 文档优先提醒。 - spec_session.py / spec_init.py 顶部加 sys.stdout / sys.stderr.reconfigure( encoding="utf-8", errors="replace") 兜底。spec_session 是 hook entry; spec_init 在 Windows 上写中文 stderr 错误信息("目录已存在"等)也需要 utf-8 才能被主代理 / 测试 stderr utf-8 读取链读对。 测试套件跨平台修复 ───────────────── - conftest.py + 6 个 test_task_swarm_*.py:SCRIPTS_DIR 从硬编码 macOS 路径 /Users/xueqiang/Git/specode/... 改成 Path(__file__).resolve().parents[1] / "scripts"。 - conftest.run_script + test_task_swarm_cli / hook:subprocess.run 加 encoding="utf-8",env 设 PYTHONUTF8=1 / PYTHONIOENCODING=utf-8,让子进程 pathlib 与 stdio 同时 utf-8。 - conftest.fake_home:monkeypatch APPDATA / LOCALAPPDATA 到 fake_home, 防止用户真实 Obsidian 安装漏到 spec_vault.detect 测试。 - 6 个 test 文件 .read_text() 加 encoding="utf-8":解决 spec_init 写 utf-8 .config.json / sessions/*.json 被默认 cp936 解码失败。 - test_on_user_prompt_help_fastpath_only_emits_help:'specode v0.6' 改 'specode v',兼容 0.10.1+ 动态版本号。 复现 + 验证 ─────────── 笔记 specode-windows-hook-异常排查记录.md 步骤 1 的 active session 触发命令 原本抛: UnicodeEncodeError: 'gbk' codec can't encode character '\U0001f4dd' in position 1103: illegal multibyte sequence (\U0001f4dd = 📝)。修复后 stderr 干净,stdout 3555 字节完整 5 段 hook 注入 JSON 含所有 emoji。 pytest 165/165 通过(修复前 Windows 上 109 fail)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…8 + 测试跨平台) 承接 fb2ef14 / 6b0a06f / 5fc2fdd 三个 commit,发布 0.10.2 release。 针对 Windows + CodeBuddy 上 specode hook 完全无法注入到主代理上下文的两个连续根因: 1. Launcher 命中 Microsoft Store python.exe / python3.exe alias stub(0 字节,跑起来 只打印 "Python was not found" 并 exit 49)→ spec_session.py 根本没被执行, SessionStart 报 [warning]、所有后续 hook 全空跑。 修复:run.sh 加 alias stub 路径检测跳过;run.cmd 优先级改 py → python3 → python。 2. emit 阶段 UnicodeEncodeError 被 _safe_hook 吞并:中文 Windows pipe stdout cp936/gbk 无法编码 emoji 📝/🪧/⛔(DOC_PRIORITY_REMINDER / STATUS_FOOTER / SPEC_MODE_CONTINUE 模板)→ hook exit 0 / stdout 空 / 主代理收不到 fast-path / session_id / selector / footer / 文档优先提醒。 修复:spec_session.py / spec_init.py 顶部 sys.stdout / stderr.reconfigure( encoding="utf-8", errors="replace")。 附带测试套件跨平台支持(Windows pytest 109 fail → 165/165 全过): hardcoded macOS 路径、.read_text() 默认 locale 解码、subprocess 编码不一致、 fake_home 未隔离 APPDATA / LOCALAPPDATA 等多个跨平台 bug 一并修复。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0.10.2 修好了 hook emit UnicodeEncodeError 后,hook 现在能正确向主代理 additionalContext 注入完整 fast-path 模板(含 verbatim print 指令 + HELP CONTENT BEGIN/END + 完整 help body)。验证: - 用真实 session id 跑 prompt="/specode:spec -h" → hook stdout 2598 字节 完整 fast-path JSON,含 specode v0.10.2 完整 help。 - 最新 session log(615f599c-...jsonl)中 hook_on_user_prompt 后无 hook_exception,emit 修复确实生效。 - CodeBuddy 缓存 plugin.json 是 0.10.2,scripts/spec_session.py 顶部含 utf-8 reconfigure,run.sh 含 alias stub 检测 — 0.10.2 修复已部署到位。 但主代理仍然不按 fast-path 走,反而 sh spec_init.py -h,把 spec_init.py argparse 输出当 help。 根因:commands/spec.md 顶部「## 立即调用」标题 + 醒目的 sh spec_init.py 代码块视觉优先级压倒底部 bullet 第 3 项 "fast-path 参数由 hook 拦截" 备注。 主代理看到 -h 时按"立即调用"分支执行。 修复:把 fast-path 分支前置成「## 第一步」,明确 -h / --help / --vault-status / --detect-vault / --sync-status / --set-vault / --set-root 不要调任何 CLI(禁止 sh spec_init.py -h 等),唯一动作是 verbatim 输出 hook 注入的 ```text 围栏内容;常规需求降为「## 第二步」。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
承接 0.10.3 修好 /specode:spec -h fast-path 旁路后,又一个 commands/spec.md
引导主代理调 CLI 而 bypass 业务规则的 case:用户在 git repo 下输入
/specode:spec ...,主代理直接 sh spec_init.py,spec 文档 silent 落到
Obsidian vault 自检测命中的目录,没有任何确认。
证据:
- ~/.config/specode/config.json 不存在、SPECODE_ROOT 未设
- spec_vault.py status 返回 source=auto,doc_root=Documents\Notes(含 .obsidian/)
- 主代理 chain-of-thought 截图:直接解析 slug + sh spec_init.py,不询问 doc_root
根因(与 0.10.3 同源):commands/spec.md 旧版直接给 sh spec_init.py 命令,
主代理照执行;SKILL.md § Document Root Resolution 只讲三层全 miss → exit 3,
没规则约束"第 3 层 auto-detect 命中(非全 miss)也应先确认"。spec_init.py
实现是 silent 用了。
修复(commands 薄、SKILL 厚原则):
1. SKILL.md § Document Root Resolution 加新子章节
「首次使用 / auto-detect 命中时的确认(强制)」:明确 source=auto / none
时禁止直接调 spec_init.py,必须 AskUserQuestion 三选(接受检测到的
vault + 持久化 / 改用其他绝对路径 + 持久化 / 中止),用户选定后
spec_vault.py set --vault <p> 持久化,下次自动用、不再问。
2. commands/spec.md 重构为 4 步路由(依次匹配 $ARGUMENTS):
- 第一步:fast-path(-h / --help / --vault-status / --detect-vault /
--sync-status,hook 已注入模板)→ verbatim print
- 第二步:set 命令(--set-vault <p> / --set-root <p>,hook 不拦截)
→ 调 spec_vault.py set --vault <p>,end turn
- 第三步:新建 spec 前 doc_root 确认(调 spec_vault.py status,按
SKILL.md 新规则)
- 第四步:spec_init.py 创建 spec
同时修正 0.10.3 commands/spec.md 把 --set-vault / --set-root 误列入
fast-path 旗标的 bug(hook 实际不拦截这俩,旧版主代理按"等 hook 注入"
会卡住)。
设计原则:commands 薄(路由 + 边界引导)、SKILL 厚(业务规则单一来源)。
commands 不重复 SKILL 细则,边界 case 指向 SKILL 章节,业务流程改动只
需要改 SKILL.md。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit 发现 continue.md / task-swarm.md 与 0.10.3 / 0.10.4 的 spec.md 修复同源: commands 直接给完整 CLI 命令模板,主代理见命令就跑,bypass 了 SKILL.md / workflow.md / references/task-swarm.md 的业务规则。 continue.md(类型 1 / 高严重度) ───────────────────────────────── 旧版「## 立即调用」直接给 acquire --spec <dir> --session <id> 模板, 跳过 workflow.md §9 的 5 步流程(list-specs → AskUserQuestion 让用户选 ≤4 项 → LockHeld → takeover-options selector → acquire → load)。无 slug 时主代理 会 invent <dir>。 重写为两步: - 第一步(无 slug):确认 doc_root → list-specs → 空列表引导 / 非空 chat 摘要 + AskUserQuestion 单列单选 → 用户选定后转第二步 - 第二步(有 slug):解析 spec_dir → acquire(LockHeld 禁止直接 --force, 先 takeover-options selector 让用户选)→ load → continue → 报告 + footer task-swarm.md(类型 2 / 中严重度) ───────────────────────────────── 旧版直接给 task_swarm.py init --tasks <spec_dir>/tasks.md,<spec_dir> 占位符 鼓励主代理 invent 路径而不去读 sessions/<id>.json 拿 active_spec_dir;缺 phase / pending_selector 前置校验,用户裸输 /specode:task-swarm 时无 phase 检查直接 init。 拆 3 步: - 第一步(前置校验,必做):read-session 强制 mode=active / active_spec_dir 非空 / phase=tasks / pending_selector=tasks-execution 已选 task-swarm 路径 - 第二步(init):用 step 1 的 active_spec_dir + /tasks.md,禁止 invent - 第三步(7 步循环):保留 sketch,详细规格指 references/task-swarm.md SKILL.md §Task-Swarm 补「/specode:task-swarm 前置校验(强制)」子节, 作为 commands/task-swarm.md 第一步引用的业务规则单一来源。 commands/task-swarm.md 精简(commands 薄、references 厚) ───────────────────────────────── 原 132 行重复了 references/task-swarm.md 的 5 段(Phase 状态机 ASCII 图 / 7 步循环展开 / 文件冲突 / 详细异常处理 / 命令调用样例)。精简到 ~70 行, 只保留 commands 路由层职责(前置校验 / init / 7 步 sketch + heartbeat / 异常出口摘要),详细规格全部指 references/task-swarm.md §1-§9 单一来源。 设计原则延续 0.10.4:commands 薄(路由 + 边界引导)、SKILL / references 厚 (业务规则 + 协议详解)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit 发现 selector 模板真实 drift 3 处: - workflow-choice: selectors.md 缺"参数完全按下列结构(直接传入,不要翻译/ 重写选项)"子句 + 约束段"立即 end turn 等待用户选择"等措辞 - doc-confirm-bugfix / doc-confirm-design: selectors.md §A3 压缩成表格列变体 差异(line 178-181),没给完整 ```text 块;spec_session.py 实际模板的简报句 也跟表格描述对不齐 - §A3 H3 标题里残留 doc-confirm-tasks(0.9.3 起已废弃合并进 tasks-execution) 修法(按"runtime 是 single source of truth、文档跟齐"原则): - selectors.md §A1 workflow-choice 补缺失措辞 3 处 - selectors.md §A3 重构为「H3 分组介绍 + H4 三个 key 各带完整 ```text 块」 (doc-confirm-requirements / doc-confirm-bugfix / doc-confirm-design) - selectors.md §A3 H3 标题去掉 tasks 残留 - spec_session.py SELECTOR_PROMPTS 不动 防回归: plugins/specode/tests/test_selectors_drift.py 在 pytest 阶段自动比对: - test_keys_match: runtime key 集合 vs selectors.md 命中 key 集合 - test_byte_identical[<key>]: parametrize 10 个 selector 逐字 byte-compare 未来改 selector 文案 / 增删 key 时 pytest fail 提醒同步两边。 跑了一遍:drift test 11/11 passed;全套 pytest 176/176 passed (165 + 11 新增,0 regression)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
命令清单在 SKILL.md / commands/*.md / README 已有详细说明且会随版本演进, help 文本内重复列一份反而容易过时(0.10.4 / 0.10.5 加 doc_root 确认步骤、 task-swarm 前置校验时都需要同步改 help)。help 改为只展示版本号 + 会话与锁 / 工作流概要 / 日志开关,命令细节让用户查 SKILL.md。 无业务行为变化。spec_session.py:HELP_OUTPUT_TEMPLATE 删除 13 行(line 614-628)。 hook 测试 17/17 通过。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
约定(references/obsidian.md §0-§1 + SKILL.md:158):spec 文档落在 <vault>/spec-in/<os>-<username>/specs/<slug>,让多设备/多用户共享同一 vault 时 各 device 的 spec 互不串扰。 但 spec_vault.py resolve_doc_root 从未实现 device_segment——auto / config 命中 后直接返回 vault 根,spec_init 拼出来变成 <vault>/specs/<slug>,少了关键的 spec-in/<device>/ 整层。复现:用户在 Windows 跑 /specode:spec,spec 落到 Documents\Notes\specs\login-page 而非约定的 Documents\Notes\spec-in\windows-qiang\specs\login-page。 修复(spec_vault.py,方案 A:只动一个文件): 1. 加 _device_segment() 函数(platform.system + getpass.getuser),返回 windows-qiang / macos-alice / linux-bob。 2. resolve_doc_root 按字段语义分场景追加 spec-in/<device>: - override / env / config-rootOverride → 不追加(用户给什么用什么) - config-obsidianRoot / docRoot / auto → 追加 spec-in/<device> 3. cmd_set 修正字段语义(旧版 --vault 和 --root 都写 obsidianRoot,导致 rootOverride 字段从未被实际使用,文档与运行时不一致): - --vault <p> → 写 obsidianRoot(resolve_doc_root 会追加 device 段) - --root <p> → 写 rootOverride(不追加) - 两字段互斥,写其中一个清掉另一个 + 清掉 legacy docRoot - 输出 doc_root 用 resolve_doc_root() 重算反映 device 段 spec_init.py / spec_session.py list-specs call site 不动(仍 <doc_root>/specs/<slug>,但 doc_root 已含 spec-in/<device>,最终路径自动变成 <vault>/spec-in/<device>/specs/<slug>)。 测试: - test_spec_vault.py 新增 4 个 device 段相关测试 - 更新 3 个现有测试反映新 schema - pytest 全套 179/179 PASS(从 0.10.7 的 176 → 179) 升级影响:旧 spec 目录不会自动迁移;新 spec 走新路径,list-specs 找不到旧路径 下的 spec;如需保留旧 spec 内容手动 mv 到 <vault>/spec-in/<device>/specs/<slug> 并更新 sessions/<id>.json 和 .active-specode.json pointer。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
复现:用户跑 /specode:spec <需求>,主代理输出 "Spec 已创建成功" 详情后接 "你可以使用 /specode:continue 进入下一阶段继续推进",且漏了状态行 footer。 证据: - spec_init.py:400-408 只输出纯 JSON,无任何 "/specode:continue 进入下一阶段" 引导(全 repo grep "使用 /specode:continue 进入" 命中 0 次) - hook_on_user_prompt 注入 footer (line 1550) 但只在 user-prompt 提交时跑; 用户输 /specode:spec 时 session 还是 idle,没注入 footer 提醒 - hook_on_stop 只 emit CODE_DOC_SYNC_STOP + SPEC_MODE_CONTINUE_REMINDER(文字 提醒"下一 turn 要 footer"),不 emit STATUS_FOOTER_TEMPLATE 本身 - → spec_init.py 把 session 改成 mode=active + pending_selector=workflow-choice 之后,本 turn hook 已经跑过、不会再注入 footer / selector;commands/spec.md 第四步没规定"成功后主代理本 turn 必做 footer + selector + 禁止 hallucinate 让用户输命令的引导"——主代理因此漏 footer 又 hallucinate /specode:continue 修复(commands 薄 / SKILL 厚原则): 1. commands/spec.md 第四步加「成功后必做」子节:明确 spec_init exit 0 后本 turn 必做 3 件事——chat 简报 + 禁止 hallucinate 命令引导、输出 footer、 立即调 AskUserQuestion 呈现 workflow-choice selector。 2. SKILL.md §Status Footer 加「新 spec 创建 / 接管的当 turn」子节:统一覆盖 /specode:spec(spec_init 完成)和 /specode:continue [slug](acquire+load +continue 完成)两类首 turn 场景,规定 hook 未刷新时主代理必须主动 chat 简报 + footer + selector,严禁"持续流程被打断"类的命令引导。/specode:spec 和 /specode:continue 是持续流程的入口,进入之后整条 phase 链由 selector + hook + phase-transition 自动推进。 spec_session.py / spec_init.py 不动,是引导文档层修复。pytest 179/179 通过。 audit 同源风险(其他 commands): - continue.md(0.10.5 重构后):step 5 已要求 footer;SKILL.md 新子节覆盖主动 selector,无需 commands 再补 - end.md:mode=ended 不输 footer,by-design - status.md:active 期间应输 footer(SKILL.md §Status Footer),轻微风险 - task-swarm.md:init 后立即 plan→fork,by-design Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
承接 0.10.9 修好 /specode:spec 创建后引导 hallucinate 之后,又发现两类同源问题:
1. selector 选定后流程缺失:用户选完 workflow-choice "Requirements first" 后
主代理只 chat ack 一句 "请下一轮输入 /specode:continue" 就 end turn,没调
phase-transition / 没 fork spec-writer / 没生成 requirements.md / 没呈现
doc-confirm-requirements selector。根因:selector 模板末尾"调用工具后立即
end turn"是历史措辞,AskUserQuestion 实际是同步阻塞工具,返回选项后主代理
应同一 turn 继续按 workflow.md 流程推进,但模板没说"选定后做什么"。
2. 主代理 hallucinate "退出 spec 模式" + invent 简化 selector:tasks 完成后
主代理输出 "Spec 流程完成!退出 spec 模式,开始编码",用 invent 的简化
selector "任务清单已就绪,下一步? → 开始编码",而非 tasks-execution 模板
4 个固定选项。
修复(commands 薄 / SKILL 厚):
A. SELECTOR_PROMPTS / selectors.md(10 个 selector 各加「用户选定后流程」段)
每个 selector 末尾约束段后加 **用户选定后流程(同一 turn 内继续)** 段,
列出每个选项的下一步动作(phase-transition target / fork agent / 下一个
selector 等)。用 **bold** 而非 ### H3 避免被 drift test 的 H3/H4 regex
误识别为 selector 边界。byte-identical 同步 spec_session.py + selectors.md。
B. SKILL.md §Selectors 顶部加 3 个子节:
- AskUserQuestion 工具语义:同步阻塞、拿到选项同一 turn 继续;严禁 "请下
一轮输入 /specode:continue" 就 end turn
- 呈现 selector 禁止 invent / 简化选项:必须用 SELECTOR_PROMPTS 模板逐字
传参
- phase-transition 不退出 spec 模式:只有 /specode:end 才退出;严禁
"退出 spec 模式,开始编码" 这类话
C. SKILL.md §Status Footer 加 "新 spec 创建/接管的当 turn"(0.10.9 已加)
继续覆盖所有"hook 未刷新本 turn 主代理主动 footer + selector" 场景。
pytest 179/179 PASS(drift test 11/11 + 全套 168 不变)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0.10.10 release commit 因 CHANGELOG Edit 时 old_string 日期不匹配漏掉了 CHANGELOG 改动,补一个独立 commit 把 entry 加上。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…进一步变薄
复现:用户跑 /specode:spec 走到 requirements phase → 主代理 fork spec-writer
agent 写 requirements.md → spec-writer 各种 Glob/Read 找不到 assets/templates/
模板(实际找的是不存在的 .template.md 后缀)→ hallucinate 18 条通用登录页面
SHALL + 408 行 design.md(JWT/HTTPS/CSRF/2FA),跟用户原始需求"在 git 目录做
登录页面"完全脱节。
根因:subagent 设计反模式 —— 每个 subagent 是独立 LLM 调用 + 新 context window,
拿不到主代理上下文(不读 SKILL.md / 不知道用户原始 source_text / 不知道流程
状态)。即使模板路径正确,subagent 仍按通用模板填空,内容不贴合用户具体需求。
修复(用户授权我自决方案):
1. 删除 plugins/specode/agents/spec-writer.md
2. SKILL.md 加 §「Spec 文档生成」单一规则来源:主代理 Read
${CLAUDE_PLUGIN_ROOT}/assets/templates/<phase>.md 模板 + 按
<spec-dir>/.config.json.source_text 填空 + Write 到 <spec-dir>/<phase>.md
3. SKILL.md Iron Rule 7 改写:移除 "必须 fork spec-writer subagent" 约束
4. 31 处 fork spec-writer 引用全部改成 "主代理按 SKILL.md §「Spec 文档生成」走":
- spec_session.py SELECTOR_PROMPTS 6 selector
- selectors.md 同步 6 处(drift test cover byte-identical)
- references/workflow.md 4 处
- references/templates.md 顶部 + 6 处
- commands/task-swarm.md 1 处
- spec_init.py 1 处 docstring
- assets/templates/tasks.md 1 处
5. assets/templates/ 4 份模板保留作主代理 Read 的骨架来源
Changed: commands/spec.md 第四步「成功后必做」从 3 件事压成一句引用 SKILL.md;
commands/continue.md 大幅瘦身只列入口路由 + 关键禁止项,详细流程 link 到
references/workflow.md。按用户指导 "命令中不要设置过多流程,只列关键必要内容,
让模型与 skill 对接流程"。
测试: drift test 11/11 + 全套 pytest 179/179 全 PASS。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixed: /specode:end 后模型仍在响应末尾输出 ─── spec-mode ─── 状态行。
根因:hook_on_user_prompt 在 mode in ("idle","ended") 时静默 early-return,
不注入任何反向消息。但此前 N 个 turn 已反复注入 STATUS_FOOTER_TEMPLATE
("请在本次响应正文之后额外输出一行 ─── spec-mode ─── ...")与
SPEC_MODE_CONTINUE_REMINDER("下一 turn 必须继续遵守 ... 通过 /specode:end
才能正式退出")。/specode:end 提交那一 turn mode 仍是 active,hook 最后一次
注入照常进行;end 之后下一 turn hook 安静停止,但模型 context 里堆积的
"必须输出 footer / 下一 turn 必须继续遵守"指令仍生效,凭惯性继续输出 banner。
修复:
1. 新增 SPEC_MODE_ENDED_REMINDER 模板:明确告知模型"已退出,作废此前所有
spec-mode 指令,不要再输出 ─── spec-mode ─── footer"
2. cmd_end 设 post_end_reminder_pending=True;同时对齐 end.md 文档清掉
active_spec_slug / active_spec_dir / spec_id / phase / task_swarm_run_id
(此前实现只改 mode/ended_at/lock_state/pending_selector,违反文档约定)
3. hook_on_user_prompt 在 mode=="ended" and post_end_reminder_pending 时
注入提醒并清标志;其他 ended/idle 路径维持原静默
行为:end 后第 1 turn 模型收到明确反向指令 → 第 2 turn 起 hook 完全静默 →
banner 不再出现。
Changed: doc-confirm-* selector option description 用具体环节名替代「下一 phase」。
- doc-confirm-requirements → 进入设计(design)环节
- doc-confirm-bugfix → 进入设计(design)环节
- doc-confirm-design → 进入任务拆分(tasks)环节
同步更新 selectors.md(drift test byte-identical cover)。workflow-choice 的
"进入下一阶段"保留泛化(next 按 workflow 动态选 requirements/design/bugfix
三选一,无法静态命名)。
测试: 扩展 test_end_sets_mode_ended_and_releases_lock 覆盖字段清零 + 新标志;
新增 test_on_user_prompt_post_end_reminder_emits_once_then_clears(hook 单元);
重写集成测试 test_after_end_user_prompt_emits_one_shot_then_nothing。
全套 pytest 180/180 PASS。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure refactor; no behavior change. spec_session.py shrinks from 2360
lines to a 231-line thin entry (argparse + COMMANDS dispatch + main)
while the implementation moves to:
_ss_io.py atomic write / session+spec config IO / lock utils /
shared constants (VALID_PHASES, STALE_LOCK_SECONDS)
_ss_selectors.py SELECTOR_PROMPTS dict + _fill_selector
_ss_reminders.py reminder template strings + help text rendering
_ss_business.py all cmd_* business commands +
_update_session_for_spec + _auto_pending_selector
_ss_hooks.py all hook_on_* + _safe_hook +
task-swarm plan reminder helpers
Filename `spec_session.py` is preserved because hooks.json, every
commands/*.md, and tests/conftest.py:run_script all reference it by
name — renaming would break existing user installs.
Backward-compat: spec_session.py re-exports read_session,
read_spec_config, _session_short, _is_lock_stale at module level so
spec_status.py:25 (`from spec_session import ...`) keeps working.
test_selectors_drift.py SCRIPTS path repointed to _ss_selectors.py;
the dict literal is byte-identical to the pre-refactor version (lifted
via source slicing, not retyped).
All 219 tests pass.
Add a new advisory hook `on-user-prompt-catalog`, registered as the third entry in hooks.json UserPromptSubmit. It implements a lightweight keyword catalog so that specode runs in "always supervising + on-demand activation" hybrid rather than supervision-only. Mechanism: - Each references/*.md file gains a YAML frontmatter `description: Use when ...` capturing **when** a reader should pick it up (superpowers style — trigger-first, not summary-first). - _ss_catalog.py defines a CATALOG dict of pre-compiled regex per reference key, with bilingual patterns (e.g. `lock|takeover|接管` → lock-protocol, `task-swarm|@writes|reviewer` → task-swarm, 8 keys total). - The hook scans each UserPromptSubmit prompt; matches yield an advisory injection listing the relevant references with their frontmatter descriptions. Activation gate: only mode=active triggers. idle / readonly / ended sessions stay silent so the hook never noises up out-of-spec turns. Drift guards: - test_catalog_keys_have_matching_reference_files — every CATALOG key resolves to a real references/<key>.md - test_every_catalog_referenced_file_has_description_frontmatter — each targeted reference carries a non-empty description field Performance: pre-compiled regex + at most 8 small file reads per fire, well under the 80 ms UserPromptSubmit budget. Tests: 237 passing (219 existing + 18 new catalog tests).
Move the 6 _ss_*.py modules created in B1 into scripts/spec_session/
package, drop the _ss_ prefix (subdirectory is now the namespace):
_ss_io.py → spec_session/_io.py
_ss_selectors.py → spec_session/_selectors.py
_ss_reminders.py → spec_session/_reminders.py
_ss_business.py → spec_session/_business.py
_ss_hooks.py → spec_session/_hooks.py
_ss_catalog.py → spec_session/_catalog.py
spec_session.py → spec_session/cli.py (thin entry: argparse + main)
scripts/spec_session.py is now a 35-line launcher that:
- applies the Windows utf-8 stdout/stderr reconfigure
- injects scripts/ into sys.path (so spec_log import still works)
- imports and calls spec_session.cli.main()
Same-name file + directory coexist safely because Python's FileFinder
gives package precedence over module within a path entry; the launcher
itself is exec'd, not imported, so there's no ambiguity for its own
file.
spec_session/__init__.py re-exports read_session / read_spec_config /
_session_short / _is_lock_stale so spec_status.py:25 continues to work
unchanged (`from spec_session import ...` resolves to the package's
__init__, which re-exports from _io).
Path resolution fixed for all moved modules: _THIS_DIR redefined as
`Path(__file__).resolve().parents[1]` (= scripts/) so existing usages
like `_THIS_DIR.parent / ".claude-plugin"` and `_THIS_DIR / "task_swarm.py"`
keep their original semantics.
Intra-package imports rewritten as absolute `from spec_session._X import …`
(not relative `from ._X`) for consistent error messages and stable
introspection.
Tests:
- test_selectors_drift.py SCRIPTS path updated to spec_session/_selectors.py
- test_catalog.py CATALOG_PY path updated to spec_session/_catalog.py
- hooks.json / commands/*.md / conftest.py:run_script all unchanged
(they reference spec_session.py by name, which is the launcher).
237 tests pass; launcher and spec_status.py smoke-tested.
Same shape as the spec_session split: move the 5 task_swarm_*.py modules into scripts/task_swarm/ package, drop the prefix, add underscore to mark them internal: task_swarm_state.py → task_swarm/_state.py task_swarm_parse_md.py → task_swarm/_parse_md.py task_swarm_outbox.py → task_swarm/_outbox.py task_swarm_prompt.py → task_swarm/_prompt.py task_swarm_writeback.py → task_swarm/_writeback.py task_swarm.py → task_swarm/cli.py scripts/task_swarm.py is now a 25-line launcher (sys.path inject + import task_swarm.cli.main; no utf-8 reconfigure needed — task_swarm emits structured JSON, not user-facing prompts with emoji). Cleaner than spec_session: the 5 submodules have zero cross-imports and zero __file__-relative path lookups (no plugin.json or references/ dir to find), so moving them required no code changes inside the modules — only the cli.py imports needed `from task_swarm_X` → `from task_swarm._X`. task_swarm/__init__.py is intentionally empty (no public re-export needed — nothing in the wider codebase does `from task_swarm import ...`; tests and cli both target submodules directly). External surface unchanged: - hooks.json doesn't reference task_swarm.py (only commands/task-swarm.md and spec_session/_hooks.py:_run_task_swarm_plan do, both via the launcher path that's preserved) - 4 tests that import submodules directly (test_task_swarm_state / outbox / parse_md / writeback) updated to use `task_swarm._X` paths 237 tests pass; task_swarm launcher smoke-tested.
Update CLAUDE.md architecture section and CHANGELOG.md Unreleased entry to describe the new scripts/ layout: two thin launchers (spec_session.py / task_swarm.py) at the top level, each with a same-name package alongside holding the actual implementation. Key additions in CLAUDE.md: - New "scripts/ layout" table showing thin-launcher + package-pair pattern for the two heavyweight CLIs - "_THIS_DIR convention inside packages" note: parents[1] resolves to scripts/, keeping all sibling-script path lookups semantically identical to the pre-split layout - All cross-references updated to new module paths (e.g. STALE_LOCK_SECONDS in spec_session/_io.py, FAST_PATH_HELP in spec_session/_hooks.py, task_swarm submodules listed as _state / _parse_md / etc.) CHANGELOG.md merges the prior B1 entry into a "two-step refactor" narrative (sibling split + package subdirectorization) so a reader following 0.10.21 → next sees one coherent story rather than two near-identical reorgs.
Phase A of the constraint-doc slim-down audit. Zero functional
impact; all 237 tests pass.
SKILL.md:
- §Selectors header: "7 个固定场景" → "8 个固定场景" (matches the
11-key SELECTOR_PROMPTS dict; previous count predated A0
project-root-choice)
- Add missing project-root-choice row to the scene table
- §AskUserQuestion 工具语义: drop the "历史措辞" framing — describe
the synchronous-blocking semantics directly instead of explaining
why a former phrasing was wrong
- §phase-transition 不退出 spec 模式: merge 3 near-identical "严禁
说 spec 流程完成" passages into 1
- Iron Rule 7: shrink from a re-explanation of spec-writer's removal
to a pointer at §Spec 文档生成 (where the canonical reasoning lives)
- Module path references updated: spec_session.py SELECTOR_PROMPTS
→ spec_session/_selectors.py
selectors.md:
- §历史措辞兼容 (L623-633) deleted — 5 obsolete phrases that were
never to appear at runtime; tracking them in this doc was pure
archaeology
- §自主判断 "上面 7 个场景对照表" → "8 个"
- Module path reference updated to spec_session/_selectors.py
- L341 section title: drop "0.9.2" version anchor
templates.md / workflow.md:
- Drop dangling version anchors ("0.9.3 起 …", "0.10.11 起 …") that
no longer carry diagnostic value; canonical reasoning still lives
in §Spec 文档生成 / CHANGELOG
Diff: -33 / +18 lines. No SELECTOR_PROMPTS body touched (those edits
are scheduled for Phase B alongside the drift-test rewrite).
Phase B of the constraint-doc slim-down audit. selectors.md shrinks
from 633 → 148 lines (-77%) by dropping 11 \`\`\`text blocks that were
byte-identical copies of SELECTOR_PROMPTS dict literals in
_selectors.py. Drift test rewritten accordingly. 227 tests pass
(was 237; net -10 from collapsing the parametrized byte-identical
check into 2 key-set checks).
What changed:
selectors.md
- §1 类型 → AskUserQuestion 参数形态 (kept; the type skeleton table
has real documentation value)
- §2 类型变体 A+ (kept; documents an unenabled feature with no code
equivalent)
- §8 场景常量库 (rewritten as an 11-row overview index pointing at
_selectors.py line numbers; no more full-template reprints)
- §hook 注入与模板替换 (kept)
- §自主判断 (kept; "7 个" → "8 个")
- All 11 \`\`\`text blocks under former §A0/A1/A2/A3(×3)/A4/A5/A6/B1/C1
deleted — single source of truth is now _selectors.py
SELECTOR_PROMPTS
test_selectors_drift.py
- Replaced 11-key parametrized byte-identical check with:
test_overview_table_matches_runtime_keys (key set equality)
test_expected_key_count (sanity: SELECTOR_PROMPTS has 11 entries)
- Drift surface area drops from "every char of every template" to
"key set of the overview table" — proportionate to what humans
actually maintain in the doc now
External link audit (every reference that pointed at the deleted
§A0/§A1/§A4/§(1)/§(6)/§(7)/§(8)/§clarification-wizard chapters):
- SKILL.md ×2: __selectors.py SELECTOR_PROMPTS['<key>'] pointer
- commands/spec.md ×2: same
- workflow.md ×3: same
- iteration.md ×3: same
- lock-protocol.md ×1: same
Each repointed link names the specific SELECTOR_PROMPTS key so the
reader knows exactly which dict entry to read.
Risk surface explicitly considered:
- main agent now reads SELECTOR_PROMPTS dict values directly (Python
triple-quoted string — markdown renders normally); no behavior
change at runtime (hook still injects the same string).
- if someone removes a SELECTOR_PROMPTS entry without updating the
overview table, test_overview_table_matches_runtime_keys catches
it; if the table goes out of sync with the dict, same.
Phase C of the constraint-doc slim-down. templates.md shrinks from
469 → 177 lines (-62%) by removing the five \`\`\`markdown skeleton
blocks that duplicated assets/templates/<phase>.md.
Single source of truth for each document's section skeleton is now
assets/templates/<phase>.md (what spec_init.py actually copies into
new spec dirs and what the main agent Reads to fill in). templates.md
keeps the writing constraints that don't live in the skeleton:
§0 命名约定 — file purpose / mutex / skeleton path lookup table
§1-§5 writing constraints per document (SHALL numbering, bugfix
EARS variants, traceability rules, log entry minimums, etc.)
§4.1 任务标记语义 — checkbox state machine (kept; not in skeleton)
§4.2 ## 测试要点 节填充提示 — author guidance (kept)
§6 EARS 四种 SHALL 写法
§7 traceability 规范
§8 Document Style 总则
§9 跨文档引用
Decoupling verified:
- spec_lint.py inspects token-level evidence (SHALL keywords, _需求:
trace tags, log file refs, ≥30-char threshold) — it never reads
templates.md or matches its section headers.
- assets/templates/tasks.md already contains the task-swarm @writes /
@depends-on / _需求:x.y_ format spec, so the §4 skeleton in
templates.md was redundant.
Tests: 227 pass.
…5 lines) Phase D (conservative scope) of the constraint-doc slim-down. Each commands/*.md already shows the full run.sh wrapper invocation in its own ```sh code block; the trailing "调用模板规约见 SKILL.md §CLI 调用规约(禁止裸 python3 …)" note repeated the same point the code block above it had already demonstrated. Removed 4 such redundant trailing reminders from: - commands/continue.md (also dropped a separate trailing reference) - commands/end.md - commands/spec.md - commands/status.md Not removed (intentional, see audit report): - The full `sh ... run.sh ... spec_*.py` invocation blocks in each command file. The audit's projected -40 lines from collapsing these into "see SKILL.md" pointers was a high-risk change: command files are read by the main agent when the user types a slash command, and SKILL.md is not guaranteed to be in context at that point (activation depends on mode=active in sessions). Keeping each command standalone is worth the ~35 lines we left on the table. Tests: 227 pass.
Phase E (final scope) of the constraint-doc slim-down audit. The audit projected ~30 lines of savings from "compressing ❌ density in SKILL.md / selectors.md / task-swarm-coder.md". Detailed re-inspection after Phases A-D shows most of those ❌s are NOT redundant: - task-swarm-coder.md (9 ❌): each names a distinct boundary the CODER subagent must not cross (no self-scoring, no review, no pass/fail, no out-of-@writes writes, no peek at other agents' dirs, no full rewrites on fix-rounds, no excuses, no scope creep). Subagents fork in isolation without the main agent's context; specific reminders are load-bearing. - selectors.md (9 ❌ after Phase B): split across §公共禁区 (4) and §hook 注入 (3) plus 2 inline; each names a separate failure mode. - task-swarm-{reviewer,validator,planner}.md (3-4 each): all role-boundary reminders; same isolation reasoning. The one real duplication is in SKILL.md itself: §Selectors opener (L89) restated three "严禁..." rules that the canonical reverse-list in §「看到 hook 注入"必须呈现 X 选择器"时的硬约束」 already enumerates with ❌ marks. Compressed L89 to a high-level pointer at that list. Tests: 227 pass. -1 line (modest, but it removes a real dup; the larger ~30-line target was over-optimistic on inspection).
…mands (-5 lines)" This reverts commit 94f6861.
Three minimal-risk edits agreed after the commands/ integral review. None of the load-bearing "防扩散" reminders (禁止 invent agent_key, 禁止 silent fallback, 禁止凭口头报告 advance, etc.) were touched — those are deliberate guardrails against documented past incidents. 1. task-swarm.md: drop the duplicate TOC of references/task-swarm.md §1-§9 at the end of "第三步:7 步循环". The same list already appears at the top of the file in §⛔强制前置阅读 (the more load-bearing location). Replaced with a single pointer line. (-10 lines) 2. task-swarm.md: rewrite §heartbeat section to a bullet list of verbs + an explicit "沿用 §第三步同款 run.sh 包装模板(不要裸 python3 …)" reminder, instead of the previous ```sh block that showed the verbs WITHOUT the run.sh wrapper. The old form contradicted this file's own "所有 task_swarm.py 子命令套同一 run.sh 包装模板" rule from a few lines above — a literal anti-pattern in the example. (-1 line, consistency fix) 3. spec.md: drop the second 4a example (中文 slug). The first 4a example demonstrates how the slug → requirement_name mapping works for English; the rule "非 ASCII slug 直接复用原文" stated in the rules block above already covers non-ASCII without needing a second worked example. Replaced with a one-line cross reference. (-2 lines) Tests: 227 pass.
- plugins/specode/.claude-plugin/plugin.json: 0.10.21 -> 0.10.22 - .claude-plugin/marketplace.json: 0.10.21 -> 0.10.22 - CHANGELOG.md: rename Unreleased -> 0.10.22 (2026-05-26) Release content: B1 spec_session split + subdirectory packages, B2 description-as-trigger catalog hook, constraint docs slim-down (phase A-E). No schema change, no hook behavior change. https://claude.ai/code/session_01C5QvwQ4CSG7oLGNfsByh2J Co-authored-by: Claude <noreply@anthropic.com>
…on 铁律 两块独立但相关的修复,统一收口"主代理不应替用户做决定"。 1) iteration-scope 误弹(acceptance→iteration 路径) `_selectors.py` acceptance-gate 模板和 `_business.py _auto_pending_selector` 互相加强地把"验收通过"等价于"立刻追问要不要继续迭代"——与 `references/iteration.md` §2/§7「不自动呈现 iteration-scope」自相矛盾。 - `_selectors.py` acceptance-gate「用户选定后流程」:验收通过 → 仅做 phase-transition + chat 一句简报 → end turn,不再串接 iteration-scope。 - `_selectors.py` iteration-scope 模板:「目的」/「前置动作」改写为只在用户 显式提出迭代调整时呈现;新增「触发条件」节列三种禁用场景。 - `_business.py _auto_pending_selector`:phase=="iteration" 返回 None, 合并冗余分支并加注释,让 hook 不再自动注入 iteration-scope。 - `SKILL.md` L113-115:移除"acceptance-gate 通过且 iteration-scope ESC" 作为退出 spec 模式的判定条件——只剩 `/specode:end` 一条。 2) Pre-requirements Clarification 铁律 clarification-wizard 模板存在但永不触发:`_auto_pending_selector(intake)` 硬编码返回 "workflow-choice",主代理也没有"何时该问、何时可跳"的指引。 本次接通主流程,把它做成主代理在 workflow-choice 选定后必走的歧义自检: - `SKILL.md` §「Pre-requirements Clarification」改写为「铁律」形态: 六维歧义自检 (scope/behavior/UX/data/validation/acceptance)、四种必呈现 场景、唯一例外(用户明确放权"由你决定"/"按业界默认"等)+ 反例三条。 - `_selectors.py` workflow-choice「用户选定后流程」拆 Step A 歧义自检 + Step B 文档生成两段式 routing,写文档过程中发现新歧义须停写补 wizard。 - `commands/spec.md` 第四步成功后必做加第 4 步指引,强调"严禁绕过 clarification-wizard 直接写文档"。 测试: - 新增 test_phase_transition_to_iteration_clears_pending_selector 作为 acceptance→iteration 不再自动注入 selector 的回归防护。 - drift / selectors / hooks 全套 228 passed。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
模板从「机器验收文档」改写为「需求 / 缺陷描述」。旧模板在「验收标准 / 当前行为 / 期望行为」节预填 WHEN ... THE System SHALL ... 占位句, 模型按填空模式扩写,整篇文档丢失"用人话讲需求"的语感。 v3 设计要点: - 主体改自然语言(用户故事 / 主流程 / 异常流程 / 根因分析) - EARS 收敛到末尾「验收要点」可选段,不再强制预填 - 「范围之外」前置到第一节,与「目标」放同一视野 - 新增「典型使用路径」编号步骤 + 可选 Mermaid flowchart / stateDiagram - bugfix 新增「根因分析」假设表(假设 / 支持证据 / 反对证据 三列) - 「待澄清问题」前置,与 Pre-requirements Clarification 铁律联动 - 新增 Priority / Severity 一行元信息(可省) 向后兼容: - 保留 ### 需求 1 / ### 需求 2 章节锚点(spec_lint REQ_TAG_RE 依赖) - 保留 Spec Type / Workflow / Status / Review Status 元信息块 - spec_lint rule_ears_shall 仅对已存在 SHALL 行校验,不预填不触发 warning - 228 项测试无回归 同时收录 0.10.22 之后的 f982181(验收通过不再自动弹 iteration-scope + Pre-requirements Clarification 铁律),详细 changelog 见 CHANGELOG.md 0.10.23 节。 bump plugin.json + marketplace.json 0.10.22 → 0.10.23。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.