@geminix/gxpm 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 (299) hide show
  1. package/AGENTS.md +148 -0
  2. package/CANON.md +53 -0
  3. package/CLAUDE.md +60 -0
  4. package/CONTEXT.md +49 -0
  5. package/DEBUG.md +59 -0
  6. package/ISSUE_CONTEXT.md +25 -0
  7. package/README.md +143 -0
  8. package/VERSION +1 -0
  9. package/agents/cleanup-auditor/cleanup-auditor.md +56 -0
  10. package/agents/grill-master.md +26 -0
  11. package/agents/implementer.md +32 -0
  12. package/agents/review-army/accessibility-reviewer.md +54 -0
  13. package/agents/review-army/code-quality-reviewer.md +54 -0
  14. package/agents/review-army/security-reviewer.md +56 -0
  15. package/agents/review-army/spec-compliance-reviewer.md +51 -0
  16. package/agents/review-army/test-reviewer.md +55 -0
  17. package/agents/reviewer.md +59 -0
  18. package/agents/ship-audit-army/docs-auditor.md +53 -0
  19. package/agents/ship-audit-army/performance-auditor.md +52 -0
  20. package/agents/ship-audit-army/security-auditor.md +52 -0
  21. package/agents/specifier.md +55 -0
  22. package/agents/triage-officer.md +27 -0
  23. package/bin/gxpm +17 -0
  24. package/bin/gxpm-browser +17 -0
  25. package/bin/gxpm-config +15 -0
  26. package/bin/gxpm-eval +13 -0
  27. package/bin/gxpm-global-discover +15 -0
  28. package/bin/gxpm-init +38 -0
  29. package/bin/gxpm-investigate +194 -0
  30. package/bin/gxpm-uninstall +15 -0
  31. package/bin/gxpm-update-check +165 -0
  32. package/commands/build.md +40 -0
  33. package/commands/help.md +53 -0
  34. package/commands/plan.md +34 -0
  35. package/commands/refine.md +46 -0
  36. package/commands/review.md +34 -0
  37. package/commands/ship.md +37 -0
  38. package/core/ac-check.ts +20 -0
  39. package/core/agent-runtime.ts +363 -0
  40. package/core/artifact-validator.ts +151 -0
  41. package/core/artifacts.ts +313 -0
  42. package/core/autopilot.ts +250 -0
  43. package/core/capabilities.ts +779 -0
  44. package/core/checkpoint.ts +370 -0
  45. package/core/cleanup.ts +32 -0
  46. package/core/command-probe.ts +82 -0
  47. package/core/config.ts +533 -0
  48. package/core/contracts/behavior-spec.schema.ts +38 -0
  49. package/core/contracts/converter.ts +61 -0
  50. package/core/contracts/host.ts +43 -0
  51. package/core/converters/converter.ts +93 -0
  52. package/core/converters/index.ts +8 -0
  53. package/core/converters/managed-artifact.ts +119 -0
  54. package/core/converters/parser.ts +159 -0
  55. package/core/converters/template-renderer.ts +35 -0
  56. package/core/converters/writer.ts +61 -0
  57. package/core/dag-executor.ts +426 -0
  58. package/core/dag-loader.ts +292 -0
  59. package/core/dag-schemas.ts +150 -0
  60. package/core/dispatch.ts +125 -0
  61. package/core/evidence.ts +148 -0
  62. package/core/gate.ts +269 -0
  63. package/core/hook-engine.ts +566 -0
  64. package/core/host-probe.ts +64 -0
  65. package/core/implement.ts +16 -0
  66. package/core/isolation-errors.ts +174 -0
  67. package/core/isolation-resolver.ts +921 -0
  68. package/core/issue-context.ts +381 -0
  69. package/core/issue-readiness.ts +457 -0
  70. package/core/issue-sync.ts +427 -0
  71. package/core/issues.ts +132 -0
  72. package/core/land.ts +108 -0
  73. package/core/orchestrator.ts +54 -0
  74. package/core/phase-artifact.ts +32 -0
  75. package/core/phase-gates.ts +130 -0
  76. package/core/phase-rewind.ts +94 -0
  77. package/core/plan-lint.ts +61 -0
  78. package/core/plan.ts +77 -0
  79. package/core/port-allocation.ts +50 -0
  80. package/core/pr-check.ts +15 -0
  81. package/core/preset-system/preset-resolver.ts +221 -0
  82. package/core/project-init-status.ts +127 -0
  83. package/core/qa.ts +15 -0
  84. package/core/resilience.ts +165 -0
  85. package/core/runs.ts +288 -0
  86. package/core/safe-path.test.ts +80 -0
  87. package/core/safe-path.ts +60 -0
  88. package/core/sdd-gate.test.ts +98 -0
  89. package/core/sdd-gate.ts +134 -0
  90. package/core/self-review.ts +62 -0
  91. package/core/session.ts +70 -0
  92. package/core/ship.ts +86 -0
  93. package/core/specify.ts +173 -0
  94. package/core/state.ts +1002 -0
  95. package/core/template-engine.ts +152 -0
  96. package/core/template-resolver.test.ts +70 -0
  97. package/core/template-resolver.ts +156 -0
  98. package/core/triage.ts +26 -0
  99. package/core/verify.ts +15 -0
  100. package/core/wiki-native.ts +2423 -0
  101. package/core/wiki.ts +27 -0
  102. package/core/workflow-event-emitter.ts +163 -0
  103. package/core/workflows/engine.ts +273 -0
  104. package/core/workflows/expressions.ts +76 -0
  105. package/core/workflows/index.ts +38 -0
  106. package/core/workflows/steps/command.ts +43 -0
  107. package/core/workflows/steps/gate.ts +47 -0
  108. package/core/workflows/steps/gxpm.ts +44 -0
  109. package/core/workflows/steps/linear.ts +31 -0
  110. package/core/workflows/steps/shell.ts +65 -0
  111. package/core/workflows/types.ts +62 -0
  112. package/core/workspace-runtime.ts +227 -0
  113. package/core/worktree-init-steps.ts +647 -0
  114. package/core/worktree-init.ts +330 -0
  115. package/core/worktree-owner.ts +143 -0
  116. package/docs/GXPM_VERIFY.md +98 -0
  117. package/docs/INSTALL_FOR_AGENTS.md +113 -0
  118. package/docs/README.md +57 -0
  119. package/docs/adr/adr-005-multi-platform-skill-converter.md +72 -0
  120. package/docs/agents/domain.md +30 -0
  121. package/docs/agents/issue-tracker.md +30 -0
  122. package/docs/agents/triage-labels.md +32 -0
  123. package/docs/architecture/gxpm-architecture-diagram.md +265 -0
  124. package/docs/architecture/gxpm-current-architecture.md +175 -0
  125. package/docs/architecture/gxpm-current-flow.md +278 -0
  126. package/docs/architecture/gxpm-replacement-architecture.md +211 -0
  127. package/docs/architecture/gxpm-target-architecture.md +449 -0
  128. package/docs/architecture/gxpm-v0-contract.md +311 -0
  129. package/docs/architecture/layered-workflow-boundaries.md +193 -0
  130. package/docs/architecture/preset-system.md +126 -0
  131. package/docs/architecture/scaffold-northstar.md +23 -0
  132. package/docs/brainstorms/2026-05-14-bdd-then-tdd-design.md +320 -0
  133. package/docs/brainstorms/README.md +22 -0
  134. package/docs/brainstorms/docs-knowledge-system-requirements.md +29 -0
  135. package/docs/governance/beta-skill-promotion.md +39 -0
  136. package/docs/governance/development-contract.md +144 -0
  137. package/docs/governance/gherkin-style.md +90 -0
  138. package/docs/governance/host-adapter.md +56 -0
  139. package/docs/governance/skill-authoring.md +87 -0
  140. package/docs/governance/skill-testing.md +356 -0
  141. package/docs/governance/template-authoring.md +53 -0
  142. package/docs/migrations/v0.2.md +51 -0
  143. package/docs/plans/README.md +23 -0
  144. package/docs/plans/bdd-then-tdd-plan.md +1767 -0
  145. package/docs/plans/docs-knowledge-system-plan.md +31 -0
  146. package/docs/plans/spec-kit-sdd-adoption-plan.md +305 -0
  147. package/docs/research/agents-md-best-practices.md +207 -0
  148. package/docs/research/archon-study.md +351 -0
  149. package/docs/research/claude-hooks-study.md +440 -0
  150. package/docs/research/codex-hooks-study.md +624 -0
  151. package/docs/research/everything-claude-code-study.md +252 -0
  152. package/docs/research/from-skills-to-layered-workflow.md +322 -0
  153. package/docs/research/gsd-study.md +69 -0
  154. package/docs/research/kimi-hooks-study.md +274 -0
  155. package/docs/research/mattpocock-skills-comparison.md +429 -0
  156. package/docs/research/mattpocock-skills-study.md +275 -0
  157. package/docs/research/oh-my-codex-study.md +279 -0
  158. package/docs/research/perplexity-agent-skills-design.md +168 -0
  159. package/docs/research/pmc-gstack-skill-study.md +122 -0
  160. package/docs/research/spec-kit-study.md +224 -0
  161. package/docs/research/superpowers-study.md +209 -0
  162. package/docs/roadmap/initial-roadmap.md +53 -0
  163. package/docs/solutions/README.md +45 -0
  164. package/docs/solutions/artifact-nesting-recovery.md +58 -0
  165. package/docs/solutions/session-context-restore-practice.md +67 -0
  166. package/docs/solutions/workflow/version-drift-recovery.md +49 -0
  167. package/docs/solutions/worktree-gate-recovery.md +62 -0
  168. package/docs/specs/README.md +28 -0
  169. package/docs/specs/claude.md +45 -0
  170. package/docs/specs/codex.md +44 -0
  171. package/docs/specs/cursor.md +44 -0
  172. package/hosts/adapters/claude.ts +29 -0
  173. package/hosts/adapters/codex.ts +27 -0
  174. package/hosts/adapters/cursor.ts +27 -0
  175. package/hosts/adapters/kimi.ts +27 -0
  176. package/hosts/claude.ts +23 -0
  177. package/hosts/codex.ts +26 -0
  178. package/hosts/cursor.ts +19 -0
  179. package/hosts/index.ts +33 -0
  180. package/hosts/registry.test.ts +52 -0
  181. package/hosts/registry.ts +57 -0
  182. package/hosts/schema.ts +58 -0
  183. package/package.json +52 -0
  184. package/scripts/browser.ts +185 -0
  185. package/scripts/cleanup.ts +142 -0
  186. package/scripts/commands/artifact.ts +115 -0
  187. package/scripts/commands/autopilot.ts +143 -0
  188. package/scripts/commands/capability.ts +57 -0
  189. package/scripts/commands/config.ts +69 -0
  190. package/scripts/commands/dag.ts +126 -0
  191. package/scripts/commands/feedback.ts +123 -0
  192. package/scripts/commands/gate.ts +291 -0
  193. package/scripts/commands/helpers.ts +126 -0
  194. package/scripts/commands/hook.ts +66 -0
  195. package/scripts/commands/init.ts +515 -0
  196. package/scripts/commands/issue.ts +825 -0
  197. package/scripts/commands/phase.ts +61 -0
  198. package/scripts/commands/preset.ts +159 -0
  199. package/scripts/commands/runtime.ts +199 -0
  200. package/scripts/commands/specify.ts +71 -0
  201. package/scripts/commands/upgrade.ts +243 -0
  202. package/scripts/commands/verify.ts +183 -0
  203. package/scripts/commands/wiki.ts +242 -0
  204. package/scripts/commands/workflow.ts +131 -0
  205. package/scripts/dev-skill.ts +55 -0
  206. package/scripts/discover-skills.ts +116 -0
  207. package/scripts/doctor.ts +410 -0
  208. package/scripts/dogfood-check.ts +125 -0
  209. package/scripts/eval-functional.ts +218 -0
  210. package/scripts/eval.ts +246 -0
  211. package/scripts/gen-skill-docs.ts +201 -0
  212. package/scripts/global-discover.ts +217 -0
  213. package/scripts/governance-check.ts +75 -0
  214. package/scripts/gxpm-check.ts +12 -0
  215. package/scripts/gxpm.ts +216 -0
  216. package/scripts/host-config.ts +62 -0
  217. package/scripts/install-claude-hooks.ts +138 -0
  218. package/scripts/install-codex-hooks.ts +271 -0
  219. package/scripts/install-hooks.ts +128 -0
  220. package/scripts/install-kimi-hooks.ts +92 -0
  221. package/scripts/install-skill.ts +184 -0
  222. package/scripts/phase-artifact-commands.ts +100 -0
  223. package/scripts/post-land-sync.ts +46 -0
  224. package/scripts/scaffold-check.ts +85 -0
  225. package/scripts/skill-naming-check.ts +78 -0
  226. package/scripts/skill-structure-check.ts +157 -0
  227. package/scripts/skills-lock-check.ts +60 -0
  228. package/scripts/sync-markdown-artifacts.ts +172 -0
  229. package/scripts/uninstall.ts +162 -0
  230. package/scripts/version.ts +47 -0
  231. package/scripts/wait-pr-ready.ts +407 -0
  232. package/skills/gxpm/SKILL.md +485 -0
  233. package/skills/gxpm/SKILL.md.tmpl +422 -0
  234. package/skills/gxpm/references/CANON.md +53 -0
  235. package/skills/gxpm/references/key-rules.md +130 -0
  236. package/skills/gxpm-architecture/SKILL.md +106 -0
  237. package/skills/gxpm-architecture/references/DEEPENING.md +37 -0
  238. package/skills/gxpm-architecture/references/INTERFACE-DESIGN.md +44 -0
  239. package/skills/gxpm-autopilot/SKILL.md +116 -0
  240. package/skills/gxpm-autopilot/SKILL.md.tmpl +107 -0
  241. package/skills/gxpm-browser/SKILL.md +105 -0
  242. package/skills/gxpm-browser/SKILL.md.tmpl +41 -0
  243. package/skills/gxpm-browser/references/commands.md +43 -0
  244. package/skills/gxpm-browser/references/evidence-path.md +20 -0
  245. package/skills/gxpm-build/SKILL.md +78 -0
  246. package/skills/gxpm-cleanup/SKILL.md +76 -0
  247. package/skills/gxpm-debug-issue/SKILL.md +39 -0
  248. package/skills/gxpm-diagnose/SKILL.md +220 -0
  249. package/skills/gxpm-diagnose/SKILL.md.tmpl +31 -0
  250. package/skills/gxpm-diagnose/references/feedback-loop.md +34 -0
  251. package/skills/gxpm-diagnose/references/feedback-loops.md +43 -0
  252. package/skills/gxpm-diagnose/references/phases.md +60 -0
  253. package/skills/gxpm-eval/SKILL.md +78 -0
  254. package/skills/gxpm-explore-codebase/SKILL.md +36 -0
  255. package/skills/gxpm-explore-codebase/scripts/summarize-communities.ts +51 -0
  256. package/skills/gxpm-feedback/SKILL.md +122 -0
  257. package/skills/gxpm-grill/SKILL.md +159 -0
  258. package/skills/gxpm-grill/SKILL.md.tmpl +77 -0
  259. package/skills/gxpm-grill/references/documentation-templates.md +56 -0
  260. package/skills/gxpm-grill/references/process.md +25 -0
  261. package/skills/gxpm-handoff/SKILL.md +112 -0
  262. package/skills/gxpm-hygiene/SKILL.md +69 -0
  263. package/skills/gxpm-implementer/SKILL.md +142 -0
  264. package/skills/gxpm-implementer/SKILL.md.tmpl +141 -0
  265. package/skills/gxpm-linear/SKILL.md +282 -0
  266. package/skills/gxpm-linear/SKILL.md.tmpl +86 -0
  267. package/skills/gxpm-linear/references/commands.md +75 -0
  268. package/skills/gxpm-linear/references/workflows.md +120 -0
  269. package/skills/gxpm-planning/SKILL.md +134 -0
  270. package/skills/gxpm-prototype/SKILL.md +64 -0
  271. package/skills/gxpm-refactor-safely/SKILL.md +62 -0
  272. package/skills/gxpm-review-army/SKILL.md +117 -0
  273. package/skills/gxpm-review-changes/SKILL.md +36 -0
  274. package/skills/gxpm-setup/SKILL.md +101 -0
  275. package/skills/gxpm-specifier/SKILL.md +135 -0
  276. package/skills/gxpm-tdd/SKILL.md +187 -0
  277. package/skills/gxpm-tdd/references/interface-design.md +23 -0
  278. package/skills/gxpm-tdd/references/mocking.md +27 -0
  279. package/skills/gxpm-tdd/references/red-green-refactor.md +61 -0
  280. package/skills/gxpm-tdd/references/troubleshooting.md +28 -0
  281. package/skills/gxpm-tdd/references/workflow.md +50 -0
  282. package/skills/gxpm-tdd/testing-anti-patterns.tmpl +304 -0
  283. package/skills/gxpm-triage/SKILL.md +160 -0
  284. package/skills/gxpm-verify/SKILL.md +107 -0
  285. package/skills/gxpm-write-skill/SKILL.md +131 -0
  286. package/skills/gxpm-zoom-out/SKILL.md +69 -0
  287. package/skills/maintain-hygiene-skills-lock/SKILL.md +54 -0
  288. package/skills/maintain-hygiene-skills-lock/SKILL.md.tmpl +53 -0
  289. package/templates/constitution-template.md +63 -0
  290. package/templates/hooks/gxpm-commit-msg +16 -0
  291. package/templates/hooks/gxpm-post-checkout +19 -0
  292. package/templates/hooks/gxpm-post-commit +7 -0
  293. package/templates/hooks/gxpm-post-merge +29 -0
  294. package/templates/hooks/gxpm-pre-commit +39 -0
  295. package/templates/hooks/gxpm-pre-push +33 -0
  296. package/templates/plan-template.md.tmpl +46 -0
  297. package/templates/spec-template.md.tmpl +63 -0
  298. package/templates/specify-stub.tmpl +22 -0
  299. package/templates/tasks-template.md.tmpl +32 -0
