golden-hoop-spell-opencode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +184 -0
  2. package/package.json +51 -0
  3. package/shared/SPIKE_RESULTS.md +597 -0
  4. package/shared/agents/ghs-context-haiku.md.template +124 -0
  5. package/shared/agents/ghs-plan-designer.md.template +128 -0
  6. package/shared/agents/ghs-plan-reviewer.md.template +170 -0
  7. package/shared/assets/features.json +67 -0
  8. package/shared/assets/progress.md +35 -0
  9. package/shared/ghs.default.json +7 -0
  10. package/shared/ghs.default.json.notes.md +34 -0
  11. package/shared/ghs.json.example +7 -0
  12. package/shared/opencode.json.example +11 -0
  13. package/shared/references/coding-agent.md +533 -0
  14. package/shared/references/context-snapshot-guide.md +98 -0
  15. package/shared/references/examples.md +299 -0
  16. package/shared/references/plan-designer.md +163 -0
  17. package/shared/references/plan-reviewer.md +193 -0
  18. package/shared/references/sprint-agent.md +261 -0
  19. package/src/index.ts +9 -0
  20. package/src/lib/assets.ts +31 -0
  21. package/src/lib/codegraph.ts +66 -0
  22. package/src/lib/config.ts +278 -0
  23. package/src/lib/nonce.ts +56 -0
  24. package/src/lib/parse.ts +175 -0
  25. package/src/lib/paths.ts +26 -0
  26. package/src/lib/project.ts +28 -0
  27. package/src/lib/scripts/append-progress-session.ts +178 -0
  28. package/src/lib/scripts/append-sprint.ts +121 -0
  29. package/src/lib/scripts/archive-sprint.ts +583 -0
  30. package/src/lib/scripts/init-project.ts +291 -0
  31. package/src/lib/scripts/parallel-utils.ts +380 -0
  32. package/src/lib/scripts/parse-completion-signal.ts +584 -0
  33. package/src/lib/scripts/parse-delimited-output.ts +632 -0
  34. package/src/lib/scripts/resolve-project-dir.ts +130 -0
  35. package/src/lib/scripts/status.ts +292 -0
  36. package/src/lib/scripts/update-feature-status.ts +169 -0
  37. package/src/lib/scripts/validate-structure.ts +290 -0
  38. package/src/lib/state.ts +305 -0
  39. package/src/plugin.ts +76 -0
  40. package/src/prompts/context-codegraph.ts +65 -0
  41. package/src/prompts/context-grep.ts +68 -0
  42. package/src/prompts/feature-impl.ts +78 -0
  43. package/src/prompts/plan-designer.ts +59 -0
  44. package/src/prompts/plan-reviewer.ts +61 -0
  45. package/src/prompts/sprint-planning.ts +47 -0
  46. package/src/tools/archive.ts +278 -0
  47. package/src/tools/code.ts +448 -0
  48. package/src/tools/config.ts +182 -0
  49. package/src/tools/force-archive.ts +195 -0
  50. package/src/tools/init.ts +193 -0
  51. package/src/tools/plan-finalize.ts +333 -0
  52. package/src/tools/plan-review.ts +759 -0
  53. package/src/tools/plan-start.ts +232 -0
  54. package/src/tools/sprint.ts +213 -0
  55. package/src/tools/status.ts +51 -0
@@ -0,0 +1,65 @@
1
+ // LLM-facing dispatch instruction for the `ghs-context-haiku` subagent —
2
+ // CODEGRAPH path.
3
+ //
4
+ // `ghs-plan-start` (s3-feat-006) probes `detectCodegraph(projectDir)`
5
+ // (s3-feat-002). When `.codegraph/` is present, the dispatcher selects THIS
6
+ // prompt: it tells the main AI to dispatch `ghs-context-haiku` via the Task
7
+ // tool with codegraph-first instructions. The subagent prefers the
8
+ // `codegraph_*` MCP tools (sub-second symbol/edge/flow queries) over manual
9
+ // `grep`+`read` crawling, falling back to file reads only for specific
10
+ // implementation details the graph doesn't surface.
11
+ //
12
+ // This constant is the dispatch directive the main chat AI reads from the
13
+ // `ghs-plan-start` tool result (plan §3.5 / §3.7 step: Task:
14
+ // ghs-context-haiku). It is NOT the verbatim prompt body baked into the
15
+ // subagent template (that lives in `shared/agents/ghs-context-haiku.md.template`,
16
+ // s3-feat-001) — it is the tighter, command-style steering text consumed in
17
+ // the tool result.
18
+ //
19
+ // The snapshot output is wrapped in the
20
+ // `<<<CONTEXT_SNAPSHOT_START>>>` / `<<<CONTEXT_SNAPSHOT_END>>>` delimiters
21
+ // (plan §3.3) so `parse-delimited-output.ts` (s3-feat-003) can extract it in
22
+ // the subsequent `ghs-plan-review(snapshot)` call.
23
+ //
24
+ // Language policy (per CLAUDE.md): human-readable prose is 中文, code
25
+ // identifiers / field names / delimiter tokens stay English.
26
+
27
+ /**
28
+ * Dispatch instruction for the context-haiku subagent when codegraph is
29
+ * available.
30
+ *
31
+ * Returned by `ghs-plan-start` when `detectCodegraph()` returns `true`. It
32
+ * steers the subagent to prefer graph queries (`codegraph_explore`,
33
+ * `codegraph_callers`, ...) over brute-force grep, and to emit the snapshot
34
+ * inside the snapshot delimiters.
35
+ *
36
+ * Kept to ~600-1200 chars. For the human-readable snapshot format reference,
37
+ * see `shared/references/context-snapshot-guide.md`.
38
+ */
39
+ export const CONTEXT_CODEGRAPH_PROMPT = `检测到 \`.codegraph/\` 已初始化 —— 本轮 plan 走 codegraph 路径。请用 Task tool 派发 \`ghs-context-haiku\` subagent 收集项目上下文快照。subagent 应优先用 codegraph MCP 工具查询符号/调用图/数据流,仅在 graph 未覆盖具体实现细节时才补读源文件。详见 shared/references/context-snapshot-guide.md。
40
+
41
+ 输入给 subagent(拼在 Task 派发的 prompt 里):
42
+ - 需求描述(用于做 relevance filter —— 只收录与需求可能相关的代码)
43
+ - project_dir(若与当前工作目录不同)
44
+
45
+ 推荐的 codegraph 查询顺序(subagent 应遵循):
46
+ 1. \`codegraph_status\` —— 确认索引健康(文件数/节点数/边数),判断是否值得信赖
47
+ 2. \`codegraph_explore "<需求关键词 + 模块名>"\` —— 一次性取回相关符号的源码(PRIMARY,多数情况这一个调用就够)
48
+ 3. \`codegraph_callers\` / \`codegraph_callees\` / \`codegraph_impact\` —— 查调用路径 / 影响面(用于关键流程与重构边界)
49
+ 4. 仅当 graph 未覆盖某个具体实现时,才用 \`read\` / \`glob\` / \`grep\` 补读个别文件
50
+
51
+ 快照内容(subagent 须产出,压缩到原始源码的 50-70%):
52
+ - 技术栈(语言/版本、运行时/框架、关键依赖、构建系统、测试框架)
53
+ - 目录结构(关键文件一行注释)
54
+ - 架构摘要(入口点、模块职责、数据模型、关键模式)
55
+ - 与需求相关的代码摘录(函数签名、schema、路由、类型定义 —— 不要整文件粘贴)
56
+
57
+ 分隔标记契约(硬性,parser 据此提取 snapshot):
58
+ - snapshot 全文必须放在 \`<<<CONTEXT_SNAPSHOT_START>>>\` 与 \`<<<CONTEXT_SNAPSHOT_END>>>\` 之间,两个标记各占独立一行
59
+ - 不要把标记或内容包进 markdown 代码围栏(不要用三反引号包裹)
60
+ - 不要翻译/改写标记:禁止 \`《《CONTEXT_SNAPSHOT_START》》\`、\`<<CONTEXT_SNAPSHOT_START>>\`、\`<<< CONTEXT_SNAPSHOT_START >>>\` 等变体
61
+ - 使用字面 ASCII 字符 \`<\`、\`>\`、\`_\`
62
+
63
+ 输出语言策略(与 CLAUDE.md 一致):快照正文/模块描述/注释用中文;代码标识符、字段名、文件路径、类型名用英文。
64
+
65
+ 收到 subagent 的分隔标记输出后,请把整段(含标记)原样作为 \`snapshot\` 参数调用 \`ghs-plan-review\` 进入 snapshot 模式,parser 会提取快照并派发下一步 designer。`;
@@ -0,0 +1,68 @@
1
+ // LLM-facing dispatch instruction for the `ghs-context-haiku` subagent —
2
+ // GREP FALLBACK path.
3
+ //
4
+ // `ghs-plan-start` (s3-feat-006) probes `detectCodegraph(projectDir)`
5
+ // (s3-feat-002). When `.codegraph/` is ABSENT (or the probe fails
6
+ // defensively), the dispatcher selects THIS prompt: it tells the main AI to
7
+ // dispatch `ghs-context-haiku` via the Task tool with grep/glob/read-first
8
+ // instructions. There are no codegraph MCP tools available, so the subagent
9
+ // builds the snapshot by manual traversal — dependency manifest → directory
10
+ // tree → entry point → config → requirement-relevant files.
11
+ //
12
+ // This constant is the dispatch directive the main chat AI reads from the
13
+ // `ghs-plan-start` tool result (plan §3.5 / §3.7 step: Task:
14
+ // ghs-context-haiku). It is NOT the verbatim prompt body baked into the
15
+ // subagent template (that lives in
16
+ // `shared/agents/ghs-context-haiku.md.template`, s3-feat-001) — it is the
17
+ // tighter, command-style steering text consumed in the tool result.
18
+ //
19
+ // The snapshot output is wrapped in the
20
+ // `<<<CONTEXT_SNAPSHOT_START>>>` / `<<<CONTEXT_SNAPSHOT_END>>>` delimiters
21
+ // (plan §3.3) so `parse-delimited-output.ts` (s3-feat-003) can extract it in
22
+ // the subsequent `ghs-plan-review(snapshot)` call.
23
+ //
24
+ // Language policy (per CLAUDE.md): human-readable prose is 中文, code
25
+ // identifiers / field names / delimiter tokens stay English.
26
+
27
+ /**
28
+ * Dispatch instruction for the context-haiku subagent when codegraph is NOT
29
+ * available.
30
+ *
31
+ * Returned by `ghs-plan-start` when `detectCodegraph()` returns `false`. It
32
+ * steers the subagent to build the snapshot via `read` / `glob` / `grep`
33
+ * (read-only `bash`) following the extraction order in
34
+ * `shared/references/context-snapshot-guide.md`, and to emit the snapshot
35
+ * inside the snapshot delimiters.
36
+ *
37
+ * Kept to ~600-1200 chars. For the human-readable snapshot format reference,
38
+ * see `shared/references/context-snapshot-guide.md`.
39
+ */
40
+ export const CONTEXT_GREP_PROMPT = `未检测到 \`.codegraph/\` —— 本轮 plan 走 grep 回退路径(无 codegraph MCP 工具可用)。请用 Task tool 派发 \`ghs-context-haiku\` subagent 收集项目上下文快照。subagent 用 \`read\` / \`glob\` / \`grep\`(read-only \`bash\`)手动遍历代码库构建快照。详见 shared/references/context-snapshot-guide.md。
41
+
42
+ 输入给 subagent(拼在 Task 派发的 prompt 里):
43
+ - 需求描述(用于做 relevance filter —— 只收录与需求可能相关的代码)
44
+ - project_dir(若与当前工作目录不同)
45
+
46
+ 推荐的提取顺序(subagent 应遵循,参照 context-snapshot-guide.md 的 Extraction Process):
47
+ 1. 读依赖清单:\`package.json\` / \`requirements.txt\` / \`Cargo.toml\` 等
48
+ 2. 取目录结构:\`glob\` 或 \`find\`(排除 node_modules、.git、build 产物)
49
+ 3. 读入口点:\`src/index.ts\` / \`main.py\` / \`src/lib.rs\` 等
50
+ 4. 读配置文件:\`.env.example\`、config 模块、数据库初始化
51
+ 5. 读与需求相关的文件:需求所属目录下的关键源文件
52
+ 6. 汇总压缩:把发现压缩成快照格式(目标 50-70% 压缩比,不要整文件粘贴)
53
+
54
+ 快照内容(subagent 须产出):
55
+ - 技术栈(语言/版本、运行时/框架、关键依赖、构建系统、测试框架)
56
+ - 目录结构(关键文件一行注释)
57
+ - 架构摘要(入口点、模块职责、数据模型、关键模式)
58
+ - 与需求相关的代码摘录(函数签名、schema、路由、类型定义 —— 只收录可能相关的,排除无关模块)
59
+
60
+ 分隔标记契约(硬性,parser 据此提取 snapshot):
61
+ - snapshot 全文必须放在 \`<<<CONTEXT_SNAPSHOT_START>>>\` 与 \`<<<CONTEXT_SNAPSHOT_END>>>\` 之间,两个标记各占独立一行
62
+ - 不要把标记或内容包进 markdown 代码围栏(不要用三反引号包裹)
63
+ - 不要翻译/改写标记:禁止 \`《《CONTEXT_SNAPSHOT_START》》\`、\`<<CONTEXT_SNAPSHOT_START>>\`、\`<<< CONTEXT_SNAPSHOT_START >>>\` 等变体
64
+ - 使用字面 ASCII 字符 \`<\`、\`>\`、\`_\`
65
+
66
+ 输出语言策略(与 CLAUDE.md 一致):快照正文/模块描述/注释用中文;代码标识符、字段名、文件路径、类型名用英文。
67
+
68
+ 收到 subagent 的分隔标记输出后,请把整段(含标记)原样作为 \`snapshot\` 参数调用 \`ghs-plan-review\` 进入 snapshot 模式,parser 会提取快照并派发下一步 designer。`;
@@ -0,0 +1,78 @@
1
+ // LLM-facing dispatch prompt for the coding subagent that implements a single
2
+ // feature.
3
+ //
4
+ // This constant is the dispatch text the main AI consumes after it selects a
5
+ // ready feature via the `ghs-code` tool (plan §3.5 / §3.7 step 5: code). It
6
+ // tells the main AI to use the Task tool to spawn an isolated coding subagent
7
+ // that implements ONE feature end-to-end (context-reset → read features.json →
8
+ // implement + verify AC → single commit → return EXACTLY ONE completion
9
+ // signal). The subagent's return signal is then parsed by
10
+ // `parse-completion-signal.ts` (s4-feat-001).
11
+ //
12
+ // It is NOT a verbatim copy of `shared/references/coding-agent.md` — that file
13
+ // is the human-readable reference doc (session protocol, parallel mode,
14
+ // testing requirements). This constant is the tight, command-style dispatch
15
+ // template the main chat AI reads from the `ghs-code` tool result and hands to
16
+ // the subagent. It distills coding-agent.md §Implementation Process +
17
+ // §Session Protocol + the §Critical Rules into the smallest prompt that
18
+ // reliably drives a single-feature implementation.
19
+ //
20
+ // Two placeholders MUST be substituted by the `ghs-code` tool before the main
21
+ // AI dispatches the subagent:
22
+ // - `<PROJECT_DIR>` → absolute project root (from `resolveProjectDir`)
23
+ // - `<feature_id>` → the selected feature's `id` (e.g. `s4-feat-004`)
24
+ // The prompt deliberately contains NO inline feature details — the subagent
25
+ // reads them from `.ghs/features.json` per Task step 1 (single source of
26
+ // truth). It also needs no `<sprint_id>` placeholder: the subagent locates its
27
+ // feature by `id == "<feature_id>"` across `sprints[].features[]`.
28
+ //
29
+ // Language policy (per CLAUDE.md): human-readable prose is 中文, code
30
+ // identifiers / field names / signal tokens / git commands stay English. The
31
+ // subagent is told to follow the same policy when it writes its commit message
32
+ // and any docs.
33
+
34
+ /**
35
+ * Dispatch prompt template for the single-feature coding subagent.
36
+ *
37
+ * Returned as part of the `ghs-code` tool's output text so the main AI can
38
+ * hand it (with `<PROJECT_DIR>` and `<feature_id>` substituted) to the Task
39
+ * tool to spawn the implementer.
40
+ *
41
+ * Kept to ~900-1600 chars: enough to pin the context-reset stance, the
42
+ * features.json-first feature lookup, the implement-and-verify loop, the
43
+ * single-commit contract (explicit `git add` paths, no `.ghs/` writes), and
44
+ * the hard completion-signal protocol; short enough to land in a tool result
45
+ * without crowding it. For the full human-readable reference, see
46
+ * `shared/references/coding-agent.md`.
47
+ */
48
+ export const FEATURE_IMPL_PROMPT = `实现本项目的一个 feature。用 Task tool 派发一个隔离的 coding subagent 完成端到端实现,返回的完成信号由 parse-completion-signal 解析。详见 shared/references/coding-agent.md。
49
+
50
+ 派发前请替换两个占位符(ghs-code tool 已注入):\`<PROJECT_DIR>\`(项目根绝对路径)与 \`<feature_id>\`(所选 feature 的 id)。prompt 内不含任何 inline feature 细节——subagent 自己从 features.json 读取。
51
+
52
+ subagent prompt 正文(原样交给 Task tool):
53
+
54
+ ---
55
+ Implement ONE feature for this project.
56
+
57
+ ## CONTEXT RESET - READ THIS FIRST
58
+ This is an isolated task. Disregard prior context, assume nothing, read files fresh, start clean.
59
+
60
+ ## Your Task
61
+ 1. 打开 \`<PROJECT_DIR>/.ghs/features.json\`,在 \`sprints[].features[]\` 中按 \`id == "<feature_id>"\` 找到你的 feature。读取它的 \`description\`/\`acceptance_criteria\`/\`technical_notes\`/\`files_affected\`——这些是你的唯一事实来源,不是 title。
62
+ 2. 若所属 sprint 含 \`plan_ref\` 字段,打开该 plan 文件(相对项目根)并读取 \`technical_notes\` 引用的章节(例如 "参考 plan §3.3 ..." 即读 §3.3)。若 \`plan_ref\` 缺失或文件不存在,记一行 warning 后照 \`technical_notes\` 原文执行。
63
+ 3. 读 \`<PROJECT_DIR>/.ghs/progress.md\` 了解近期项目上下文。
64
+ 4. 按 coding-agent.md 工作流实现 feature,并验证全部 \`acceptance_criteria\` 已满足。
65
+ 5. 运行 lint/build,然后做**恰好一次** commit:显式 \`git add <每个修改过的实现文件路径>\`(不要 \`git add -A\`/\`git add .\`,不要提交任何 \`.ghs/*\` 文件),commit message 为 \`feat(<scope>): <简述> (Feature: <feature_id>)\`。
66
+
67
+ ## Feature ID
68
+ <feature_id>
69
+
70
+ ## Critical Rules
71
+ - 不要修改任何 \`.ghs/\` 文件。可以 READ features.json,但 MUST NOT write。
72
+ - 只聚焦本 feature,不要 scope-creep 到其它 feature。
73
+ - 结尾输出 EXACTLY ONE 信号,独占一行:\`FEATURE COMPLETE: <feature_id>\` 或 \`FEATURE BLOCKED: <feature_id> - <原因>\`。禁止小写、禁止 "FEATURE COMPLETED"、禁止自然语言、禁止中文变体(如 "特性完成")。
74
+ ---
75
+
76
+ 语言策略(与 CLAUDE.md 一致):commit message 与任何文档用中文正文;代码标识符、字段名、枚举值、文件路径、日志/错误信息、完成信号 token 用英文。
77
+
78
+ 收到 subagent 返回后,把原始输出按 Verification Phase 交给 parse-completion-signal 解析(\`status: completed | blocked | unknown\`),据此更新 features.json 与 progress.md。unknown 时走 Format Recovery 重试,耗尽后用 AskUserQuestion 让用户裁决。`;
@@ -0,0 +1,59 @@
1
+ // LLM-facing dispatch instruction for the `ghs-plan-designer` subagent.
2
+ //
3
+ // This constant is the dispatch text the main AI consumes after it returns a
4
+ // snapshot via `ghs-plan-review(snapshot)` (plan §3.5 / §3.7 step: Task:
5
+ // ghs-plan-designer). It tells the main AI to use the Task tool to dispatch
6
+ // the `ghs-plan-designer` subagent, what input to feed it (the context
7
+ // snapshot + the requirement), and the hard delimiter protocol the designer
8
+ // must obey so `parse-delimited-output.ts` (s3-feat-003) can extract its
9
+ // output in the subsequent `ghs-plan-review(plan)` call.
10
+ //
11
+ // It is NOT a verbatim copy of `shared/references/plan-designer.md` — that
12
+ // file is the human-readable reference doc baked into the designer
13
+ // subagent's own prompt body (s3-feat-001 template). This constant is the
14
+ // tight, command-style dispatch directive the main chat AI reads from the
15
+ // tool result to drive the plan loop forward.
16
+ //
17
+ // Language policy (per CLAUDE.md): human-readable prose is 中文, code
18
+ // identifiers / field names / delimiter tokens stay English.
19
+
20
+ /**
21
+ * Dispatch instruction for the plan-designer subagent.
22
+ *
23
+ * Returned as part of a plan tool's output text so the main AI immediately
24
+ * knows how to spawn `ghs-plan-designer` via the Task tool and what output
25
+ * contract to enforce.
26
+ *
27
+ * Kept to ~600-1200 chars: enough to pin the delimiter protocol, the
28
+ * snapshot-first working approach, the completion signal, and the language
29
+ * policy; short enough to land in a tool result without crowding it. For the
30
+ * full human-readable reference, see `shared/references/plan-designer.md`.
31
+ */
32
+ export const PLAN_DESIGNER_PROMPT = `接下来请用 Task tool 派发 \`ghs-plan-designer\` subagent 设计技术方案。subagent 收到的是 context snapshot + 需求描述,产出一份可执行的技术 plan。详见 shared/references/plan-designer.md。
33
+
34
+ 输入给 subagent(拼在 Task 派发的 prompt 里):
35
+ - 需求描述(用户原始需求 + 任何已澄清的约束)
36
+ - context snapshot 路径或全文(上一轮 ghs-context-haiku 的产物)
37
+
38
+ 工作方式(务必让 subagent 遵守):
39
+ - 先读 context snapshot,再按需补读个别源文件;snapshot 已覆盖架构概览/模块职责/数据模型时不要再全文复读
40
+ - 方案须与现有架构一致、分阶段可执行、可回滚、可测试
41
+ - 若需要用户澄清无法从代码/需求推断的事项,首行输出 \`QUESTION: <问题>\`
42
+
43
+ 分隔标记契约(硬性,parser 据此提取 plan):
44
+ - plan 全文必须放在 \`<<<PLAN_START>>>\` 与 \`<<<PLAN_END>>>\` 之间,两个标记各占独立一行
45
+ - 不要把标记或内容包进 markdown 代码围栏(不要用三反引号包裹)
46
+ - 不要翻译/改写标记:禁止 \`《《PLAN_START》》\`、\`<<PLAN_START>>\`、\`<<< PLAN_START >>>\` 等变体
47
+ - 使用字面 ASCII 字符 \`<\`、\`>\`、\`_\`
48
+ - 正确示例:
49
+ <<<PLAN_START>>>
50
+ # 方案标题
51
+ ...正文...
52
+ <<<PLAN_END>>>
53
+ PLAN DESIGN COMPLETE
54
+
55
+ 完成信号:设计完成输出 \`PLAN DESIGN COMPLETE\`;需用户澄清输出 \`QUESTION: <具体问题>\`(不要用 QUESTION 替代你自己的技术判断)。
56
+
57
+ 输出语言策略(与 CLAUDE.md 一致):方案正文/章节标题/风险描述用中文;代码标识符、字段名、枚举值、文件路径、日志/错误信息用英文。
58
+
59
+ 收到 subagent 的分隔标记输出后,请把整段(含标记)原样作为 \`plan\` 参数调用 \`ghs-plan-review\` 进入 plan 模式评审。`;
@@ -0,0 +1,61 @@
1
+ // LLM-facing dispatch instruction for the `ghs-plan-reviewer` subagent.
2
+ //
3
+ // This constant is the dispatch text the main AI consumes after it returns a
4
+ // plan via `ghs-plan-review(plan)` (plan §3.5 / §3.7 step: Task:
5
+ // ghs-plan-reviewer). It tells the main AI to use the Task tool to dispatch
6
+ // the `ghs-plan-reviewer` subagent, what input to feed it (the plan + the
7
+ // context snapshot), and the hard delimiter protocol + verdict line the
8
+ // reviewer must obey so `parse-delimited-output.ts` (s3-feat-003) can extract
9
+ // its output and the dispatcher can read PASS/FAIL in the subsequent
10
+ // `ghs-plan-review(review)` call.
11
+ //
12
+ // It is NOT a verbatim copy of `shared/references/plan-reviewer.md` — that
13
+ // file is the human-readable reference doc baked into the reviewer
14
+ // subagent's own prompt body (s3-feat-001 template). This constant is the
15
+ // tight, command-style dispatch directive the main chat AI reads from the
16
+ // tool result.
17
+ //
18
+ // Language policy (per CLAUDE.md): human-readable prose is 中文, code
19
+ // identifiers / field names / delimiter tokens stay English.
20
+
21
+ /**
22
+ * Dispatch instruction for the plan-reviewer subagent.
23
+ *
24
+ * Returned as part of a plan tool's output text so the main AI immediately
25
+ * knows how to spawn `ghs-plan-reviewer` via the Task tool and what output
26
+ * contract to enforce (delimiter + verdict line).
27
+ *
28
+ * Kept to ~600-1300 chars: enough to pin the delimiter protocol, the
29
+ * verdict line format, the severity taxonomy, the snapshot-first review
30
+ * approach, and the language policy. For the full human-readable reference,
31
+ * see `shared/references/plan-reviewer.md`.
32
+ */
33
+ export const PLAN_REVIEWER_PROMPT = `接下来请用 Task tool 派发 \`ghs-plan-reviewer\` subagent 评审技术方案。subagent 从架构师视角审查 plan 的可行性/完整性/可执行性,产出带严重度分级的评审报告。详见 shared/references/plan-reviewer.md。
34
+
35
+ 输入给 subagent(拼在 Task 派发的 prompt 里):
36
+ - 待评审的 plan 全文(上一轮 ghs-plan-designer 的产物)
37
+ - context snapshot 路径或全文(用于核对 plan 与现有架构的一致性)
38
+
39
+ 工作方式(务必让 subagent 遵守):
40
+ - 先读 context snapshot 建立架构基线,再读 plan,按架构上下文逐条评估
41
+ - 只在需要核对 plan 中某条具体断言时才补读源文件
42
+ - 每条反馈必须带严重度:Severe(会导致 bug/数据丢失/安全漏洞/逻辑不自洽)/ Medium(方向对但实现路径有问题)/ Optimization(不影响实现但能提质)
43
+ - 你是 guardian 不是 grader:反馈要帮 designer 改进方案,但真正的架构缺陷绝不能放过
44
+
45
+ 分隔标记契约(硬性,parser 据此提取 review):
46
+ - review 报告全文必须放在 \`<<<REVIEW_START>>>\` 与 \`<<<REVIEW_END>>>\` 之间,两个标记各占独立一行
47
+ - 不要把标记或内容包进 markdown 代码围栏(不要用三反引号包裹)
48
+ - 不要翻译/改写标记:禁止 \`《《REVIEW_START》》\`、\`<<REVIEW_START>>\`、\`<<< REVIEW_START >>>\` 等变体
49
+ - 使用字面 ASCII 字符 \`<\`、\`>\`、\`_\`
50
+
51
+ 裁决行(硬性,dispatcher 据此读 PASS/FAIL):
52
+ - 紧跟 \`<<<REVIEW_END>>>\` 之后,独占一行输出:
53
+ \`REVIEW COMPLETE | Verdict: PASS|FAIL | Severe: X Medium: Y Optimization: Z\`
54
+ - PASS = 仅 Optimization 项、无 Severe/Medium;FAIL = 存在任一 Severe/Medium
55
+ - 缺失或格式错误的裁决行会被 dispatcher 重试
56
+
57
+ 完成信号:评审完成输出上述裁决行;需用户澄清(真·业务抉择)输出 \`QUESTION: <具体问题>\`。
58
+
59
+ 输出语言策略(与 CLAUDE.md 一致):评审报告正文/章节标题/问题描述用中文;代码标识符、字段名、严重度枚举(Severe/Medium/Optimization)、Verdict 值(PASS/FAIL)用英文。
60
+
61
+ 收到 subagent 的分隔标记输出后,请把整段(含标记 + 裁决行)原样作为 \`review\` 参数调用 \`ghs-plan-review\` 进入 review 模式判定。PASS 则推进到 \`ghs-plan-finalize\`;FAIL 则触发 designer 修订(附评审报告)。`;
@@ -0,0 +1,47 @@
1
+ // LLM-facing instruction returned by the `ghs-sprint` tool after it writes a
2
+ // new sprint skeleton to features.json.
3
+ //
4
+ // This prompt is NOT a verbatim copy of shared/references/sprint-agent.md —
5
+ // that file is a human-readable reference doc. This constant is the
6
+ // command-style instruction the AI consumes to decompose requirements into
7
+ // atomic features (s2-feat-002). It distills the source's Steps 2-5
8
+ // (Create Atomic Features / Categorize and Prioritize / Define Acceptance
9
+ // Criteria / Order by Dependencies) into the tightest form that reliably
10
+ // steers feature decomposition.
11
+ //
12
+ // Language policy (per CLAUDE.md): human-readable prose is 中文, code
13
+ // identifiers / field names / enum values stay English. The AI is told to
14
+ // follow the same policy when writing features.
15
+
16
+ /**
17
+ * The sprint-planning instruction. Returned as part of the `ghs-sprint`
18
+ * tool's output text so the AI immediately knows how to break the sprint
19
+ * goal into atomic features and append them via `update-feature-status`.
20
+ *
21
+ * Length is kept ~500-1500 chars: enough to specify the feature schema,
22
+ * atomic-feature criteria, AC format, dependency ordering, and complexity
23
+ * estimation; short enough to land in the tool result without crowding it.
24
+ * For the full human-readable reference, see
25
+ * shared/references/sprint-agent.md.
26
+ */
27
+ export const SPRINT_PLANNING_PROMPT = `Sprint 骨架已写入 features.json。接下来请把 sprint goal 拆成 atomic features,逐个用 update-feature-status 追加(status 初始为 pending)。详见 shared/references/sprint-agent.md。
28
+
29
+ 拆分原则(每个 feature 必须同时满足):
30
+ - 原子性:单个 session 可完成(< 4 小时)
31
+ - 独立性:依赖最少,可单独验证
32
+ - 可测:有明确、可执行的验收标准
33
+ - 有价值:交付可感知的用户/系统价值
34
+
35
+ feature schema(字段名用英文):
36
+ { id, category, priority, title, description, acceptance_criteria[], technical_notes, status, dependencies[], estimated_complexity, files_affected[] }
37
+ - id:s{N}-feat-{NNN}(N = 当前 sprint 编号,NNN 零填充 3 位,sprint 内顺序递增)
38
+ - category:core | ui | api | auth | data | infra
39
+ - priority:high(sprint 阻塞项/核心)| medium(重要不阻塞)| low(可推迟)
40
+ - status:pending | in_progress | completed | blocked(blocked 必须带 blocked_reason)
41
+ - estimated_complexity:small(<2h)| medium(2-4h)| large(4h+,必须继续拆分)
42
+
43
+ acceptance_criteria 写法(Given/When/Then):用可验证的条件描述,避免主观措辞。示例:Given features.json 存在,when 调 appendSprint,then 新 sprint 出现在 sprints 末尾且原对象不被修改。
44
+
45
+ 依赖排序:基础设施先行 → 核心功能 → 支撑功能 → UI;有 dependencies 的 feature 必须排在被依赖项之后。在 progress.md 记录实现顺序与理由。
46
+
47
+ 语言策略(与 CLAUDE.md 一致):description / acceptance_criteria / technical_notes 等人类可读字段用中文;代码标识符、字段名、枚举值、日志/错误信息用英文。`;
@@ -0,0 +1,278 @@
1
+ // `ghs-archive` tool — archive completed sprints (or preview / list them).
2
+ //
3
+ // Three modes (mutually exclusive; `list` wins, then `dry_run`, then the
4
+ // default `archive`):
5
+ // - `list: true` → print all completed sprints without archiving.
6
+ // - `dry_run: true` → preview what would be archived; write no files.
7
+ // - (neither) → actually move completed sprints to `.ghs/archived/`.
8
+ //
9
+ // The dry-run path ALSO writes a nonce file (`.ghs/.force-archive-nonce`)
10
+ // when there are any *incomplete* sprints remaining. This is the gate the
11
+ // `ghs-force-archive` tool reads back: the user is expected to transcribe
12
+ // the nonce to confirm a subsequent force-archive call. The nonce persists
13
+ // across calls on disk (simpler than threading per-invocation state through
14
+ // the tool protocol — see the feature's technical_notes).
15
+ //
16
+ // Output text mirrors the Python `archive_sprint.py` script byte-for-byte
17
+ // (verified by `test/equivalence/archive.test.ts`); we append a short
18
+ // nonce section to the dry-run output when one is issued.
19
+
20
+ import { tool } from "@opencode-ai/plugin";
21
+ import type { ToolContext } from "@opencode-ai/plugin/tool";
22
+ import { existsSync } from "node:fs";
23
+ import { readFile, writeFile, unlink } from "node:fs/promises";
24
+ import { join, resolve } from "node:path";
25
+
26
+ import {
27
+ archiveSprints,
28
+ getAllSprints,
29
+ getCompletedSprints,
30
+ formatArchiveReport,
31
+ formatListReport,
32
+ type ArchivedSprintInfo,
33
+ } from "../lib/scripts/archive-sprint.ts";
34
+ import { generateNonce } from "../lib/nonce.ts";
35
+ import { resolveProjectDir } from "../lib/project.ts";
36
+
37
+ /** Path of the per-project nonce file used by `ghs-force-archive`. */
38
+ function nonceFilePath(projectDir: string): string {
39
+ return join(resolve(projectDir), ".ghs", ".force-archive-nonce");
40
+ }
41
+
42
+ /**
43
+ * Write the issued nonce to disk so `ghs-force-archive` can read it back.
44
+ * Truncated on each call so stale nonces from a prior run can't be reused.
45
+ */
46
+ async function writeNonce(projectDir: string, nonce: string): Promise<void> {
47
+ await writeFile(nonceFilePath(projectDir), nonce, "utf8");
48
+ }
49
+
50
+ /**
51
+ * Read (and delete) the nonce file. Deleting on read means a captured nonce
52
+ * can only be transcribed once — a stale `ghs-force-archive` call after the
53
+ * nonce has been consumed will fail the gate.
54
+ *
55
+ * Returns the nonce string, or null when the file is absent. If `consume`
56
+ * is false the file is left in place (used by `ghs-force-archive`'s own
57
+ * pre-flight check; the actual consume happens after the gate passes).
58
+ */
59
+ async function readNonce(
60
+ projectDir: string,
61
+ consume: boolean,
62
+ ): Promise<string | null> {
63
+ const path = nonceFilePath(projectDir);
64
+ if (!existsSync(path)) {
65
+ return null;
66
+ }
67
+ const nonce = (await readFile(path, "utf8")).trim();
68
+ if (consume) {
69
+ try {
70
+ await unlink(path);
71
+ } catch {
72
+ // Nonce file may already be gone (concurrent call) — non-fatal.
73
+ }
74
+ }
75
+ return nonce;
76
+ }
77
+
78
+ /**
79
+ * Count sprints that are NOT completed (i.e. could still be force-archived).
80
+ * Used to decide whether the nonce gate is even relevant for this project.
81
+ */
82
+ function countIncompleteSprints(featuresData: Record<string, unknown>): number {
83
+ const all = getAllSprints(featuresData);
84
+ return all.filter((s) => s.status !== "completed").length;
85
+ }
86
+
87
+ /** Load features.json from `<projectDir>/.ghs/features.json`. Returns null if missing. */
88
+ async function loadFeaturesData(
89
+ projectDir: string,
90
+ ): Promise<Record<string, unknown> | null> {
91
+ const path = join(resolve(projectDir), ".ghs", "features.json");
92
+ if (!existsSync(path)) {
93
+ return null;
94
+ }
95
+ const text = await readFile(path, "utf8");
96
+ return JSON.parse(text) as Record<string, unknown>;
97
+ }
98
+
99
+ // Exported for `ghs-force-archive` to read + consume the nonce file.
100
+ export { readNonce, writeNonce, nonceFilePath };
101
+
102
+ /**
103
+ * The `ghs-archive` tool definition. Registered under the `ghs-archive` key.
104
+ */
105
+ export const archiveTool = tool({
106
+ description:
107
+ "Archive completed sprints to `.ghs/archived/` and remove them from features.json. " +
108
+ "Three modes: `list: true` lists completed sprints without changing anything; " +
109
+ "`dry_run: true` previews what would be archived without writing files; " +
110
+ "neither flag actually moves completed sprints to `.ghs/archived/`. " +
111
+ "Only sprints with status 'completed' are archived — use `ghs-force-archive` to archive incomplete sprints.",
112
+ args: {
113
+ dry_run: tool.schema
114
+ .boolean()
115
+ .optional()
116
+ .describe(
117
+ "When true, preview what would be archived without modifying any files.",
118
+ ),
119
+ list: tool.schema
120
+ .boolean()
121
+ .optional()
122
+ .describe(
123
+ "When true, list completed sprints without archiving. Takes precedence over `dry_run`.",
124
+ ),
125
+ project_dir: tool.schema
126
+ .string()
127
+ .optional()
128
+ .describe(
129
+ "Absolute path of the project root. Defaults to the opencode session's worktree/directory.",
130
+ ),
131
+ },
132
+ async execute(
133
+ args: { dry_run?: boolean; list?: boolean; project_dir?: string },
134
+ ctx: ToolContext,
135
+ ): Promise<string> {
136
+ const projectDir = args.project_dir
137
+ ? resolve(args.project_dir)
138
+ : resolveProjectDir(ctx);
139
+
140
+ const listMode = args.list === true;
141
+ const dryRunMode = !listMode && args.dry_run === true;
142
+ const archiveMode = !listMode && !dryRunMode;
143
+
144
+ // ----- list mode -----
145
+ if (listMode) {
146
+ const features = await loadFeaturesData(projectDir);
147
+ if (!features) {
148
+ return [
149
+ "=== Sprint Archiver ===",
150
+ "",
151
+ `Project directory: ${projectDir}`,
152
+ "",
153
+ "❌ features.json not found. Run `ghs-init` first.",
154
+ ].join("\n") + "\n";
155
+ }
156
+ const completed = getCompletedSprints(features);
157
+ return formatListReport({
158
+ projectDir,
159
+ force: false,
160
+ sprints: completed,
161
+ });
162
+ }
163
+
164
+ // ----- dry-run + archive modes both go through archiveSprints -----
165
+ // We call archiveSprints twice for the archive path: once with
166
+ // dryRun=true to get the preview info, then once with dryRun=false to
167
+ // actually move the files. This keeps the report identical to Python's
168
+ // (which prints the per-sprint "Archiving sprint:" line *before* the
169
+ // move) without reaching into archiveSprintFiles' internals.
170
+ //
171
+ // For dry-run, the single dryRun=true call is enough.
172
+ const preview = await archiveSprints({
173
+ projectDir,
174
+ dryRun: true,
175
+ force: false,
176
+ });
177
+
178
+ // Pre-load the features data — we need it to compute remainingCount and
179
+ // to decide whether to issue a force-archive nonce below.
180
+ const featuresBefore = await loadFeaturesData(projectDir);
181
+
182
+ if (preview.length === 0) {
183
+ // Nothing completed to archive. But if there are *incomplete* sprints,
184
+ // we still want to issue a nonce so the user can `ghs-force-archive`
185
+ // them. Short-circuit the normal report and append a nonce hint.
186
+ const report = formatArchiveReport({
187
+ projectDir,
188
+ mode: dryRunMode ? "dry-run" : "archive",
189
+ force: false,
190
+ sprintsConsidered: [],
191
+ archived: [],
192
+ remainingCount: featuresBefore ? getAllSprints(featuresBefore).length : 0,
193
+ resetProgress: false,
194
+ });
195
+ return maybeAppendNonce(projectDir, report, featuresBefore);
196
+ }
197
+
198
+ let archived: ArchivedSprintInfo[];
199
+ let featuresAfter: Record<string, unknown> | null = null;
200
+ if (dryRunMode) {
201
+ archived = preview;
202
+ } else {
203
+ // archiveMode: actually archive.
204
+ archived = await archiveSprints({
205
+ projectDir,
206
+ dryRun: false,
207
+ force: false,
208
+ });
209
+ featuresAfter = await loadFeaturesData(projectDir);
210
+ }
211
+
212
+ // Compute remainingCount + resetProgress for the report.
213
+ const featuresForReport = featuresAfter ?? (await loadFeaturesData(projectDir));
214
+ const remainingSprints: Record<string, unknown>[] = featuresForReport
215
+ ? getAllSprints(featuresForReport)
216
+ : [];
217
+ const remainingCount = remainingSprints.length;
218
+ const resetProgress = archived.length > 0 && remainingCount === 0;
219
+
220
+ // sprintsConsidered for the report: the preview (completed sprints).
221
+ // `formatArchiveReport` accepts `sprintsConsidered: Sprint[]` where
222
+ // `Sprint` is `Record<string, unknown>` internally — so the mapped
223
+ // object literal matches without a cast.
224
+ const sprintsConsidered = preview.map((info) => ({
225
+ id: info.sprint_id,
226
+ name: info.sprint_name,
227
+ status: info.sprint_status,
228
+ }));
229
+
230
+ const report = formatArchiveReport({
231
+ projectDir,
232
+ mode: archiveMode ? "archive" : "dry-run",
233
+ force: false,
234
+ sprintsConsidered,
235
+ archived,
236
+ remainingCount,
237
+ resetProgress,
238
+ });
239
+
240
+ // ----- nonce gate hook -----
241
+ return maybeAppendNonce(projectDir, report, featuresForReport);
242
+ },
243
+ });
244
+
245
+ /**
246
+ * Append a force-archive nonce hint to the report when there are any
247
+ * incomplete sprints remaining. Issues a fresh nonce and writes it to
248
+ * `.ghs/.force-archive-nonce`. When there are no incomplete sprints,
249
+ * returns `report` unchanged.
250
+ *
251
+ * The features data is passed in (rather than re-loaded) so callers that
252
+ * already have a recent copy can avoid a redundant disk read.
253
+ */
254
+ async function maybeAppendNonce(
255
+ projectDir: string,
256
+ report: string,
257
+ features: Record<string, unknown> | null,
258
+ ): Promise<string> {
259
+ if (!features) {
260
+ return report;
261
+ }
262
+ const incomplete = countIncompleteSprints(features);
263
+ if (incomplete === 0) {
264
+ return report;
265
+ }
266
+ const nonce = generateNonce();
267
+ await writeNonce(projectDir, nonce);
268
+ return (
269
+ report +
270
+ [
271
+ "",
272
+ "⚠️ Incomplete sprints remain and can only be removed with `ghs-force-archive`.",
273
+ ` To confirm a force-archive, transcribe this token back: ${nonce}`,
274
+ " (Call `ghs-force-archive` with `transcription: \"<token>\"`.)",
275
+ ].join("\n") +
276
+ "\n"
277
+ );
278
+ }