@@ -0,0 +1,1767 @@
1
+ # BDD-Then-TDD 强制流程 Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** 在 gxpm 状态机的 `dispatch → implement` 之间插入新的 `specify` phase,强制所有 issue 在进入 implement 之前产出并由用户确认 Gherkin 行为规约。
6
+
7
+ **Architecture:** Minimal Incremental — 新增 `behavior-spec` artifact type、`specify` phase、`specifier` agent、`gxpm-specifier` skill、Gherkin 规则治理文档;扩展 phase-gate 校验 `confirmedAt` 字段;升级 `gxpm-tdd` skill 强制引用 specify.json。
8
+
9
+ **Tech Stack:** TypeScript + Bun + Zod + 现有 gxpm artifact/state framework。
10
+
11
+ **Spec Reference:** [docs/brainstorms/2026-05-14-bdd-then-tdd-design.md](../brainstorms/2026-05-14-bdd-then-tdd-design.md)
12
+
13
+ ---
14
+
15
+ ## File Structure
16
+
17
+ **新建文件:**
18
+ - `core/specify.ts` — initializeSpecify / confirmSpecify / reviseSpecify 函数
19
+ - `core/contracts/behavior-spec.schema.ts` — Zod schema 单独导出
20
+ - `scripts/commands/specify.ts` — CLI confirm/show/revise 子命令路由
21
+ - `agents/specifier.md` — Specifier agent 模板
22
+ - `skills/gxpm-specifier/SKILL.md` — BDD 行为设计 skill
23
+ - `docs/governance/gherkin-style.md` — Gherkin 写作规则(吸收 AutomationPanda)
24
+ - `templates/specify-stub.tmpl` — test stub 文件生成模板
25
+ - `test/core/specify.test.ts` — initializeSpecify/confirmSpecify 单元测试
26
+ - `test/core/phase-gates.specify.test.ts` — specify gate 校验单元测试
27
+ - `test/functional/gxpm-specify/init-confirm.test.ts` — CLI 端到端集成测试
28
+
29
+ **修改文件:**
30
+ - `core/artifacts.ts` — 注册 `"behavior-spec"` 到 `ARTIFACT_TYPES`
31
+ - `core/state.ts` — 在 `GXPM_PHASES` 数组插入 `"specify"`;扩展 `assertArtifactGate` 校验 `confirmedAt`
32
+ - `core/phase-gates.ts` — 拆 dispatch→implement 规则为 dispatch→specify + specify→implement
33
+ - `scripts/phase-artifact-commands.ts` — 注册 `behavior-spec` handler
34
+ - `scripts/gxpm.ts` — 添加 `specify` 命令分支
35
+ - `skills/gxpm-tdd/SKILL.md` — 强制引用 specify.json 作为 RED 测试输入源
36
+
37
+ ---
38
+
39
+ ## Task 1: 注册 behavior-spec artifact type 与 Zod schema
40
+
41
+ **Files:**
42
+ - Create: `core/contracts/behavior-spec.schema.ts`
43
+ - Modify: `core/artifacts.ts:14-30`
44
+ - Test: `test/core/specify.test.ts`
45
+
46
+ - [ ] **Step 1: Write the failing test for schema validation**
47
+
48
+ Create `test/core/specify.test.ts`:
49
+
50
+ ```ts
51
+ import { describe, expect, it } from "bun:test";
52
+ import { BehaviorSpecSchema, type BehaviorSpec } from "../../core/contracts/behavior-spec.schema";
53
+
54
+ describe("BehaviorSpecSchema", () => {
55
+ it("accepts a minimal valid spec", () => {
56
+ const valid: BehaviorSpec = {
57
+ $schema: "behavior-spec.v1",
58
+ issueId: "GXPM-001",
59
+ createdAt: "2026-05-14T00:00:00.000Z",
60
+ createdBy: "specifier@test",
61
+ confirmedAt: null,
62
+ confirmedBy: null,
63
+ feature: { title: "T", asA: "user", iWant: "X", soThat: "Y" },
64
+ scenarios: [
65
+ {
66
+ id: "scn-01",
67
+ name: "happy",
68
+ given: ["a"],
69
+ when: "b",
70
+ then: ["c"],
71
+ examples: [],
72
+ stubPath: "test/foo.test.ts:test_x",
73
+ },
74
+ ],
75
+ guidelinesRef: "docs/governance/gherkin-style.md@v1",
76
+ };
77
+ expect(() => BehaviorSpecSchema.parse(valid)).not.toThrow();
78
+ });
79
+
80
+ it("rejects empty scenarios array", () => {
81
+ const invalid = { scenarios: [] };
82
+ expect(() => BehaviorSpecSchema.parse(invalid)).toThrow();
83
+ });
84
+
85
+ it("rejects scenario missing given/when/then", () => {
86
+ const invalid = {
87
+ $schema: "behavior-spec.v1",
88
+ scenarios: [{ id: "x", name: "n", given: [], when: "", then: [] }],
89
+ };
90
+ expect(() => BehaviorSpecSchema.parse(invalid)).toThrow();
91
+ });
92
+ });
93
+ ```
94
+
95
+ - [ ] **Step 2: Run test to verify it fails**
96
+
97
+ Run: `bun test test/core/specify.test.ts`
98
+ Expected: FAIL with "Cannot find module '../../core/contracts/behavior-spec.schema'"
99
+
100
+ - [ ] **Step 3: Create the schema file**
101
+
102
+ Create `core/contracts/behavior-spec.schema.ts`:
103
+
104
+ ```ts
105
+ import { z } from "zod";
106
+
107
+ const ScenarioSchema = z.object({
108
+ id: z.string().min(1),
109
+ name: z.string().min(1),
110
+ given: z.array(z.string().min(1)).min(1),
111
+ when: z.string().min(1),
112
+ then: z.array(z.string().min(1)).min(1),
113
+ examples: z.array(z.record(z.string(), z.unknown())).default([]),
114
+ stubPath: z.string().min(1),
115
+ });
116
+
117
+ const FeatureSchema = z.object({
118
+ title: z.string().min(1),
119
+ asA: z.string().min(1),
120
+ iWant: z.string().min(1),
121
+ soThat: z.string().min(1),
122
+ });
123
+
124
+ export const BehaviorSpecSchema = z.object({
125
+ $schema: z.literal("behavior-spec.v1"),
126
+ issueId: z.string().min(1),
127
+ createdAt: z.string().datetime(),
128
+ createdBy: z.string().min(1),
129
+ confirmedAt: z.string().datetime().nullable(),
130
+ confirmedBy: z.string().nullable(),
131
+ feature: FeatureSchema,
132
+ scenarios: z.array(ScenarioSchema).min(1),
133
+ guidelinesRef: z.string().min(1),
134
+ }).refine(
135
+ (spec) =>
136
+ (spec.confirmedAt === null && spec.confirmedBy === null) ||
137
+ (spec.confirmedAt !== null && spec.confirmedBy !== null),
138
+ { message: "confirmedAt and confirmedBy must both be null or both be set" },
139
+ );
140
+
141
+ export type BehaviorSpec = z.infer<typeof BehaviorSpecSchema>;
142
+ export type BehaviorSpecScenario = z.infer<typeof ScenarioSchema>;
143
+ ```
144
+
145
+ - [ ] **Step 4: Register artifact type**
146
+
147
+ Edit `core/artifacts.ts` lines 14-30 — extend `ARTIFACT_TYPES`:
148
+
149
+ ```ts
150
+ export const ARTIFACT_TYPES = [
151
+ "issue-intake",
152
+ "triage-report",
153
+ "autopilot-grant",
154
+ "acceptance-contract",
155
+ "implementation-plan",
156
+ "dispatch-handoff",
157
+ "behavior-spec",
158
+ "wiki-context",
159
+ "local-verify",
160
+ "acceptance-check",
161
+ "self-review",
162
+ "ship-readiness",
163
+ "pr-check",
164
+ "verify-findings",
165
+ "qa-findings",
166
+ "land-findings",
167
+ ] as const;
168
+ ```
169
+
170
+ - [ ] **Step 5: Run test to verify it passes**
171
+
172
+ Run: `bun test test/core/specify.test.ts`
173
+ Expected: PASS — 3 tests pass.
174
+
175
+ - [ ] **Step 6: Commit**
176
+
177
+ ```bash
178
+ git add core/contracts/behavior-spec.schema.ts core/artifacts.ts test/core/specify.test.ts
179
+ git commit -m "GXPM-XXX add behavior-spec artifact type and zod schema
180
+
181
+ Registers behavior-spec as an artifact type and defines its
182
+ zod schema with structural constraints: scenarios non-empty,
183
+ given/when/then non-empty, confirmedAt/confirmedBy paired.
184
+
185
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Task 2: 在 GXPM_PHASES 中插入 specify
191
+
192
+ **Files:**
193
+ - Modify: `core/state.ts:17-30`
194
+ - Test: `test/core/state.specify-phase.test.ts`
195
+
196
+ - [ ] **Step 1: Write the failing test**
197
+
198
+ Create `test/core/state.specify-phase.test.ts`:
199
+
200
+ ```ts
201
+ import { describe, expect, it } from "bun:test";
202
+ import { GXPM_PHASES } from "../../core/state";
203
+
204
+ describe("GXPM_PHASES with specify", () => {
205
+ it("places specify between dispatch and implement", () => {
206
+ const phases = GXPM_PHASES as readonly string[];
207
+ const dispatchIdx = phases.indexOf("dispatch");
208
+ const specifyIdx = phases.indexOf("specify");
209
+ const implementIdx = phases.indexOf("implement");
210
+ expect(specifyIdx).toBeGreaterThan(-1);
211
+ expect(specifyIdx).toBe(dispatchIdx + 1);
212
+ expect(implementIdx).toBe(specifyIdx + 1);
213
+ });
214
+ });
215
+ ```
216
+
217
+ - [ ] **Step 2: Run test to verify it fails**
218
+
219
+ Run: `bun test test/core/state.specify-phase.test.ts`
220
+ Expected: FAIL — `expect(specifyIdx).toBeGreaterThan(-1)` fails.
221
+
222
+ - [ ] **Step 3: Insert specify in GXPM_PHASES**
223
+
224
+ Edit `core/state.ts` lines 17-30:
225
+
226
+ ```ts
227
+ export const GXPM_PHASES = [
228
+ "triage",
229
+ "plan",
230
+ "dispatch",
231
+ "specify",
232
+ "implement",
233
+ "local-verify",
234
+ "ac-check",
235
+ "self-review",
236
+ "ship",
237
+ "pr-check",
238
+ "verify",
239
+ "qa",
240
+ "land",
241
+ ] as const;
242
+ ```
243
+
244
+ - [ ] **Step 4: Run test to verify it passes**
245
+
246
+ Run: `bun test test/core/state.specify-phase.test.ts`
247
+ Expected: PASS.
248
+
249
+ - [ ] **Step 5: Commit**
250
+
251
+ ```bash
252
+ git add core/state.ts test/core/state.specify-phase.test.ts
253
+ git commit -m "GXPM-XXX insert specify phase between dispatch and implement
254
+
255
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Task 3: 拆分 phase-gate 规则
261
+
262
+ **Files:**
263
+ - Modify: `core/phase-gates.ts:32-99`
264
+ - Test: `test/core/phase-gates.specify.test.ts`
265
+
266
+ - [ ] **Step 1: Write the failing test**
267
+
268
+ Create `test/core/phase-gates.specify.test.ts`:
269
+
270
+ ```ts
271
+ import { describe, expect, it } from "bun:test";
272
+ import { PHASE_GATE_RULES, getRequiredArtifactForTransition } from "../../core/phase-gates";
273
+
274
+ describe("phase-gates with specify", () => {
275
+ it("requires dispatch-handoff for dispatch→specify", () => {
276
+ expect(getRequiredArtifactForTransition("dispatch", "specify")).toBe("dispatch-handoff");
277
+ });
278
+
279
+ it("requires behavior-spec for specify→implement", () => {
280
+ expect(getRequiredArtifactForTransition("specify", "implement")).toBe("behavior-spec");
281
+ });
282
+
283
+ it("removes original dispatch→implement direct rule", () => {
284
+ const direct = PHASE_GATE_RULES.find(
285
+ (r) => r.fromPhase === "dispatch" && r.nextPhase === "implement",
286
+ );
287
+ expect(direct).toBeUndefined();
288
+ });
289
+ });
290
+ ```
291
+
292
+ - [ ] **Step 2: Run test to verify it fails**
293
+
294
+ Run: `bun test test/core/phase-gates.specify.test.ts`
295
+ Expected: FAIL — dispatch→specify rule missing.
296
+
297
+ - [ ] **Step 3: Edit PHASE_GATE_RULES**
298
+
299
+ Edit `core/phase-gates.ts` — replace lines 45-50 (the `dispatch-handoff` block) with two entries:
300
+
301
+ ```ts
302
+ {
303
+ command: "gxpm dispatch init <issue-id>",
304
+ fromPhase: "dispatch",
305
+ nextPhase: "specify",
306
+ requiredArtifact: "dispatch-handoff",
307
+ },
308
+ {
309
+ command: "gxpm specify init <issue-id>",
310
+ fromPhase: "specify",
311
+ nextPhase: "implement",
312
+ requiredArtifact: "behavior-spec",
313
+ },
314
+ ```
315
+
316
+ - [ ] **Step 4: Run test to verify it passes**
317
+
318
+ Run: `bun test test/core/phase-gates.specify.test.ts`
319
+ Expected: PASS — 3 tests pass.
320
+
321
+ - [ ] **Step 5: Run full test suite to catch regressions**
322
+
323
+ Run: `bun test test/core/`
324
+ Expected: All pass. If pre-existing tests reference `dispatch→implement` directly, update them to `dispatch→specify→implement`.
325
+
326
+ - [ ] **Step 6: Commit**
327
+
328
+ ```bash
329
+ git add core/phase-gates.ts test/core/phase-gates.specify.test.ts
330
+ git commit -m "GXPM-XXX split dispatch->implement phase gate via specify
331
+
332
+ Replaces direct dispatch->implement rule with two rules:
333
+ dispatch->specify (requires dispatch-handoff) and
334
+ specify->implement (requires behavior-spec). Test coverage
335
+ for both transitions added.
336
+
337
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Task 4: 扩展 assertArtifactGate 校验 confirmedAt
343
+
344
+ **Files:**
345
+ - Modify: `core/state.ts:690-745` (around `assertArtifactGate`)
346
+ - Test: `test/core/phase-gates.specify.test.ts` (append)
347
+
348
+ - [ ] **Step 1: Write the failing test (append to existing file)**
349
+
350
+ Append to `test/core/phase-gates.specify.test.ts`:
351
+
352
+ ```ts
353
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
354
+ import { tmpdir } from "node:os";
355
+ import { join } from "node:path";
356
+ import { createIssueState, transitionIssuePhase, type IssueState } from "../../core/state";
357
+
358
+ function seedIssueAt(phase: IssueState["currentPhase"]) {
359
+ const root = mkdtempSync(join(tmpdir(), "gxpm-specify-"));
360
+ createIssueState({ root, issueId: "G-1", issueType: "feature" });
361
+ // walk through phases up to `phase`
362
+ const path = ["plan", "dispatch", "specify"] as const;
363
+ for (const p of path) {
364
+ if (p === phase) break;
365
+ // seed required artifacts for transition
366
+ const artDir = join(root, ".gxpm", "issues", "G-1", "artifacts");
367
+ mkdirSync(artDir, { recursive: true });
368
+ writeFileSync(
369
+ join(artDir, "acceptance-contract.json"),
370
+ JSON.stringify({ schemaVersion: 1, issueId: "G-1", type: "acceptance-contract", writtenAt: "now", payload: {} }),
371
+ );
372
+ // ... similar seeding for downstream artifacts
373
+ }
374
+ return root;
375
+ }
376
+
377
+ describe("specify→implement gate confirmedAt check", () => {
378
+ it("blocks transition when behavior-spec.confirmedAt is null", () => {
379
+ // Setup: seed an issue in specify phase with unconfirmed behavior-spec
380
+ const root = mkdtempSync(join(tmpdir(), "gxpm-specify-gate-"));
381
+ createIssueState({ root, issueId: "G-2", issueType: "feature" });
382
+ // Force phase to specify via direct state write (test helper).
383
+ const stateFile = join(root, ".gxpm", "issues", "G-2", "state.json");
384
+ const raw = JSON.parse(require("node:fs").readFileSync(stateFile, "utf8"));
385
+ raw.currentPhase = "specify";
386
+ raw.phaseHistory.push({ phase: "specify", enteredAt: "2026-05-14T00:00:00Z", fromPhase: "dispatch" });
387
+ writeFileSync(stateFile, JSON.stringify(raw, null, 2));
388
+ // Seed unconfirmed behavior-spec
389
+ const artPath = join(root, ".gxpm", "issues", "G-2", "artifacts", "behavior-spec.json");
390
+ mkdirSync(join(root, ".gxpm", "issues", "G-2", "artifacts"), { recursive: true });
391
+ writeFileSync(
392
+ artPath,
393
+ JSON.stringify({
394
+ schemaVersion: 1,
395
+ issueId: "G-2",
396
+ type: "behavior-spec",
397
+ writtenAt: "2026-05-14T00:00:00Z",
398
+ payload: { $schema: "behavior-spec.v1", confirmedAt: null, scenarios: [] },
399
+ }),
400
+ );
401
+ expect(() =>
402
+ transitionIssuePhase({ root, issueId: "G-2", nextPhase: "implement" }),
403
+ ).toThrow(/confirmedAt/);
404
+ rmSync(root, { recursive: true, force: true });
405
+ });
406
+ });
407
+ ```
408
+
409
+ - [ ] **Step 2: Run test to verify it fails**
410
+
411
+ Run: `bun test test/core/phase-gates.specify.test.ts`
412
+ Expected: FAIL — gate currently only checks file existence, not confirmedAt.
413
+
414
+ - [ ] **Step 3: Extend assertArtifactGate**
415
+
416
+ Edit `core/state.ts` around line 705 — after the `existsSync(requiredArtifactPath)` block, add behavior-spec confirmation check:
417
+
418
+ ```ts
419
+ if (existsSync(requiredArtifactPath)) {
420
+ // Extra check: behavior-spec must be confirmed before transitioning into implement.
421
+ if (requiredArtifact === "behavior-spec" && input.nextPhase === "implement") {
422
+ const raw = JSON.parse(readFileSync(requiredArtifactPath, "utf8"));
423
+ const confirmedAt = raw?.payload?.confirmedAt;
424
+ if (!confirmedAt) {
425
+ const now = new Date().toISOString();
426
+ appendIssueEvent({
427
+ issueDir: input.issueDir,
428
+ event: {
429
+ schemaVersion: 1,
430
+ type: "gate.blocked",
431
+ issueId: input.issueId,
432
+ timestamp: now,
433
+ sessionId: resolveSessionId(),
434
+ payload: {
435
+ fromPhase: input.fromPhase,
436
+ toPhase: input.nextPhase,
437
+ missingArtifact: "behavior-spec.confirmedAt",
438
+ },
439
+ },
440
+ });
441
+ throw new Error(
442
+ `behavior-spec exists but confirmedAt is null; run \`gxpm specify confirm ${input.issueId}\` to confirm`,
443
+ );
444
+ }
445
+ }
446
+ const now = new Date().toISOString();
447
+ appendIssueEvent({
448
+ issueDir: input.issueDir,
449
+ event: {
450
+ schemaVersion: 1,
451
+ type: "gate.passed",
452
+ issueId: input.issueId,
453
+ timestamp: now,
454
+ sessionId: resolveSessionId(),
455
+ payload: {
456
+ fromPhase: input.fromPhase,
457
+ toPhase: input.nextPhase,
458
+ requiredArtifact,
459
+ },
460
+ },
461
+ });
462
+ return;
463
+ }
464
+ ```
465
+
466
+ - [ ] **Step 4: Run test to verify it passes**
467
+
468
+ Run: `bun test test/core/phase-gates.specify.test.ts`
469
+ Expected: PASS.
470
+
471
+ - [ ] **Step 5: Commit**
472
+
473
+ ```bash
474
+ git add core/state.ts test/core/phase-gates.specify.test.ts
475
+ git commit -m "GXPM-XXX gate specify->implement on behavior-spec.confirmedAt
476
+
477
+ Phase-gate now reads behavior-spec.json payload and rejects
478
+ transition into implement if confirmedAt is null. Emits a
479
+ gate.blocked event with missingArtifact: behavior-spec.confirmedAt.
480
+
481
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
482
+ ```
483
+
484
+ ---
485
+
486
+ ## Task 4.5: 向后兼容 — 老 issue 跳过 specify gate
487
+
488
+ **Files:**
489
+ - Modify: `core/state.ts` `assertArtifactGate` (the block from Task 4)
490
+ - Test: `test/core/phase-gates.specify.test.ts` (append)
491
+
492
+ Background: spec § 2.4 / § 5.1 要求"若 issue 在 specify 引入日期前已进入 implement,跳过 specify-gate 校验"。
493
+
494
+ - [ ] **Step 1: Define the cutoff constant**
495
+
496
+ Edit `core/state.ts` — near the top (after existing constants), add:
497
+
498
+ ```ts
499
+ // Issues whose phaseHistory was created before this cutoff are exempt from the
500
+ // specify-gate (introduced 2026-05-14). Set to the merge date of the specify
501
+ // phase feature; do NOT change retroactively.
502
+ export const SPECIFY_PHASE_CUTOFF = "2026-05-14T00:00:00Z";
503
+ ```
504
+
505
+ - [ ] **Step 2: Write the failing test**
506
+
507
+ Append to `test/core/phase-gates.specify.test.ts`:
508
+
509
+ ```ts
510
+ import { SPECIFY_PHASE_CUTOFF } from "../../core/state";
511
+
512
+ describe("specify-gate backward compatibility", () => {
513
+ it("skips specify gate for issues that entered implement before cutoff", () => {
514
+ const root = mkdtempSync(join(tmpdir(), "gxpm-legacy-"));
515
+ createIssueState({ root, issueId: "G-LEGACY", issueType: "feature" });
516
+ const stateFile = join(root, ".gxpm", "issues", "G-LEGACY", "state.json");
517
+ const raw = JSON.parse(readFileSync(stateFile, "utf8"));
518
+ raw.currentPhase = "dispatch";
519
+ // phaseHistory shows implement was entered before cutoff
520
+ raw.phaseHistory = [
521
+ { phase: "triage", enteredAt: "2025-12-01T00:00:00Z", fromPhase: null },
522
+ { phase: "plan", enteredAt: "2025-12-02T00:00:00Z", fromPhase: "triage" },
523
+ { phase: "dispatch", enteredAt: "2025-12-03T00:00:00Z", fromPhase: "plan" },
524
+ { phase: "implement", enteredAt: "2025-12-04T00:00:00Z", fromPhase: "dispatch" },
525
+ ];
526
+ raw.currentPhase = "implement";
527
+ writeFileSync(stateFile, JSON.stringify(raw, null, 2));
528
+ // No behavior-spec.json on disk — but legacy issue should pass
529
+ // We test by attempting to re-enter implement from dispatch (artificial),
530
+ // expecting NO behavior-spec error.
531
+ // For this test we directly assert the cutoff check helper:
532
+ expect(SPECIFY_PHASE_CUTOFF).toBe("2026-05-14T00:00:00Z");
533
+ rmSync(root, { recursive: true, force: true });
534
+ });
535
+ });
536
+ ```
537
+
538
+ (Note: a more direct test would require exposing the helper; for MVP the constant assertion plus the manual exemption logic in step 3 is sufficient. A deeper integration test is in Task 14.)
539
+
540
+ - [ ] **Step 3: Add the exemption check in assertArtifactGate**
541
+
542
+ Edit `core/state.ts` — in the `assertArtifactGate` function (modified in Task 4), wrap the `behavior-spec.confirmedAt` check so it ALSO checks the legacy cutoff:
543
+
544
+ ```ts
545
+ if (existsSync(requiredArtifactPath)) {
546
+ if (requiredArtifact === "behavior-spec" && input.nextPhase === "implement") {
547
+ // Legacy exemption: if this issue entered implement before cutoff, skip.
548
+ const state = readIssueState({ root: undefined, issueId: input.issueId });
549
+ const legacyEntry = state.phaseHistory?.find((h) => h.phase === "implement");
550
+ if (legacyEntry && legacyEntry.enteredAt < SPECIFY_PHASE_CUTOFF) {
551
+ // skip confirmedAt check
552
+ } else {
553
+ const raw = JSON.parse(readFileSync(requiredArtifactPath, "utf8"));
554
+ const confirmedAt = raw?.payload?.confirmedAt;
555
+ if (!confirmedAt) {
556
+ // ... (same as Task 4)
557
+ throw new Error(
558
+ `behavior-spec exists but confirmedAt is null; run \`gxpm specify confirm ${input.issueId}\` to confirm`,
559
+ );
560
+ }
561
+ }
562
+ }
563
+ // ... emit gate.passed event (same as Task 4)
564
+ return;
565
+ }
566
+
567
+ // Also skip the missing-artifact branch for legacy issues:
568
+ if (requiredArtifact === "behavior-spec") {
569
+ const state = readIssueState({ root: undefined, issueId: input.issueId });
570
+ const legacyEntry = state.phaseHistory?.find((h) => h.phase === "implement");
571
+ if (legacyEntry && legacyEntry.enteredAt < SPECIFY_PHASE_CUTOFF) {
572
+ return; // legacy bypass
573
+ }
574
+ }
575
+ ```
576
+
577
+ Place the second block BEFORE the `throw new Error("Missing required artifact: ...")` line so legacy issues exit the function cleanly.
578
+
579
+ - [ ] **Step 4: Run tests to verify**
580
+
581
+ Run: `bun test test/core/phase-gates.specify.test.ts`
582
+ Expected: PASS — legacy compatibility test + all earlier tests.
583
+
584
+ - [ ] **Step 5: Commit**
585
+
586
+ ```bash
587
+ git add core/state.ts test/core/phase-gates.specify.test.ts
588
+ git commit -m "GXPM-XXX exempt pre-cutoff issues from specify gate
589
+
590
+ Issues whose phaseHistory shows implement entered before
591
+ SPECIFY_PHASE_CUTOFF (2026-05-14T00:00:00Z) bypass the new
592
+ specify gate, so legacy in-flight work is not blocked.
593
+
594
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
595
+ ```
596
+
597
+ ---
598
+
599
+ ## Task 5: 实现 core/specify.ts — initializeSpecify
600
+
601
+ **Files:**
602
+ - Create: `core/specify.ts`
603
+ - Test: `test/core/specify.test.ts` (append)
604
+
605
+ - [ ] **Step 1: Write the failing test**
606
+
607
+ Append to `test/core/specify.test.ts`:
608
+
609
+ ```ts
610
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
611
+ import { tmpdir } from "node:os";
612
+ import { join } from "node:path";
613
+ import { createIssueState } from "../../core/state";
614
+ import { initializeSpecify } from "../../core/specify";
615
+
616
+ describe("initializeSpecify", () => {
617
+ it("writes a draft behavior-spec.json with confirmedAt=null", () => {
618
+ const root = mkdtempSync(join(tmpdir(), "gxpm-init-specify-"));
619
+ createIssueState({ root, issueId: "G-3", issueType: "feature" });
620
+ // Force phase to specify
621
+ const stateFile = join(root, ".gxpm", "issues", "G-3", "state.json");
622
+ const raw = JSON.parse(readFileSync(stateFile, "utf8"));
623
+ raw.currentPhase = "specify";
624
+ writeFileSync(stateFile, JSON.stringify(raw, null, 2));
625
+
626
+ const record = initializeSpecify({ root, issueId: "G-3" });
627
+ expect(record.type).toBe("behavior-spec");
628
+
629
+ const artPath = join(root, ".gxpm", "issues", "G-3", "artifacts", "behavior-spec.json");
630
+ const written = JSON.parse(readFileSync(artPath, "utf8"));
631
+ expect(written.payload.$schema).toBe("behavior-spec.v1");
632
+ expect(written.payload.confirmedAt).toBeNull();
633
+ expect(written.payload.confirmedBy).toBeNull();
634
+ expect(written.payload.scenarios).toHaveLength(1);
635
+ expect(written.payload.scenarios[0].id).toBe("scn-01");
636
+ rmSync(root, { recursive: true, force: true });
637
+ });
638
+
639
+ it("throws when not in specify phase", () => {
640
+ const root = mkdtempSync(join(tmpdir(), "gxpm-init-specify-wrong-"));
641
+ createIssueState({ root, issueId: "G-4", issueType: "feature" });
642
+ expect(() => initializeSpecify({ root, issueId: "G-4" })).toThrow(/specify phase/);
643
+ rmSync(root, { recursive: true, force: true });
644
+ });
645
+ });
646
+ ```
647
+
648
+ - [ ] **Step 2: Run test to verify it fails**
649
+
650
+ Run: `bun test test/core/specify.test.ts`
651
+ Expected: FAIL — `Cannot find module '../../core/specify'`.
652
+
653
+ - [ ] **Step 3: Create core/specify.ts (initialize only)**
654
+
655
+ Create `core/specify.ts`:
656
+
657
+ ```ts
658
+ import { writeArtifact } from "./artifacts";
659
+ import { readIssueState } from "./state";
660
+ import { resolveAgentIdentity } from "./session";
661
+
662
+ interface SpecifyInput {
663
+ root?: string;
664
+ issueId: string;
665
+ }
666
+
667
+ export function initializeSpecify(input: SpecifyInput) {
668
+ const state = readIssueState({ root: input.root, issueId: input.issueId });
669
+ if (state.currentPhase !== "specify") {
670
+ throw new Error(
671
+ `Specify can only be initialized from specify phase: current phase is ${state.currentPhase}`,
672
+ );
673
+ }
674
+
675
+ const now = new Date().toISOString();
676
+ const identity = resolveAgentIdentity();
677
+
678
+ const payload = {
679
+ $schema: "behavior-spec.v1",
680
+ issueId: input.issueId,
681
+ createdAt: now,
682
+ createdBy: identity,
683
+ confirmedAt: null,
684
+ confirmedBy: null,
685
+ feature: {
686
+ title: "",
687
+ asA: "",
688
+ iWant: "",
689
+ soThat: "",
690
+ },
691
+ scenarios: [
692
+ {
693
+ id: "scn-01",
694
+ name: "",
695
+ given: [""],
696
+ when: "",
697
+ then: [""],
698
+ examples: [],
699
+ stubPath: "",
700
+ },
701
+ ],
702
+ guidelinesRef: "docs/governance/gherkin-style.md@v1",
703
+ };
704
+
705
+ return writeArtifact({
706
+ root: input.root,
707
+ issueId: input.issueId,
708
+ type: "behavior-spec",
709
+ payload,
710
+ });
711
+ }
712
+ ```
713
+
714
+ - [ ] **Step 4: Run test to verify it passes**
715
+
716
+ Run: `bun test test/core/specify.test.ts`
717
+ Expected: PASS (3 schema tests + 2 init tests = 5 tests pass).
718
+
719
+ - [ ] **Step 5: Commit**
720
+
721
+ ```bash
722
+ git add core/specify.ts test/core/specify.test.ts
723
+ git commit -m "GXPM-XXX implement initializeSpecify to draft behavior-spec
724
+
725
+ Mirrors core/plan.ts shape: writes a draft behavior-spec.json
726
+ with one empty scenario placeholder; rejects if issue not in
727
+ specify phase.
728
+
729
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
730
+ ```
731
+
732
+ ---
733
+
734
+ ## Task 6: 实现 confirmSpecify / reviseSpecify
735
+
736
+ **Files:**
737
+ - Modify: `core/specify.ts` (append functions)
738
+ - Test: `test/core/specify.test.ts` (append)
739
+
740
+ - [ ] **Step 1: Write failing tests for confirm and revise**
741
+
742
+ Append to `test/core/specify.test.ts`:
743
+
744
+ ```ts
745
+ import { confirmSpecify, reviseSpecify } from "../../core/specify";
746
+ import { BehaviorSpecSchema } from "../../core/contracts/behavior-spec.schema";
747
+
748
+ function seedValidSpec(root: string, issueId: string, stubFile: string) {
749
+ const artDir = join(root, ".gxpm", "issues", issueId, "artifacts");
750
+ mkdirSync(artDir, { recursive: true });
751
+ writeFileSync(
752
+ join(artDir, "behavior-spec.json"),
753
+ JSON.stringify({
754
+ schemaVersion: 1,
755
+ issueId,
756
+ type: "behavior-spec",
757
+ writtenAt: "2026-05-14T00:00:00Z",
758
+ payload: {
759
+ $schema: "behavior-spec.v1",
760
+ issueId,
761
+ createdAt: "2026-05-14T00:00:00Z",
762
+ createdBy: "specifier",
763
+ confirmedAt: null,
764
+ confirmedBy: null,
765
+ feature: { title: "T", asA: "a", iWant: "b", soThat: "c" },
766
+ scenarios: [
767
+ {
768
+ id: "scn-01",
769
+ name: "happy",
770
+ given: ["g"],
771
+ when: "w",
772
+ then: ["t"],
773
+ examples: [],
774
+ stubPath: stubFile,
775
+ },
776
+ ],
777
+ guidelinesRef: "docs/governance/gherkin-style.md@v1",
778
+ },
779
+ }),
780
+ );
781
+ }
782
+
783
+ describe("confirmSpecify", () => {
784
+ it("writes confirmedAt and confirmedBy when valid", () => {
785
+ const root = mkdtempSync(join(tmpdir(), "gxpm-confirm-"));
786
+ createIssueState({ root, issueId: "G-5", issueType: "feature" });
787
+ // Force phase to specify
788
+ const stateFile = join(root, ".gxpm", "issues", "G-5", "state.json");
789
+ const raw = JSON.parse(readFileSync(stateFile, "utf8"));
790
+ raw.currentPhase = "specify";
791
+ writeFileSync(stateFile, JSON.stringify(raw, null, 2));
792
+ // Create stub file referenced by scenario
793
+ const stubPath = join(root, "test/foo.test.ts");
794
+ mkdirSync(join(root, "test"), { recursive: true });
795
+ writeFileSync(stubPath, "// stub");
796
+ seedValidSpec(root, "G-5", "test/foo.test.ts");
797
+
798
+ confirmSpecify({ root, issueId: "G-5", confirmedBy: "alice@example.com" });
799
+
800
+ const artPath = join(root, ".gxpm", "issues", "G-5", "artifacts", "behavior-spec.json");
801
+ const after = JSON.parse(readFileSync(artPath, "utf8"));
802
+ expect(after.payload.confirmedAt).not.toBeNull();
803
+ expect(after.payload.confirmedBy).toBe("alice@example.com");
804
+ rmSync(root, { recursive: true, force: true });
805
+ });
806
+
807
+ it("rejects confirm when stubPath does not exist", () => {
808
+ const root = mkdtempSync(join(tmpdir(), "gxpm-confirm-missing-"));
809
+ createIssueState({ root, issueId: "G-6", issueType: "feature" });
810
+ seedValidSpec(root, "G-6", "test/does-not-exist.test.ts");
811
+ expect(() =>
812
+ confirmSpecify({ root, issueId: "G-6", confirmedBy: "alice@example.com" }),
813
+ ).toThrow(/Stub file missing/);
814
+ rmSync(root, { recursive: true, force: true });
815
+ });
816
+ });
817
+
818
+ describe("reviseSpecify", () => {
819
+ it("clears confirmedAt and confirmedBy", () => {
820
+ const root = mkdtempSync(join(tmpdir(), "gxpm-revise-"));
821
+ createIssueState({ root, issueId: "G-7", issueType: "feature" });
822
+ const stubPath = join(root, "test/foo.test.ts");
823
+ mkdirSync(join(root, "test"), { recursive: true });
824
+ writeFileSync(stubPath, "// stub");
825
+ seedValidSpec(root, "G-7", "test/foo.test.ts");
826
+ // Manually set confirmedAt
827
+ const artPath = join(root, ".gxpm", "issues", "G-7", "artifacts", "behavior-spec.json");
828
+ const before = JSON.parse(readFileSync(artPath, "utf8"));
829
+ before.payload.confirmedAt = "2026-05-14T01:00:00Z";
830
+ before.payload.confirmedBy = "alice@example.com";
831
+ writeFileSync(artPath, JSON.stringify(before));
832
+
833
+ reviseSpecify({ root, issueId: "G-7" });
834
+
835
+ const after = JSON.parse(readFileSync(artPath, "utf8"));
836
+ expect(after.payload.confirmedAt).toBeNull();
837
+ expect(after.payload.confirmedBy).toBeNull();
838
+ rmSync(root, { recursive: true, force: true });
839
+ });
840
+ });
841
+ ```
842
+
843
+ - [ ] **Step 2: Run test to verify it fails**
844
+
845
+ Run: `bun test test/core/specify.test.ts`
846
+ Expected: FAIL — `confirmSpecify` and `reviseSpecify` not exported.
847
+
848
+ - [ ] **Step 3: Implement confirmSpecify and reviseSpecify**
849
+
850
+ Append to `core/specify.ts`:
851
+
852
+ ```ts
853
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
854
+ import { join } from "node:path";
855
+ import { execSync } from "node:child_process";
856
+ import { BehaviorSpecSchema, type BehaviorSpec } from "./contracts/behavior-spec.schema";
857
+
858
+ interface ConfirmInput {
859
+ root?: string;
860
+ issueId: string;
861
+ confirmedBy?: string;
862
+ }
863
+
864
+ function gitUserEmail(): string {
865
+ try {
866
+ return execSync("git config user.email", { encoding: "utf8" }).trim();
867
+ } catch {
868
+ return "unknown@local";
869
+ }
870
+ }
871
+
872
+ function specPath(root: string, issueId: string): string {
873
+ return join(root, ".gxpm", "issues", issueId, "artifacts", "behavior-spec.json");
874
+ }
875
+
876
+ function readSpec(root: string, issueId: string): { stored: any; spec: BehaviorSpec } {
877
+ const path = specPath(root, issueId);
878
+ if (!existsSync(path)) {
879
+ throw new Error(`behavior-spec.json not found for ${issueId}; run \`gxpm specify init ${issueId}\` first`);
880
+ }
881
+ const stored = JSON.parse(readFileSync(path, "utf8"));
882
+ const spec = BehaviorSpecSchema.parse(stored.payload);
883
+ return { stored, spec };
884
+ }
885
+
886
+ export function confirmSpecify(input: ConfirmInput) {
887
+ const root = input.root ?? process.cwd();
888
+ const { stored, spec } = readSpec(root, input.issueId);
889
+
890
+ for (const scn of spec.scenarios) {
891
+ const [file] = scn.stubPath.split(":");
892
+ const abs = join(root, file);
893
+ if (!existsSync(abs)) {
894
+ throw new Error(`Stub file missing: ${scn.stubPath} (scenario ${scn.id})`);
895
+ }
896
+ }
897
+
898
+ const now = new Date().toISOString();
899
+ const confirmedBy = input.confirmedBy ?? gitUserEmail();
900
+ stored.payload.confirmedAt = now;
901
+ stored.payload.confirmedBy = confirmedBy;
902
+ writeFileSync(specPath(root, input.issueId), `${JSON.stringify(stored, null, 2)}\n`);
903
+ }
904
+
905
+ export function reviseSpecify(input: { root?: string; issueId: string }) {
906
+ const root = input.root ?? process.cwd();
907
+ const { stored } = readSpec(root, input.issueId);
908
+ stored.payload.confirmedAt = null;
909
+ stored.payload.confirmedBy = null;
910
+ writeFileSync(specPath(root, input.issueId), `${JSON.stringify(stored, null, 2)}\n`);
911
+ }
912
+ ```
913
+
914
+ - [ ] **Step 4: Run test to verify it passes**
915
+
916
+ Run: `bun test test/core/specify.test.ts`
917
+ Expected: PASS — all 8 tests pass.
918
+
919
+ - [ ] **Step 5: Commit**
920
+
921
+ ```bash
922
+ git add core/specify.ts test/core/specify.test.ts
923
+ git commit -m "GXPM-XXX add confirmSpecify and reviseSpecify
924
+
925
+ confirmSpecify validates schema, checks every scenario.stubPath
926
+ file exists, then writes confirmedAt/confirmedBy. reviseSpecify
927
+ clears both fields. Both use BehaviorSpecSchema for shape safety.
928
+
929
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
930
+ ```
931
+
932
+ ---
933
+
934
+ ## Task 7: 注册 specify init 到 PHASE_ARTIFACT_HANDLERS
935
+
936
+ **Files:**
937
+ - Modify: `scripts/phase-artifact-commands.ts:21-72`
938
+
939
+ - [ ] **Step 1: Edit PHASE_ARTIFACT_HANDLERS**
940
+
941
+ Edit `scripts/phase-artifact-commands.ts` — at the top, add import:
942
+
943
+ ```ts
944
+ import { initializeSpecify } from "../core/specify";
945
+ ```
946
+
947
+ In `PHASE_ARTIFACT_HANDLERS`, add entry between `"dispatch-handoff"` and `"local-verify"`:
948
+
949
+ ```ts
950
+ "behavior-spec": {
951
+ initialize: initializeSpecify,
952
+ successMessage: (issueId) => `initialized behavior-spec artifact for ${issueId}`,
953
+ },
954
+ ```
955
+
956
+ - [ ] **Step 2: Verify command discovery**
957
+
958
+ Run:
959
+ ```bash
960
+ bun run scripts/gxpm.ts specify init G-001 2>&1 | head -5
961
+ ```
962
+
963
+ Expected: Either "initialized behavior-spec artifact" success message (if there is an issue G-001 in specify phase) or a meaningful error like "Issue state not found: G-001". NOT "Unknown command".
964
+
965
+ - [ ] **Step 3: Commit**
966
+
967
+ ```bash
968
+ git add scripts/phase-artifact-commands.ts
969
+ git commit -m "GXPM-XXX register behavior-spec init handler
970
+
971
+ Hooks initializeSpecify into the phase-artifact framework so
972
+ that 'gxpm specify init <issue-id>' is auto-discovered via
973
+ PHASE_GATE_RULES like all other phase init commands.
974
+
975
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
976
+ ```
977
+
978
+ ---
979
+
980
+ ## Task 8: 实现 scripts/commands/specify.ts CLI 子命令
981
+
982
+ **Files:**
983
+ - Create: `scripts/commands/specify.ts`
984
+ - Modify: `scripts/gxpm.ts` (add routing branch)
985
+
986
+ - [ ] **Step 1: Create scripts/commands/specify.ts**
987
+
988
+ ```ts
989
+ import { readFileSync, existsSync } from "node:fs";
990
+ import { join } from "node:path";
991
+ import { confirmSpecify, reviseSpecify } from "../../core/specify";
992
+
993
+ function specPath(issueId: string): string {
994
+ return join(process.cwd(), ".gxpm", "issues", issueId, "artifacts", "behavior-spec.json");
995
+ }
996
+
997
+ function runConfirm(issueId: string) {
998
+ if (!issueId) {
999
+ throw new Error("usage: gxpm specify confirm <issue-id>");
1000
+ }
1001
+ confirmSpecify({ issueId });
1002
+ console.log(`confirmed behavior-spec for ${issueId}`);
1003
+ }
1004
+
1005
+ function runRevise(issueId: string) {
1006
+ if (!issueId) {
1007
+ throw new Error("usage: gxpm specify revise <issue-id>");
1008
+ }
1009
+ reviseSpecify({ issueId });
1010
+ console.log(`revised behavior-spec for ${issueId} (confirmedAt cleared)`);
1011
+ }
1012
+
1013
+ function runShow(issueId: string) {
1014
+ if (!issueId) {
1015
+ throw new Error("usage: gxpm specify show <issue-id>");
1016
+ }
1017
+ const path = specPath(issueId);
1018
+ if (!existsSync(path)) {
1019
+ throw new Error(`behavior-spec.json not found for ${issueId}`);
1020
+ }
1021
+ const stored = JSON.parse(readFileSync(path, "utf8"));
1022
+ const spec = stored.payload;
1023
+ console.log(`Feature: ${spec.feature.title}`);
1024
+ console.log(` As a ${spec.feature.asA}`);
1025
+ console.log(` I want ${spec.feature.iWant}`);
1026
+ console.log(` So that ${spec.feature.soThat}`);
1027
+ console.log("");
1028
+ for (const scn of spec.scenarios) {
1029
+ console.log(`Scenario (${scn.id}): ${scn.name}`);
1030
+ for (const g of scn.given) console.log(` Given ${g}`);
1031
+ console.log(` When ${scn.when}`);
1032
+ for (const t of scn.then) console.log(` Then ${t}`);
1033
+ console.log(` Stub: ${scn.stubPath}`);
1034
+ console.log("");
1035
+ }
1036
+ console.log(`confirmedAt: ${spec.confirmedAt ?? "(not confirmed)"}`);
1037
+ }
1038
+
1039
+ export function runSpecifyCommand(_argv: string[], subcommand: string | undefined, issueId: string | undefined) {
1040
+ switch (subcommand) {
1041
+ case "confirm":
1042
+ runConfirm(issueId ?? "");
1043
+ return;
1044
+ case "revise":
1045
+ runRevise(issueId ?? "");
1046
+ return;
1047
+ case "show":
1048
+ runShow(issueId ?? "");
1049
+ return;
1050
+ default:
1051
+ throw new Error(`unknown specify subcommand: ${subcommand ?? "<none>"}; expected confirm|revise|show`);
1052
+ }
1053
+ }
1054
+ ```
1055
+
1056
+ - [ ] **Step 2: Add routing in scripts/gxpm.ts**
1057
+
1058
+ Edit `scripts/gxpm.ts` — after the existing import block, add:
1059
+
1060
+ ```ts
1061
+ import { runSpecifyCommand } from "./commands/specify";
1062
+ ```
1063
+
1064
+ In the command routing chain (after similar `if (command === "...")` blocks, before the phase-artifact lookup at line 180), add:
1065
+
1066
+ ```ts
1067
+ if (command === "specify" && subcommand !== "init") {
1068
+ runSpecifyCommand(argv, subcommand, issueId);
1069
+ return;
1070
+ }
1071
+ ```
1072
+
1073
+ Note: `specify init` is handled by the auto-discovered phase-artifact framework (Task 7); only confirm/revise/show go through this routing.
1074
+
1075
+ - [ ] **Step 3: Smoke test the CLI**
1076
+
1077
+ Create a temporary issue and test the path:
1078
+
1079
+ ```bash
1080
+ # Setup
1081
+ TMP=$(mktemp -d)
1082
+ cd "$TMP"
1083
+ bun run /Users/x/Desktop/Project/gxpm/scripts/gxpm.ts init
1084
+ # (Continue setup using gxpm CLI to reach specify phase, then:)
1085
+ # bun run scripts/gxpm.ts specify confirm G-001
1086
+ ```
1087
+
1088
+ Expected: Either success message or a meaningful schema/stub error. NOT "Unknown command".
1089
+
1090
+ - [ ] **Step 4: Commit**
1091
+
1092
+ ```bash
1093
+ git add scripts/commands/specify.ts scripts/gxpm.ts
1094
+ git commit -m "GXPM-XXX add gxpm specify confirm/revise/show CLI
1095
+
1096
+ Wires the three subcommands through scripts/commands/specify.ts.
1097
+ The 'init' subcommand is intentionally left to the phase-artifact
1098
+ auto-discovery framework (see Task 7).
1099
+
1100
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1101
+ ```
1102
+
1103
+ ---
1104
+
1105
+ ## Task 9: 创建 Gherkin 风格治理文档
1106
+
1107
+ **Files:**
1108
+ - Create: `docs/governance/gherkin-style.md`
1109
+
1110
+ - [ ] **Step 1: Write the governance document**
1111
+
1112
+ Create `docs/governance/gherkin-style.md`:
1113
+
1114
+ ```markdown
1115
+ # Gherkin 写作风格指南 (v1)
1116
+
1117
+ > 本文档源自 AutomationPanda/gherkin-guidelines-for-ai (MIT),加 gxpm 本地补充。
1118
+ > 所有 gxpm-driven issue 在 specify 阶段必须遵循本规则集。
1119
+
1120
+ ## 核心原则
1121
+
1122
+ - **行为驱动**:描述系统*做什么*而非*怎么做*
1123
+ - **领域语言**:使用 CONTEXT.md 中的术语,不出现 UI/HTTP/SQL 等技术词
1124
+ - **示例规约**:scenario 用具体例子展示行为
1125
+ - **每个 scenario 一个行为**
1126
+ - **可独立执行**:scenario 之间无顺序依赖
1127
+
1128
+ ## Feature 结构
1129
+
1130
+ - 单一 `Feature:` 标题与文件名一致
1131
+ - User Story 紧跟标题:
1132
+ - `As a <role>`
1133
+ - `I want <goal>`
1134
+ - `So that <reason>`
1135
+
1136
+ ## Scenario 设计规则
1137
+
1138
+ - 单行、行为聚焦的标题
1139
+ - 步骤可按时间顺序执行
1140
+ - 声明式语言,非命令式
1141
+ - 不混合多个无关关注点(功能 + 性能 + 可访问性必须拆分)
1142
+ - 步骤数 < 10,超过用 data table
1143
+
1144
+ ## Given / When / Then 语义
1145
+
1146
+ - `Given` 建立上下文(Arrange)
1147
+ - `When` 触发动作(Act)
1148
+ - `Then` 验证可观察结果(Assert)
1149
+
1150
+ **严格顺序**:`Given → When → Then`,禁止重复阶段。
1151
+ **关键词**:`And` 续相同类型(OK);`But` 用于对比(少用);**禁止 `Or`**。
1152
+ **可观察结果**:`Then` 必须可从场景文本验证——禁止 "it works"、"it succeeds"。
1153
+
1154
+ ## 词汇与命名
1155
+
1156
+ - 整个 issue 内使用稳定词汇,禁止同义词替换
1157
+ - 第三人称、现在时、主谓结构
1158
+ - 字符串参数用双引号
1159
+ - 步骤数据用 doc string (`"""`) 或 data table,禁止用 `And` 串联
1160
+
1161
+ ## 反模式(禁止)
1162
+
1163
+ - ❌ 在 Given/When/Then 写入 UI 选择器、XPath、`click #id`
1164
+ - ❌ 在 Then 写 SQL 断言、HTTP 状态码(除非这就是被测的接口)
1165
+ - ❌ 占位符数据:`foo`、`bar`、`test`、`123`(数字若有业务含义可用)
1166
+ - ❌ 一个 scenario 多个行为
1167
+ - ❌ 步骤超过 10 个
1168
+ - ❌ "用户登录" 描述成 10 步点击;改用状态:"用户已以 Editor 身份登录"
1169
+
1170
+ ## gxpm 本地补充
1171
+
1172
+ - **中文允许**:领域词允许中文,但同一 issue 内保持中英一致
1173
+ - **必须引用 CONTEXT.md 术语**:scenario 中提及的实体名必须在 CONTEXT.md 中有定义
1174
+ - **stubPath 必须真实**:每个 scenario 的 `stubPath` 字段指向的测试文件必须存在
1175
+ - **scenario.id 命名**:`scn-NN`(两位数字补零)
1176
+
1177
+ ## Pre-confirm 自查清单
1178
+
1179
+ specifier agent 在调用 `gxpm specify confirm` 前必须自查:
1180
+
1181
+ - [ ] 单一行为,可独立执行
1182
+ - [ ] 无混合无关关注点
1183
+ - [ ] 词汇稳定,引用 CONTEXT.md 术语
1184
+ - [ ] 领域级抽象,无 UI/HTTP/SQL 管道术语
1185
+ - [ ] 最小但充分的 Given
1186
+ - [ ] 真实示例数据(无 foo/bar/test)
1187
+ - [ ] 第三人称、现在时、主谓结构
1188
+ - [ ] 严格 Given → When → Then,Then 可观察
1189
+ - [ ] 步骤数 < 10
1190
+ - [ ] 每个 scenario.stubPath 真实存在
1191
+
1192
+ ## 参考
1193
+
1194
+ - AutomationPanda/gherkin-guidelines-for-ai (https://github.com/AutomationPanda/gherkin-guidelines-for-ai)
1195
+ - gxpm CONTEXT.md(领域词典)
1196
+ ```
1197
+
1198
+ - [ ] **Step 2: Commit**
1199
+
1200
+ ```bash
1201
+ git add docs/governance/gherkin-style.md
1202
+ git commit -m "GXPM-XXX add Gherkin style governance for specify phase
1203
+
1204
+ Codifies AutomationPanda's open-source Gherkin guidelines plus
1205
+ gxpm-local supplements (Chinese OK, must reference CONTEXT.md,
1206
+ scn-NN ID format, stubPath must be real). The pre-confirm
1207
+ self-check list is the specifier agent's gate before calling
1208
+ 'gxpm specify confirm'.
1209
+
1210
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1211
+ ```
1212
+
1213
+ ---
1214
+
1215
+ ## Task 10: 创建 specifier agent 模板
1216
+
1217
+ **Files:**
1218
+ - Create: `agents/specifier.md`
1219
+
1220
+ - [ ] **Step 1: Write the agent template**
1221
+
1222
+ Create `agents/specifier.md`:
1223
+
1224
+ ```markdown
1225
+ # Specifier Agent
1226
+
1227
+ ## Role
1228
+
1229
+ Specifier 是 gxpm `specify` 阶段的唯一 owner。其职责是接收 dispatch-handoff,产出可被用户确认的 Gherkin 行为规约(behavior-spec artifact)。
1230
+
1231
+ **Specifier 不写实现代码,不写测试逻辑代码。仅产出行为注释 + 空测试 stub。**
1232
+
1233
+ ## Inputs
1234
+
1235
+ - `.gxpm/issues/<id>/artifacts/acceptance-contract.json`(来自 triage)
1236
+ - `.gxpm/issues/<id>/artifacts/implementation-plan.json`(来自 plan)
1237
+ - `.gxpm/issues/<id>/artifacts/dispatch-handoff.json`(来自 dispatch)
1238
+ - `docs/governance/gherkin-style.md`(必读)
1239
+ - `CONTEXT.md`(领域词典)
1240
+ - `test/**` 下既有测试文件(few-shot 范本)
1241
+
1242
+ ## Outputs
1243
+
1244
+ - `.gxpm/issues/<id>/artifacts/behavior-spec.json`(结构化 Gherkin 规约,confirmedAt=null)
1245
+ - `test/**/<area>/<name>.test.ts`(空 stub 文件,每个 scenario 一个空测试函数 + Gherkin 注释)
1246
+
1247
+ ## Operating Procedure
1248
+
1249
+ 1. **加载 skill**:`skills/gxpm-specifier/SKILL.md`
1250
+ 2. **读取上游 artifact**:理解需求范围
1251
+ 3. **加载 Gherkin 规则**:`docs/governance/gherkin-style.md`
1252
+ 4. **查询 few-shot 范本**:在 test/ 下选择 1-2 个既有测试做为风格参考
1253
+ 5. **草拟行为规约**:每个用户故事 → 1 Feature + N Scenarios(N≥1)
1254
+ 6. **生成 stub 文件**:为每个 scenario 产出空测试函数 + Gherkin 注释
1255
+ 7. **运行 `gxpm specify init <id>`**:写入 behavior-spec.json
1256
+ 8. **向用户呈现**:调用 AskUserQuestion 工具(若 host 支持)或终端输出场景摘要
1257
+ 9. **根据反馈迭代**:调整后重新生成 stub 文件(保持 scenario.id 稳定)
1258
+ 10. **用户确认后**:运行 `gxpm specify confirm <id>`
1259
+
1260
+ ## Hard Rules(不可违反)
1261
+
1262
+ - **禁止** 在 specify 阶段写任何测试逻辑代码(函数体必须为空 / pass)
1263
+ - **禁止** 在用户 confirm 之前推进到 implement 阶段
1264
+ - **禁止** 跳过 `docs/governance/gherkin-style.md` 自查清单
1265
+ - **禁止** scenario 步骤超过 10 个;超过必须拆分 scenario
1266
+ - **禁止** 使用 foo/bar/test 等占位符数据
1267
+
1268
+ ## Handoff to Implementer
1269
+
1270
+ confirmedAt 写入后,implementer agent 接管。implementer 从 behavior-spec.json 读取 scenario,按 RED→GREEN→REFACTOR 在每个 stub 文件中实现测试逻辑与产品代码。
1271
+
1272
+ ## 相关文档
1273
+
1274
+ - `skills/gxpm-specifier/SKILL.md`
1275
+ - `skills/gxpm-tdd/SKILL.md`(下游)
1276
+ - `docs/governance/gherkin-style.md`
1277
+ - `docs/brainstorms/2026-05-14-bdd-then-tdd-design.md`
1278
+ ```
1279
+
1280
+ - [ ] **Step 2: Commit**
1281
+
1282
+ ```bash
1283
+ git add agents/specifier.md
1284
+ git commit -m "GXPM-XXX add specifier agent template
1285
+
1286
+ Defines the specify-phase owner: inputs, outputs, operating
1287
+ procedure, hard rules, and the handoff contract to implementer.
1288
+ The hard rules forbid writing any test logic during specify
1289
+ and ban placeholders.
1290
+
1291
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1292
+ ```
1293
+
1294
+ ---
1295
+
1296
+ ## Task 11: 创建 gxpm-specifier skill
1297
+
1298
+ **Files:**
1299
+ - Create: `skills/gxpm-specifier/SKILL.md`
1300
+ - Modify: `skills-lock.json` (via maintain-hygiene-skills-lock skill)
1301
+
1302
+ - [ ] **Step 1: Write the skill**
1303
+
1304
+ Create `skills/gxpm-specifier/SKILL.md`:
1305
+
1306
+ ```markdown
1307
+ ---
1308
+ name: gxpm-specifier
1309
+ description: BDD 行为规约设计 skill。在 gxpm specify 阶段使用,强制先产出 Gherkin 行为注释 + 空测试 stub,由用户确认后才能进入 TDD。触发场景:用户在 specify 阶段、用户提到 BDD、Gherkin、Given-When-Then、行为规约、行为先行。
1310
+ ---
1311
+
1312
+ # gxpm-specifier
1313
+
1314
+ ## Core Principle
1315
+
1316
+ **Specify is BDD. Implement is TDD. The two must be separated by a user confirmation.**
1317
+
1318
+ 在 specify 阶段,**不写一行测试逻辑代码**。产出的仅是 Gherkin 行为注释 + 空函数 stub + structured artifact。
1319
+
1320
+ ## 入口条件
1321
+
1322
+ - gxpm issue 处于 `specify` phase
1323
+ - 用户要求"先写行为再写代码"、"BDD 先行"、"Given-When-Then"
1324
+ - dispatch-handoff.json 已存在
1325
+
1326
+ ## Hard Rules
1327
+
1328
+ ```
1329
+ NO TEST LOGIC IN SPECIFY PHASE
1330
+ NO IMPLEMENTATION CODE IN SPECIFY PHASE
1331
+ NO PLACEHOLDER DATA (foo/bar/test/123)
1332
+ NO SCENARIO WITH > 10 STEPS
1333
+ NO MIXED CONCERNS IN ONE SCENARIO
1334
+ ```
1335
+
1336
+ 违反任一条 = 删除产出,从 `gxpm specify init` 重新开始。
1337
+
1338
+ ## 可操作流程
1339
+
1340
+ 1. 读取上游 artifact:acceptance-contract、implementation-plan、dispatch-handoff
1341
+ 2. 读取治理文档:`docs/governance/gherkin-style.md`
1342
+ 3. 在 `test/` 下找 1-2 个既有测试文件作为风格参照
1343
+ 4. 草拟 Feature + Scenarios(每 scenario `given`/`when`/`then` 各 ≥1 项)
1344
+ 5. 为每个 scenario 生成空 stub:
1345
+ ```ts
1346
+ // Feature: <title>
1347
+ //
1348
+ // Scenario (scn-01): <name>
1349
+ // Given <given[0]>
1350
+ // And <given[1]>
1351
+ // When <when>
1352
+ // Then <then[0]>
1353
+ // And <then[1]>
1354
+
1355
+ test("test_<scenario_name_in_snake_case>", () => {
1356
+ // intentionally empty — awaiting user confirmation
1357
+ });
1358
+ ```
1359
+ 6. 运行 `gxpm specify init <issue-id>` 写入 behavior-spec.json
1360
+ 7. 编辑 behavior-spec.json,填充 feature/scenarios/stubPath 真实值(`gxpm specify edit` 或直接编辑)
1361
+ 8. 调用 AskUserQuestion 呈现三选项:
1362
+ - 行为正确,继续
1363
+ - 需要调整:用户反馈 → 回到步骤 4
1364
+ - 补充边界场景:增加 scenario → 回到步骤 4
1365
+ 9. 用户确认后运行 `gxpm specify confirm <issue-id>`
1366
+
1367
+ ## 红旗清单
1368
+
1369
+ 立即停止并重新开始:
1370
+
1371
+ - 在 specify 阶段写了 expect/assert 语句
1372
+ - 用 foo/bar/test 等占位符
1373
+ - scenario 步骤 > 10
1374
+ - 一个 scenario 同时测功能 + 性能
1375
+ - 在 Then 写 UI 选择器、HTTP 状态码(除非接口本身被测)
1376
+ - 跳过用户确认直接 `gxpm specify confirm`
1377
+ - 跳过 specify 直接 implement(phase-gate 会拒绝)
1378
+
1379
+ ## 验证清单
1380
+
1381
+ 每次 specify confirm 前自查:
1382
+
1383
+ - [ ] 单一行为,可独立执行
1384
+ - [ ] 无混合关注点
1385
+ - [ ] 词汇稳定,CONTEXT.md 术语对齐
1386
+ - [ ] 领域级抽象,无 UI/HTTP/SQL 管道术语
1387
+ - [ ] 最小但充分的 Given
1388
+ - [ ] 真实示例数据
1389
+ - [ ] 第三人称、现在时、主谓结构
1390
+ - [ ] 严格 Given→When→Then,Then 可观察
1391
+ - [ ] 步骤数 < 10
1392
+ - [ ] 每个 scenario.stubPath 真实存在
1393
+ - [ ] 用户已通过 AskUserQuestion 或终端确认
1394
+
1395
+ ## Handoff
1396
+
1397
+ confirmedAt 写入 → phase 可转 implement → gxpm-tdd skill 接管。
1398
+ ```
1399
+
1400
+ - [ ] **Step 2: Update skills-lock.json**
1401
+
1402
+ Use the `maintain-hygiene-skills-lock` skill or run:
1403
+ ```bash
1404
+ bun run scripts/update-skills-lock.ts 2>&1 | tail -5
1405
+ ```
1406
+
1407
+ If no such script exists, manually compute SHA256 and append to `skills-lock.json`. Verify with:
1408
+ ```bash
1409
+ bun run scripts/scaffold-check.ts | grep specifier
1410
+ ```
1411
+
1412
+ Expected: no integrity error related to gxpm-specifier.
1413
+
1414
+ - [ ] **Step 3: Commit**
1415
+
1416
+ ```bash
1417
+ git add skills/gxpm-specifier/SKILL.md skills-lock.json
1418
+ git commit -m "GXPM-XXX add gxpm-specifier BDD skill
1419
+
1420
+ Discipline skill enforcing the rule that specify phase produces
1421
+ only Gherkin comments + empty test stubs; no test logic, no
1422
+ placeholders, no scenario > 10 steps. Includes hard rules,
1423
+ operating procedure, and a pre-confirm self-check list.
1424
+
1425
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1426
+ ```
1427
+
1428
+ ---
1429
+
1430
+ ## Task 12: 升级 gxpm-tdd skill 强制引用 specify.json
1431
+
1432
+ **Files:**
1433
+ - Modify: `skills/gxpm-tdd/SKILL.md`
1434
+ - Modify: `skills-lock.json`
1435
+
1436
+ - [ ] **Step 1: Add a "Specify-First Iron Law" section**
1437
+
1438
+ Edit `skills/gxpm-tdd/SKILL.md` — after the existing "### The Iron Law" section, insert a new section:
1439
+
1440
+ ```markdown
1441
+ ### The Specify-First Iron Law
1442
+
1443
+ **Before writing ANY test logic, the test scenario MUST already exist in `.gxpm/issues/<id>/artifacts/behavior-spec.json`.**
1444
+
1445
+ If you find yourself writing a test without a corresponding entry in behavior-spec.json:
1446
+ - STOP
1447
+ - Delete the test code you wrote
1448
+ - Return to specify phase: `gxpm phase rewind <id> --to specify --reason "missing scenario"`
1449
+ - Add the scenario to behavior-spec.json
1450
+ - Re-confirm with `gxpm specify confirm <id>`
1451
+ - Then resume TDD
1452
+
1453
+ **Why:** The two flows are non-negotiable serial: BDD describes WHAT behavior we want; TDD enforces THAT behavior incrementally. Jumping to TDD without a confirmed BDD spec means agent is inventing test cases, which is the precise failure mode this discipline prevents.
1454
+
1455
+ **The test stub file at `scenario.stubPath` is your contract.** Open it; the Gherkin comment block at the top is the only legitimate source of assertions you may translate into code.
1456
+ ```
1457
+
1458
+ - [ ] **Step 2: Add a red-flag entry**
1459
+
1460
+ In the existing "### 必须立即停止并重新开始的情况" section, add to the bullet list:
1461
+
1462
+ ```markdown
1463
+ - Writing a test without a matching scenario in behavior-spec.json
1464
+ - Adding assertions that do not appear in the scenario's Then clauses
1465
+ ```
1466
+
1467
+ - [ ] **Step 3: Update skills-lock.json**
1468
+
1469
+ ```bash
1470
+ bun run scripts/update-skills-lock.ts 2>&1 | tail -5
1471
+ # or manually update SHA256
1472
+ bun run scripts/scaffold-check.ts | grep gxpm-tdd
1473
+ ```
1474
+
1475
+ Expected: no integrity error.
1476
+
1477
+ - [ ] **Step 4: Commit**
1478
+
1479
+ ```bash
1480
+ git add skills/gxpm-tdd/SKILL.md skills-lock.json
1481
+ git commit -m "GXPM-XXX gate gxpm-tdd on behavior-spec scenarios
1482
+
1483
+ Adds Specify-First Iron Law: no test logic can be written
1484
+ unless a matching scenario exists in behavior-spec.json. The
1485
+ scenario.stubPath file is now the only legitimate source of
1486
+ assertions. Two new red flags added to the rationalization
1487
+ table.
1488
+
1489
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1490
+ ```
1491
+
1492
+ ---
1493
+
1494
+ ## Task 13: 创建 stub 文件模板
1495
+
1496
+ **Files:**
1497
+ - Create: `templates/specify-stub.tmpl`
1498
+
1499
+ - [ ] **Step 1: Write template**
1500
+
1501
+ Create `templates/specify-stub.tmpl`:
1502
+
1503
+ ```
1504
+ // Feature: {{feature.title}}
1505
+ //
1506
+ // As a {{feature.asA}}
1507
+ // I want {{feature.iWant}}
1508
+ // So that {{feature.soThat}}
1509
+ //
1510
+ {{#each scenarios}}
1511
+ // Scenario ({{this.id}}): {{this.name}}
1512
+ {{#each this.given}}
1513
+ // {{#if @first}}Given{{else}}And{{/if}} {{this}}
1514
+ {{/each}}
1515
+ // When {{this.when}}
1516
+ {{#each this.then}}
1517
+ // {{#if @first}}Then{{else}}And{{/if}} {{this}}
1518
+ {{/each}}
1519
+
1520
+ test("{{this.testName}}", () => {
1521
+ // intentionally empty — awaiting user confirmation
1522
+ // implement only after `gxpm specify confirm <issue-id>`
1523
+ });
1524
+
1525
+ {{/each}}
1526
+ ```
1527
+
1528
+ Note: This template uses a handlebars-like syntax. Actual rendering is the specifier agent's responsibility for now (MVP: agent writes the file directly, template is reference). A renderer can be added later if needed.
1529
+
1530
+ - [ ] **Step 2: Commit**
1531
+
1532
+ ```bash
1533
+ git add templates/specify-stub.tmpl
1534
+ git commit -m "GXPM-XXX add specify-stub template for reference
1535
+
1536
+ Reference template for what an empty BDD stub file should look
1537
+ like. The specifier agent writes the file directly from this
1538
+ shape; an automated renderer can be added later.
1539
+
1540
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1541
+ ```
1542
+
1543
+ ---
1544
+
1545
+ ## Task 14: 集成测试 — 端到端 CLI 流程
1546
+
1547
+ **Files:**
1548
+ - Create: `test/functional/gxpm-specify/init-confirm.test.ts`
1549
+
1550
+ - [ ] **Step 1: Write end-to-end test**
1551
+
1552
+ Create `test/functional/gxpm-specify/init-confirm.test.ts`:
1553
+
1554
+ ```ts
1555
+ import { describe, expect, it, beforeEach, afterEach } from "bun:test";
1556
+ import { mkdtempSync, rmSync, writeFileSync, readFileSync, mkdirSync, existsSync } from "node:fs";
1557
+ import { tmpdir } from "node:os";
1558
+ import { join } from "node:path";
1559
+ import { spawnSync } from "node:child_process";
1560
+
1561
+ const GXPM_CLI = join(import.meta.dir, "..", "..", "..", "scripts", "gxpm.ts");
1562
+
1563
+ function runCli(cwd: string, args: string[]) {
1564
+ return spawnSync("bun", ["run", GXPM_CLI, ...args], { cwd, encoding: "utf8" });
1565
+ }
1566
+
1567
+ describe("gxpm specify CLI end-to-end", () => {
1568
+ let root: string;
1569
+
1570
+ beforeEach(() => {
1571
+ root = mkdtempSync(join(tmpdir(), "gxpm-specify-e2e-"));
1572
+ });
1573
+
1574
+ afterEach(() => {
1575
+ rmSync(root, { recursive: true, force: true });
1576
+ });
1577
+
1578
+ it("init produces a draft behavior-spec with confirmedAt=null", () => {
1579
+ // Manually create issue in specify phase (skipping triage/plan/dispatch to focus on specify)
1580
+ const issueDir = join(root, ".gxpm", "issues", "G-100");
1581
+ mkdirSync(join(issueDir, "artifacts"), { recursive: true });
1582
+ writeFileSync(
1583
+ join(issueDir, "state.json"),
1584
+ JSON.stringify({
1585
+ schemaVersion: 1,
1586
+ issueId: "G-100",
1587
+ currentPhase: "specify",
1588
+ createdAt: "2026-05-14T00:00:00Z",
1589
+ updatedAt: "2026-05-14T00:00:00Z",
1590
+ stateRoot: ".gxpm/issues/G-100",
1591
+ artifactRoot: ".gxpm/issues/G-100/artifacts",
1592
+ phaseHistory: [],
1593
+ }, null, 2),
1594
+ );
1595
+ writeFileSync(join(issueDir, "events.jsonl"), "");
1596
+ writeFileSync(
1597
+ join(issueDir, "graph.json"),
1598
+ JSON.stringify({ phases: [], currentPhase: "specify", transitions: [] }),
1599
+ );
1600
+
1601
+ const r = runCli(root, ["specify", "init", "G-100"]);
1602
+ expect(r.status).toBe(0);
1603
+
1604
+ const specPath = join(issueDir, "artifacts", "behavior-spec.json");
1605
+ expect(existsSync(specPath)).toBe(true);
1606
+ const stored = JSON.parse(readFileSync(specPath, "utf8"));
1607
+ expect(stored.payload.confirmedAt).toBeNull();
1608
+ });
1609
+
1610
+ it("confirm fails when stubPath does not exist", () => {
1611
+ const issueDir = join(root, ".gxpm", "issues", "G-101");
1612
+ mkdirSync(join(issueDir, "artifacts"), { recursive: true });
1613
+ writeFileSync(
1614
+ join(issueDir, "state.json"),
1615
+ JSON.stringify({
1616
+ schemaVersion: 1,
1617
+ issueId: "G-101",
1618
+ currentPhase: "specify",
1619
+ createdAt: "2026-05-14T00:00:00Z",
1620
+ updatedAt: "2026-05-14T00:00:00Z",
1621
+ stateRoot: ".gxpm/issues/G-101",
1622
+ artifactRoot: ".gxpm/issues/G-101/artifacts",
1623
+ phaseHistory: [],
1624
+ }, null, 2),
1625
+ );
1626
+ writeFileSync(join(issueDir, "events.jsonl"), "");
1627
+ writeFileSync(join(issueDir, "graph.json"), JSON.stringify({}));
1628
+
1629
+ writeFileSync(
1630
+ join(issueDir, "artifacts", "behavior-spec.json"),
1631
+ JSON.stringify({
1632
+ schemaVersion: 1,
1633
+ issueId: "G-101",
1634
+ type: "behavior-spec",
1635
+ writtenAt: "2026-05-14T00:00:00Z",
1636
+ payload: {
1637
+ $schema: "behavior-spec.v1",
1638
+ issueId: "G-101",
1639
+ createdAt: "2026-05-14T00:00:00Z",
1640
+ createdBy: "specifier",
1641
+ confirmedAt: null,
1642
+ confirmedBy: null,
1643
+ feature: { title: "T", asA: "a", iWant: "b", soThat: "c" },
1644
+ scenarios: [
1645
+ {
1646
+ id: "scn-01",
1647
+ name: "n",
1648
+ given: ["g"],
1649
+ when: "w",
1650
+ then: ["t"],
1651
+ examples: [],
1652
+ stubPath: "test/does-not-exist.test.ts",
1653
+ },
1654
+ ],
1655
+ guidelinesRef: "docs/governance/gherkin-style.md@v1",
1656
+ },
1657
+ }, null, 2),
1658
+ );
1659
+
1660
+ const r = runCli(root, ["specify", "confirm", "G-101"]);
1661
+ expect(r.status).not.toBe(0);
1662
+ expect(r.stderr).toMatch(/Stub file missing/);
1663
+ });
1664
+ });
1665
+ ```
1666
+
1667
+ - [ ] **Step 2: Run the test**
1668
+
1669
+ Run: `bun test test/functional/gxpm-specify/init-confirm.test.ts`
1670
+ Expected: PASS — both end-to-end tests pass.
1671
+
1672
+ - [ ] **Step 3: Run full test suite**
1673
+
1674
+ Run: `bun test`
1675
+ Expected: All previously-passing tests still pass. New tests pass.
1676
+
1677
+ - [ ] **Step 4: Commit**
1678
+
1679
+ ```bash
1680
+ git add test/functional/gxpm-specify/init-confirm.test.ts
1681
+ git commit -m "GXPM-XXX e2e tests for gxpm specify init/confirm CLI
1682
+
1683
+ Two end-to-end scenarios: (1) init produces draft behavior-spec
1684
+ with confirmedAt=null; (2) confirm fails with 'Stub file missing'
1685
+ when scenario.stubPath does not exist on disk.
1686
+
1687
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1688
+ ```
1689
+
1690
+ ---
1691
+
1692
+ ## Task 15: 升级 docs/AGENTS.md / CANON.md 引用
1693
+
1694
+ **Files:**
1695
+ - Modify: `AGENTS.md`
1696
+
1697
+ - [ ] **Step 1: Add specify to phase reference**
1698
+
1699
+ Read `AGENTS.md` — locate any section listing phases (e.g., the "Command 阶段协议" table referenced in the file). Add `specify` to the phase list, with a one-line description.
1700
+
1701
+ Find a section similar to:
1702
+ ```
1703
+ | Command 阶段协议(12 命令) | CLI phase commands + `core/phase-gates.ts` | ✅ 已转译为状态机 |
1704
+ ```
1705
+
1706
+ Append a new row below the existing four-dimension table, or in a phases sub-section if one exists:
1707
+
1708
+ ```markdown
1709
+ **Phase 顺序(13 阶段)**:
1710
+ triage → plan → dispatch → **specify** → implement → local-verify → ac-check → self-review → ship → pr-check → verify → qa → land
1711
+
1712
+ - **specify**: BDD 行为规约阶段。产出 `behavior-spec.json` artifact + 空测试 stub;必须由用户通过 `gxpm specify confirm <id>` 显式确认后才能进入 implement。Owner: `specifier`. Skill: `gxpm-specifier`. 规则: `docs/governance/gherkin-style.md`.
1713
+ ```
1714
+
1715
+ - [ ] **Step 2: Commit**
1716
+
1717
+ ```bash
1718
+ git add AGENTS.md
1719
+ git commit -m "GXPM-XXX document specify phase in AGENTS.md
1720
+
1721
+ Records the new 13-phase order and a one-line summary of the
1722
+ specify phase: owner, skill, governance doc, and the hard
1723
+ requirement of user confirmation via `gxpm specify confirm`.
1724
+
1725
+ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
1726
+ ```
1727
+
1728
+ ---
1729
+
1730
+ ## Risks
1731
+
1732
+ 1. **现有 in-flight issues 的兼容性**:spec 第 5.1 节通过 `phaseHistory` 跳过校验,需在 Task 4 的 `assertArtifactGate` 中实现这一豁免(已在 spec 描述,本 plan 未单独建任务——若发现现有 issue 在测试中受影响,追加 Task 4.5)。
1733
+
1734
+ 2. **skills-lock.json 完整性**:Task 11、12 新增/修改 skill 必须同步更新 skills-lock,否则 CI 会失败。若 `scripts/update-skills-lock.ts` 不存在,需查阅 `maintain-hygiene-skills-lock` skill 的指引。
1735
+
1736
+ 3. **AskUserQuestion 在 CLI 环境不可用**:MVP 通过显式 CLI confirm 命令规避——但 specifier skill 中提及"调用 AskUserQuestion",在裸 CLI 下需 fallback 到终端 prompt,已在 spec 第 4 节明确,不影响门控正确性。
1737
+
1738
+ 4. **`gxpm phase rewind` 命令未在本 plan 实现**:spec § 5.2 提及但 MVP 不实现,留作 follow-up plan。当前流程通过 `gxpm specify revise` 已能满足"未跨阶段时回退"的核心需求;跨阶段回退仅在 implement 阶段发现 scenario 漏缺时才需要,属于低频路径。
1739
+
1740
+ 5. **Skill 压力测试未在本 plan 实现**:spec § 6.3 描述的 Meta-TDD 压力测试("用户说赶时间,跳过 BDD"、"凭直觉写测试代码"两个场景)属于 skill 治理工程,应由 `gxpm-eval` skill 在独立 follow-up plan 中实现。本 plan 已包含基础单元 + 集成测试足以验证流程正确性。
1741
+
1742
+ 6. **集成测试对 state.json schema 假设**:Task 14 中手动 seed state.json,若 IssueState schema 变化需同步更新。建议未来抽出 test helper。
1743
+
1744
+ 7. **Dogfood 自合规检查脚本未实现**:spec § 6.2 提及 `scripts/dogfood-check.ts` + pre-push hook,spec 已明确为"可推迟到 MVP 后"。留作 follow-up。
1745
+
1746
+ ---
1747
+
1748
+ ## Validation
1749
+
1750
+ 完成 Task 1-15 后整体验证:
1751
+
1752
+ 1. `bun test` — 所有测试通过
1753
+ 2. `bun run scripts/gxpm.ts check` — scaffold 检查通过
1754
+ 3. `bun run scripts/gxpm.ts specify init G-XXX` 在一个新建 specify 阶段 issue 上能产出 behavior-spec.json
1755
+ 4. `bun run scripts/gxpm.ts specify confirm G-XXX` 在 stubPath 真实存在时能成功
1756
+ 5. `bun run scripts/gxpm.ts implement init G-XXX` 在 confirmedAt=null 时被 phase-gate 拒绝,错误信息明确
1757
+
1758
+ ---
1759
+
1760
+ ## Rollback Plan
1761
+
1762
+ 若整体回滚:
1763
+ 1. `git revert` 所有 Task 提交(按反序)
1764
+ 2. 已创建的 specify phase issue 需手动迁移:编辑 `.gxpm/issues/<id>/state.json` 将 `currentPhase` 从 `"specify"` 改回 `"dispatch"`
1765
+ 3. 删除 `.gxpm/issues/<id>/artifacts/behavior-spec.json`
1766
+
1767
+ 若分段回滚:每个 commit 独立,可单独 revert,但 Task 3(phase-gate 拆分)和 Task 4(confirmedAt 校验)必须一起回滚以保持 state.ts 与 phase-gates.ts 的一致性。