@opengsd/gsd-core 1.11.0 → 1.12.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 (395) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +1 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-dom-verifier.md +169 -0
  7. package/agents/gsd-eval-auditor.md +1 -1
  8. package/agents/gsd-executor.md +17 -9
  9. package/agents/gsd-framework-selector.md +1 -3
  10. package/agents/gsd-intel-updater.md +1 -1
  11. package/agents/gsd-mempalace-curator.md +0 -1
  12. package/agents/gsd-pattern-mapper.md +11 -0
  13. package/agents/gsd-phase-researcher.md +3 -1
  14. package/agents/gsd-plan-checker.md +15 -55
  15. package/agents/gsd-planner.md +6 -4
  16. package/agents/gsd-project-researcher.md +1 -1
  17. package/agents/gsd-research-synthesizer.md +2 -2
  18. package/agents/gsd-roadmapper.md +15 -11
  19. package/agents/gsd-ui-checker.md +63 -4
  20. package/agents/gsd-ui-researcher.md +41 -3
  21. package/agents/gsd-verifier.md +1 -1
  22. package/bin/install.js +609 -134
  23. package/commands/gsd/discuss-phase.md +1 -1
  24. package/commands/gsd/import.md +1 -1
  25. package/commands/gsd/quick.md +8 -4
  26. package/gsd-core/bin/gsd-tools.cjs +567 -51
  27. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  28. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  29. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  30. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  31. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  32. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  33. package/gsd-core/bin/lib/audit.cjs +163 -41
  34. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  35. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  36. package/gsd-core/bin/lib/capability-registry.cjs +336 -95
  37. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  38. package/gsd-core/bin/lib/capability-validator.cjs +205 -18
  39. package/gsd-core/bin/lib/check-command-router.cjs +145 -5
  40. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  41. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  42. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  43. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  44. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  45. package/gsd-core/bin/lib/commands.cjs +543 -44
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  47. package/gsd-core/bin/lib/config-loader.cjs +118 -29
  48. package/gsd-core/bin/lib/config.cjs +92 -2
  49. package/gsd-core/bin/lib/configuration.cjs +129 -37
  50. package/gsd-core/bin/lib/core-utils.cjs +84 -7
  51. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  52. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  53. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  54. package/gsd-core/bin/lib/frontmatter.cjs +840 -305
  55. package/gsd-core/bin/lib/gap-checker.cjs +27 -3
  56. package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
  57. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  58. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
  59. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  60. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  61. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  62. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  63. package/gsd-core/bin/lib/init.cjs +120 -41
  64. package/gsd-core/bin/lib/install-engine.cjs +68 -3
  65. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
  66. package/gsd-core/bin/lib/install-profiles.cjs +78 -4
  67. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  68. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  69. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  70. package/gsd-core/bin/lib/intel.cjs +101 -26
  71. package/gsd-core/bin/lib/io.cjs +160 -15
  72. package/gsd-core/bin/lib/learnings.cjs +85 -14
  73. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  74. package/gsd-core/bin/lib/markdown-table.cjs +52 -4
  75. package/gsd-core/bin/lib/milestone.cjs +90 -5
  76. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  77. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  78. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  79. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  80. package/gsd-core/bin/lib/phase-id.cjs +70 -4
  81. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  82. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  83. package/gsd-core/bin/lib/phase.cjs +405 -84
  84. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  85. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  86. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  87. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  88. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
  89. package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
  90. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  91. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  92. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  93. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  94. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  95. package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
  96. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  97. package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
  98. package/gsd-core/bin/lib/roadmap.cjs +74 -19
  99. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
  100. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
  101. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
  102. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  103. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  104. package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
  105. package/gsd-core/bin/lib/smart-entry.cjs +12 -22
  106. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  107. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  108. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  109. package/gsd-core/bin/lib/state-document.cjs +186 -0
  110. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  111. package/gsd-core/bin/lib/state-transition.cjs +517 -101
  112. package/gsd-core/bin/lib/state.cjs +946 -163
  113. package/gsd-core/bin/lib/surface.cjs +10 -2
  114. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  115. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  116. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  117. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  118. package/gsd-core/bin/lib/uat.cjs +1376 -125
  119. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  120. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  121. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  122. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  123. package/gsd-core/bin/lib/vendor/README.md +43 -5
  124. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  125. package/gsd-core/bin/lib/verification.cjs +14 -1
  126. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  127. package/gsd-core/bin/lib/verify.cjs +95 -40
  128. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  129. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  130. package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
  131. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  132. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  133. package/gsd-core/bin/shared/exit-codes.json +8 -0
  134. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  135. package/gsd-core/bin/shared/model-catalog.json +8 -1
  136. package/gsd-core/references/agent-contracts.md +3 -2
  137. package/gsd-core/references/api-coverage.md +24 -2
  138. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  139. package/gsd-core/references/checkpoints.md +37 -19
  140. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  141. package/gsd-core/references/edge-probe.md +8 -0
  142. package/gsd-core/references/execute-mvp-tdd.md +1 -3
  143. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  144. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  145. package/gsd-core/references/failing-direction.md +78 -0
  146. package/gsd-core/references/gate-prompts.md +1 -1
  147. package/gsd-core/references/git-integration.md +5 -5
  148. package/gsd-core/references/git-planning-commit.md +3 -3
  149. package/gsd-core/references/gsd-run-resolver.md +1 -1
  150. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  151. package/gsd-core/references/model-profiles.md +1 -1
  152. package/gsd-core/references/nyquist-compliance.md +74 -0
  153. package/gsd-core/references/offer-next.md +3 -5
  154. package/gsd-core/references/phase-argument-parsing.md +3 -3
  155. package/gsd-core/references/planner-failing-direction.md +53 -0
  156. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  157. package/gsd-core/references/planner-revision.md +1 -1
  158. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  159. package/gsd-core/references/planning-config.md +37 -8
  160. package/gsd-core/references/reviewer-instances.md +31 -0
  161. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  162. package/gsd-core/references/tdd.md +1 -3
  163. package/gsd-core/references/ui-brand.md +65 -21
  164. package/gsd-core/references/ui-consideration-probe.md +1 -1
  165. package/gsd-core/references/universal-anti-patterns.md +2 -2
  166. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  167. package/gsd-core/references/verify-mvp-mode.md +1 -1
  168. package/gsd-core/references/workstream-flag.md +11 -11
  169. package/gsd-core/templates/README.md +1 -1
  170. package/gsd-core/templates/SECURITY.md +3 -3
  171. package/gsd-core/templates/UI-SPEC.md +25 -3
  172. package/gsd-core/templates/VALIDATION.md +3 -3
  173. package/gsd-core/templates/phase-prompt.md +3 -0
  174. package/gsd-core/templates/state.md +7 -0
  175. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  176. package/gsd-core/workflows/add-backlog.md +1 -1
  177. package/gsd-core/workflows/add-phase.md +3 -3
  178. package/gsd-core/workflows/add-tests.md +3 -8
  179. package/gsd-core/workflows/add-todo.md +1 -1
  180. package/gsd-core/workflows/ai-integration-phase.md +4 -9
  181. package/gsd-core/workflows/audit-fix.md +12 -3
  182. package/gsd-core/workflows/audit-milestone.md +9 -9
  183. package/gsd-core/workflows/audit-uat.md +17 -2
  184. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  185. package/gsd-core/workflows/autonomous.md +10 -26
  186. package/gsd-core/workflows/check-todos.md +1 -1
  187. package/gsd-core/workflows/cleanup.md +2 -2
  188. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
  189. package/gsd-core/workflows/code-review-fix.md +1 -1
  190. package/gsd-core/workflows/code-review.md +121 -40
  191. package/gsd-core/workflows/complete-milestone.md +15 -10
  192. package/gsd-core/workflows/debug.md +5 -3
  193. package/gsd-core/workflows/diagnose-issues.md +12 -6
  194. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  195. package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
  196. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  197. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  198. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
  199. package/gsd-core/workflows/discuss-phase.md +1 -1
  200. package/gsd-core/workflows/do.md +3 -6
  201. package/gsd-core/workflows/docs-update.md +5 -4
  202. package/gsd-core/workflows/edit-phase.md +1 -1
  203. package/gsd-core/workflows/eval-review.md +4 -9
  204. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  205. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
  206. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  207. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  208. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  209. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
  210. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  211. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  212. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  213. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  214. package/gsd-core/workflows/execute-phase.md +38 -54
  215. package/gsd-core/workflows/execute-plan.md +17 -12
  216. package/gsd-core/workflows/explore.md +1 -1
  217. package/gsd-core/workflows/extract-learnings.md +1 -1
  218. package/gsd-core/workflows/fast.md +2 -2
  219. package/gsd-core/workflows/forensics.md +1 -1
  220. package/gsd-core/workflows/graduation.md +5 -5
  221. package/gsd-core/workflows/health.md +3 -6
  222. package/gsd-core/workflows/import.md +14 -11
  223. package/gsd-core/workflows/inbox.md +4 -5
  224. package/gsd-core/workflows/ingest-docs.md +44 -11
  225. package/gsd-core/workflows/insert-phase.md +5 -5
  226. package/gsd-core/workflows/list-seeds.md +5 -3
  227. package/gsd-core/workflows/list-workspaces.md +1 -1
  228. package/gsd-core/workflows/manager.md +12 -23
  229. package/gsd-core/workflows/map-codebase.md +1 -1
  230. package/gsd-core/workflows/milestone-summary.md +1 -1
  231. package/gsd-core/workflows/mvp-phase.md +2 -2
  232. package/gsd-core/workflows/new-milestone.md +9 -21
  233. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  234. package/gsd-core/workflows/new-project.md +12 -26
  235. package/gsd-core/workflows/new-workspace.md +1 -1
  236. package/gsd-core/workflows/next.md +2 -2
  237. package/gsd-core/workflows/pause-work.md +1 -1
  238. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  239. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  240. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  241. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  242. package/gsd-core/workflows/plan-phase.md +121 -42
  243. package/gsd-core/workflows/plan-review-convergence.md +46 -9
  244. package/gsd-core/workflows/plant-seed.md +2 -2
  245. package/gsd-core/workflows/pr-branch.md +187 -51
  246. package/gsd-core/workflows/profile-user.md +16 -14
  247. package/gsd-core/workflows/progress.md +27 -12
  248. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  249. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
  250. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  251. package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
  252. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  253. package/gsd-core/workflows/quick.md +20 -29
  254. package/gsd-core/workflows/remove-phase.md +4 -4
  255. package/gsd-core/workflows/remove-workspace.md +2 -2
  256. package/gsd-core/workflows/resume-project.md +8 -12
  257. package/gsd-core/workflows/review.md +193 -15
  258. package/gsd-core/workflows/scan.md +1 -1
  259. package/gsd-core/workflows/secure-phase.md +2 -2
  260. package/gsd-core/workflows/settings-advanced.md +7 -9
  261. package/gsd-core/workflows/settings-integrations.md +64 -31
  262. package/gsd-core/workflows/settings.md +3 -5
  263. package/gsd-core/workflows/ship.md +12 -6
  264. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  265. package/gsd-core/workflows/sketch.md +12 -18
  266. package/gsd-core/workflows/smart-entry.md +3 -5
  267. package/gsd-core/workflows/spec-phase.md +23 -1
  268. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  269. package/gsd-core/workflows/spike.md +20 -31
  270. package/gsd-core/workflows/stats.md +2 -2
  271. package/gsd-core/workflows/sync-skills.md +1 -1
  272. package/gsd-core/workflows/thread.md +11 -7
  273. package/gsd-core/workflows/transition.md +5 -5
  274. package/gsd-core/workflows/ui-phase.md +10 -16
  275. package/gsd-core/workflows/ui-review.md +6 -10
  276. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  277. package/gsd-core/workflows/undo.md +8 -16
  278. package/gsd-core/workflows/update.md +6 -10
  279. package/gsd-core/workflows/validate-phase.md +2 -2
  280. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  281. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  282. package/gsd-core/workflows/verify-work.md +57 -18
  283. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  284. package/hooks/dist/gsd-config-reload.js +18 -12
  285. package/hooks/dist/gsd-context-monitor.js +19 -10
  286. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  287. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  288. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  289. package/hooks/dist/gsd-cursor-stop.js +2 -1
  290. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  291. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  292. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  293. package/hooks/dist/gsd-graphify-update.sh +22 -18
  294. package/hooks/dist/gsd-node-runner.sh +76 -0
  295. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  296. package/hooks/dist/gsd-prompt-guard.js +16 -7
  297. package/hooks/dist/gsd-read-guard.js +16 -7
  298. package/hooks/dist/gsd-read-injection-scanner.js +17 -8
  299. package/hooks/dist/gsd-session-state.sh +1 -0
  300. package/hooks/dist/gsd-statusline.js +215 -26
  301. package/hooks/dist/gsd-validate-commit.sh +80 -6
  302. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  303. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  304. package/hooks/dist/gsd-workflow-guard.js +34 -16
  305. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  306. package/hooks/dist/gsd-write-guard.js +35 -25
  307. package/hooks/dist/lib/cli-exit.js +560 -0
  308. package/hooks/dist/lib/exit-code-registry.js +98 -0
  309. package/hooks/dist/lib/git-probe.js +84 -0
  310. package/hooks/dist/lib/hook-exit.js +81 -0
  311. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  312. package/hooks/gsd-agent-isolation-guard.js +77 -38
  313. package/hooks/gsd-config-reload.js +18 -12
  314. package/hooks/gsd-context-monitor.js +19 -10
  315. package/hooks/gsd-cursor-post-tool.js +3 -1
  316. package/hooks/gsd-cursor-pre-tool.js +3 -1
  317. package/hooks/gsd-cursor-session-start.js +2 -1
  318. package/hooks/gsd-cursor-stop.js +2 -1
  319. package/hooks/gsd-cursor-subagent-start.js +28 -23
  320. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  321. package/hooks/gsd-ensure-canonical-path.js +2 -1
  322. package/hooks/gsd-graphify-update.sh +22 -18
  323. package/hooks/gsd-node-runner.sh +76 -0
  324. package/hooks/gsd-phase-boundary.sh +1 -0
  325. package/hooks/gsd-prompt-guard.js +16 -7
  326. package/hooks/gsd-read-guard.js +16 -7
  327. package/hooks/gsd-read-injection-scanner.js +17 -8
  328. package/hooks/gsd-session-state.sh +1 -0
  329. package/hooks/gsd-statusline.js +215 -26
  330. package/hooks/gsd-validate-commit.sh +80 -6
  331. package/hooks/gsd-windsurf-pre-command.js +16 -11
  332. package/hooks/gsd-windsurf-pre-write.js +22 -13
  333. package/hooks/gsd-workflow-guard.js +34 -16
  334. package/hooks/gsd-worktree-path-guard.js +36 -21
  335. package/hooks/gsd-write-guard.js +35 -25
  336. package/hooks/lib/cli-exit.js +560 -0
  337. package/hooks/lib/exit-code-registry.js +98 -0
  338. package/hooks/lib/git-probe.js +84 -0
  339. package/hooks/lib/hook-exit.js +81 -0
  340. package/hooks/managed-hooks-registry.cjs +3 -0
  341. package/package.json +12 -7
  342. package/scripts/base64-scan.sh +74 -12
  343. package/scripts/build-hooks.js +5 -0
  344. package/scripts/check-glossary-refs.cjs +77 -15
  345. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  346. package/scripts/ci-check-job-near-cap.cjs +49 -0
  347. package/scripts/ci-pr-mergeability.cjs +262 -0
  348. package/scripts/ci-test-scope.cjs +45 -12
  349. package/scripts/ci-timeout-report.cjs +230 -0
  350. package/scripts/docs-guard-registry.cjs +396 -0
  351. package/scripts/gen-capability-registry.cjs +8 -6
  352. package/scripts/gen-exit-code-docs.cjs +318 -0
  353. package/scripts/gen-exit-code-registry.cjs +891 -0
  354. package/scripts/gen-features.cjs +836 -0
  355. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  356. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  357. package/scripts/gen-loop-host-contract.cjs +134 -1
  358. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  359. package/scripts/gen-state-md-docs.cjs +727 -0
  360. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  361. package/scripts/lib/ci-job-timing.cjs +72 -0
  362. package/scripts/lib/cli-exit.cjs +546 -44
  363. package/scripts/lib/drift-scan.cjs +32 -2
  364. package/scripts/lib/exit-code-registry.cjs +98 -0
  365. package/scripts/lib/ndjson-reporter.cjs +119 -0
  366. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  367. package/scripts/lint-docs-guard-registration.cjs +495 -0
  368. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  369. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  370. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  371. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  372. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  373. package/scripts/lint-phase-enumeration-drift.cjs +21 -8
  374. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  375. package/scripts/lint-removed-but-needed.cjs +184 -16
  376. package/scripts/lint-seam-enforcement.cjs +182 -0
  377. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  378. package/scripts/lint-source-test-name-collision.cjs +241 -0
  379. package/scripts/lint-state-write-path-drift.cjs +337 -432
  380. package/scripts/lint-test-file-count.allowlist.json +122 -4
  381. package/scripts/lint-test-file-count.cjs +25 -3
  382. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  383. package/scripts/lint-vendored-deps.cjs +208 -35
  384. package/scripts/mutation-matrix.cjs +599 -50
  385. package/scripts/prompt-injection-scan.sh +75 -14
  386. package/scripts/secret-scan.sh +75 -13
  387. package/scripts/select-docs-guards.cjs +56 -0
  388. package/scripts/sync-runtime-launcher.cjs +22 -3
  389. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  390. package/skills/gsd-import/SKILL.md +1 -1
  391. package/skills/gsd-quick/SKILL.md +8 -4
  392. package/vscode/package.json +1 -1
  393. package/bin/lib/ui-safety-gate.cjs +0 -109
  394. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  395. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -9,6 +9,11 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = void 0;
12
+ exports.toFiniteNumber = toFiniteNumber;
13
+ exports.isUnfilledFieldValue = isUnfilledFieldValue;
14
+ exports.leadingCalendarDate = leadingCalendarDate;
15
+ exports.isRealCalendarDate = isRealCalendarDate;
16
+ exports.stateFieldContinuation = stateFieldContinuation;
12
17
  exports.stateExtractField = stateExtractField;
13
18
  exports.stateFieldValue = stateFieldValue;
14
19
  exports.stateCurrentPositionSlice = stateCurrentPositionSlice;
@@ -28,6 +33,15 @@ const pattern_cjs_1 = require("./pattern.cjs");
28
33
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
29
34
  const planningScopeMod = require("./planning-scope.cjs");
30
35
  const { SCOPE } = planningScopeMod;
36
+ /**
37
+ * Coerce an arbitrary frontmatter scalar to a finite number, or `null` if it
38
+ * is not one. Exported per ADR-3473 §8.6: `state-transition.cts`'s
39
+ * progress-ratchet unmeasured-scan check ("is this derived total a real
40
+ * measurement?") must ask through the SAME coercion this module already uses
41
+ * for `existingProgressExceedsDerived`, rather than growing a second private
42
+ * copy. This matters because frontmatter scalars arrive as STRINGS
43
+ * (`"0"`, not `0`) — a raw `=== 0` test is wrong at both call sites.
44
+ */
31
45
  function toFiniteNumber(value) {
32
46
  const number = Number(value);
33
47
  return Number.isFinite(number) ? number : null;
@@ -208,6 +222,178 @@ function locateFieldRow(content, fieldName) {
208
222
  }
209
223
  return null;
210
224
  }
225
+ /**
226
+ * True only when y/m/d name a date that actually exists on the calendar.
227
+ *
228
+ * `Date.parse` validates shape but not value: it rolls an out-of-range day
229
+ * FORWARD rather than rejecting it (`2026-02-30` -> `2026-03-02`,
230
+ * `2026-04-31` -> `2026-05-01`). Shape-only validation would therefore
231
+ * propagate a different, wrong instant instead of failing safe — precisely
232
+ * what ADR-227 ("validate shape AND value; on failure of either layer coerce
233
+ * to the contract's safe default, never propagate") exists to prevent. A
234
+ * round-trip through Date.UTC detects the rollover: any component the
235
+ * constructor normalised comes back changed.
236
+ *
237
+ * #3696: this predicate previously lived privately inside `smart-entry.cts`,
238
+ * where it gated `parseActivityTimestamp`. `state validate` needed the same
239
+ * answer to assert the `last_activity` invariant (S008), and a second copy is
240
+ * the "generative fix divergence" class outright — two surfaces that disagree
241
+ * about whether a STATE.md is usable is the defect #3696 opens with, so a
242
+ * parity test over two copies would be codifying the bug rather than fixing
243
+ * it. It moves here because this module is already the designated owner of
244
+ * STATE.md field semantics (ADR-3180 §7.7) and `smart-entry.cts` imports no
245
+ * peer that would make the reverse direction a cycle.
246
+ */
247
+ /**
248
+ * True when a field carries no value a writer ever supplied: absent, blank, or
249
+ * still holding the shipped template's bracket placeholder.
250
+ *
251
+ * `templates/state.md:35` ships `Last activity: [YYYY-MM-DD] — [What happened]`,
252
+ * so EVERY freshly-initialized project has this exact string until something
253
+ * records activity. #3696's first cut only spared the ABSENT form, which made
254
+ * S008 fire on the shipped template itself — caught by the pre-existing
255
+ * "template-equivalent phase identities remain clean without disk drift" test,
256
+ * which is precisely what it is there for.
257
+ *
258
+ * The placeholder test is anchored at the START rather than "contains a bracket
259
+ * anywhere", so a real description that happens to cite one — `2026-08-19 — fixed
260
+ * [#123] parsing` — is still a filled-in value. That keeps the rule from
261
+ * silently swallowing genuine drift.
262
+ *
263
+ * Distinct from `isStateTemplateDefault`, which answers a different question
264
+ * ("may a later handler overwrite this?") and deliberately returns true for a
265
+ * bare ISO date — a perfectly valid value here.
266
+ */
267
+ function isUnfilledFieldValue(value) {
268
+ if (value === null || value === undefined)
269
+ return true;
270
+ const trimmed = value.trim();
271
+ return trimmed === '' || trimmed.startsWith('[');
272
+ }
273
+ /**
274
+ * The `YYYY-MM-DD` prefix of `value`, but only when it names a date that
275
+ * actually exists. `null` for anything else — no leading date token at all, or
276
+ * a token that is shape-valid and calendar-impossible.
277
+ *
278
+ * #3696 review: this is deliberately a LEADING-TOKEN test, not the fully
279
+ * anchored prose grammar `parseProseLastActivityField` uses. That function
280
+ * requires the whole value to be `date` or `date <separator> description`, and
281
+ * returns `{date: <the entire raw string>}` when it does not match — a shape
282
+ * that reads like success. Asserting the S008 invariant through it therefore
283
+ * rejected values the real reader accepts: `smart-entry`'s
284
+ * `parseActivityTimestamp` needs only a leading date and reconstructs the
285
+ * instant even when the suffix carries no dash, so
286
+ * `Last activity: 2026-08-24 Shipped feature X` parses fine there while S008
287
+ * called it unreadable. That is the same two-surfaces-disagree defect #3696
288
+ * exists to close, merely pointing the other way.
289
+ *
290
+ * So the invariant asserted is the one the readers actually share: a leading
291
+ * ISO date token that is a real calendar date.
292
+ */
293
+ function leadingCalendarDate(value) {
294
+ if (!value)
295
+ return null;
296
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?![\d-])/.exec(value.trim());
297
+ if (!match)
298
+ return null;
299
+ return isRealCalendarDate(Number(match[1]), Number(match[2]), Number(match[3]))
300
+ ? `${match[1]}-${match[2]}-${match[3]}`
301
+ : null;
302
+ }
303
+ function isRealCalendarDate(year, month, day) {
304
+ if (month < 1 || month > 12 || day < 1 || day > 31)
305
+ return false;
306
+ const probe = new Date(Date.UTC(year, month - 1, day));
307
+ return (probe.getUTCFullYear() === year &&
308
+ probe.getUTCMonth() === month - 1 &&
309
+ probe.getUTCDate() === day);
310
+ }
311
+ /**
312
+ * Markdown structure that can legitimately follow a single-line field. A line
313
+ * matching any of these is the NEXT construct, never a continuation of the
314
+ * field above it.
315
+ *
316
+ * BREADTH IS THE POINT, and the failure direction is deliberate: a missed
317
+ * truncation costs a diagnostic nobody sees, while a false S009 reports drift on
318
+ * a well-formed STATE.md — a gate that fires on valid documents is worse than no
319
+ * gate. When a shape is ambiguous, it belongs here.
320
+ *
321
+ * #3696 review round 2 added the last three arms after all three were shown to
322
+ * produce false S009 fires on well-formed content: an indented code block, an
323
+ * HTML block, and a setext underline (`===`, which the `[-*_]{3,}` rule does not
324
+ * cover — it only knows `-`, `*` and `_`).
325
+ */
326
+ const MD_STRUCTURE_LINE_RE = /^(?:#{1,6}\s|\||>|```|~~~|[-*_]{3,}\s*$|=+\s*$|[-*+]\s|\d+[.)]\s|\[[^\]]+\]:|<|(?: {4}|\t))/;
327
+ /**
328
+ * A setext heading's underline — `===` or `---` on its own line. The line ABOVE
329
+ * one of these is a heading TITLE, which is indistinguishable from prose on its
330
+ * own, so the scan must look ahead by one line rather than consume it. Without
331
+ * this, `Last activity: …\nMy Heading\n===` reported "My Heading ===" as dropped
332
+ * continuation text (#3696 review round 2).
333
+ */
334
+ const SETEXT_UNDERLINE_RE = /^(?:=+|-+)\s*$/;
335
+ const STATE_SIBLING_FIELD_LINE_RE = /^\*{0,2}[A-Za-z][A-Za-z0-9 _-]*\*{0,2}:{1,2}\*{0,2}(?:\s|$)/;
336
+ /**
337
+ * Return the prose that FOLLOWS a single-line field but plainly belongs to it —
338
+ * i.e. the remainder `stateExtractField` silently drops when a writer emits a
339
+ * value long enough to wrap.
340
+ *
341
+ * `stateExtractField`'s `(.+)` is newline-excluding, so
342
+ *
343
+ * Last activity: 2026-08-19 — Project initialized from ingest; PROJECT.md,
344
+ * REQUIREMENTS.md, ROADMAP.md written
345
+ *
346
+ * yields only the first line and the rest is lost with no diagnostic (#3696).
347
+ * `templates/state.md` prescribes a single-line field, so the DOCUMENT is what
348
+ * is wrong here, not the reader — this function exists so `state validate` can
349
+ * SAY so, not so the reader can start guessing at a multi-line grammar the
350
+ * template does not sanction.
351
+ *
352
+ * That is also why the fix is not in `stateExtractField` itself: it has 20
353
+ * direct callers and a CRITICAL blast radius (ADR-3180 §7.7, Rejected #1), and
354
+ * joining continuations there would apply to every field — `Status:` would
355
+ * swallow the line beneath it.
356
+ *
357
+ * Returns `null` when the field is absent, is a pipe-table row (a table cell
358
+ * cannot wrap), or is followed by end-of-file, a blank line, Markdown
359
+ * structure, or a sibling field.
360
+ */
361
+ function stateFieldContinuation(content, fieldName) {
362
+ const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
363
+ // Same two single-line grammars stateExtractField uses, in the same order, so
364
+ // this locates exactly the line whose value it returned. The pipe-table rung
365
+ // is deliberately absent: a `| Field | value |` row is bounded by its closing
366
+ // pipe and cannot wrap.
367
+ const match = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i').exec(content) ??
368
+ new RegExp(`^${escaped}:[ \\t]*(.+)`, 'im').exec(content);
369
+ if (!match)
370
+ return null;
371
+ // `(.+)` stops at the line terminator, so the field's line ends where the
372
+ // match does. JS `.` excludes \r as well as \n, so on a CRLF document the \r
373
+ // sits just AFTER the match rather than inside it — hence the strip below
374
+ // before testing for the newline.
375
+ const afterValue = match.index + match[0].length;
376
+ const rest = content.slice(afterValue).replace(/^\r/, '');
377
+ if (!rest.startsWith('\n'))
378
+ return null; // end of file: nothing follows
379
+ const lines = rest.slice(1).split('\n').map((line) => line.replace(/\r$/, ''));
380
+ const continuation = [];
381
+ for (let i = 0; i < lines.length; i++) {
382
+ const line = lines[i];
383
+ if (!line.trim())
384
+ break;
385
+ if (MD_STRUCTURE_LINE_RE.test(line))
386
+ break;
387
+ if (STATE_SIBLING_FIELD_LINE_RE.test(line))
388
+ break;
389
+ // Look ahead one line: a setext underline below makes THIS line a heading
390
+ // title, so stop before consuming it rather than after.
391
+ if (i + 1 < lines.length && SETEXT_UNDERLINE_RE.test(lines[i + 1]))
392
+ break;
393
+ continuation.push(line.trim());
394
+ }
395
+ return continuation.length ? continuation.join(' ') : null;
396
+ }
211
397
  function stateExtractField(content, fieldName) {
212
398
  const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
213
399
  // Bold inline format: **FieldName:** value
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ /**
3
+ * STATE.md Field Schema — the one declaration (ADR-3473 §8.8, issue #3873).
4
+ *
5
+ * Phase 3 substrate. Before this module, "which STATE.md keys exist and what
6
+ * they carry" was declared in THREE hand-maintained places that were already
7
+ * observed to disagree (see the `last_activity` docstring below):
8
+ *
9
+ * - `FIELD_CLASSIFICATION` (`src/state-transition.cts`) — source/preservation/
10
+ * guard/mergeStrategy per frontmatter key (ADR-1769 §4 / ADR-3408).
11
+ * - `FRONTMATTER_BODY_SOURCE` (`src/state-transition.cts`) — which BODY field
12
+ * a frontmatter key derives from.
13
+ * - `FRONTMATTER_KEY_TO_BODY_LABEL` (`src/state.cts`) — the Title-Case label
14
+ * a report speaks a preserved field in (ADR-3408 §8.4/§8.5).
15
+ *
16
+ * This module is the single row-per-key declaration those three now PROJECT
17
+ * from at load time (`state-transition.cts` / `state.cts`), rather than
18
+ * hand-maintaining a fourth copy of the same knowledge. Every exported shape
19
+ * of the three original tables is unchanged — same keys, same key ORDER, same
20
+ * frozen/null-prototype-ness — so every existing consumer (the preservation
21
+ * dispatch loop, `getFieldClassification`, `getPreserveWhenUnchangedFields`,
22
+ * `bodyLabelFor`, and issue #3872's `declaredLeavesOf`) keeps working without
23
+ * an edit. See `.gsd/phase/feat-3873-state-md-schema/40-design.md`.
24
+ *
25
+ * LEAF MODULE, DELIBERATELY. This file imports from neither `state-transition.cts`
26
+ * nor `state.cts` — both of those import THIS module, and either importing
27
+ * back would be the exact CJS require-cycle `src/health-diagnostic-types.cts`'s
28
+ * own docstring describes breaking for the health-diagnostic rule tables
29
+ * (`module.exports` read before it is assigned, so a destructured value comes
30
+ * back `undefined`). `FieldSource` / `FieldPreservation` / `FieldGuard` /
31
+ * `FieldMergeStrategy` therefore live HERE now and are re-exported (by the same
32
+ * name, so no importer of `state-transition.cts` needs to change) from
33
+ * `state-transition.cts`.
34
+ *
35
+ * ADR-457 build-at-publish: source in `src/state-md-schema.cts`, compiled to
36
+ * `gsd-core/bin/lib/state-md-schema.cjs` (gitignored).
37
+ *
38
+ * Design: .gsd/phase/feat-3873-state-md-schema/40-design.md
39
+ * Test matrix: .gsd/phase/feat-3873-state-md-schema/50-test-matrix.md
40
+ */
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.STATE_FIELD_SCHEMA = exports.STATUS_LIFECYCLE_ENUM = void 0;
43
+ /**
44
+ * The seven CANONICAL values `normalizeStateStatus` (`src/state-document.cts`)
45
+ * maps recognized raw status prose TO — the function's default fallback plus
46
+ * each branch's literal output, in the order the function tests them. This is
47
+ * NOT the raw body prose vocabulary `CONTEXT.md`'s "STATE.md Status Lifecycle
48
+ * (ADR-2207)" entry documents (`Ready to plan` → `All phases complete` →
49
+ * `<version> milestone complete` → `Awaiting next milestone`, plus the
50
+ * handler-authored strings in `KNOWN_TEMPLATE_DEFAULTS['Status']`) — that is
51
+ * free-form prose `normalizeStateStatus` READS.
52
+ *
53
+ * CORRECTED (#3873 phase-3 test-matrix row 26 — verified by executing
54
+ * `normalizeStateStatus`, not by reading this docstring's prior claim):
55
+ * this is NOT a closed set the `status` frontmatter key is restricted to at
56
+ * runtime. `normalizeStateStatus` is deliberately LENIENT: its fallback is
57
+ * `normalizedStatus = status || 'unknown'`, and when none of its
58
+ * substring-match branches recognize the raw input, that fallback — the
59
+ * caller's raw, UNRECOGNIZED prose — is returned unchanged. A status value
60
+ * outside this seven-member set is not rejected, coerced, or normalized; it
61
+ * passes straight through into the frontmatter. `STATUS_LIFECYCLE_ENUM` is
62
+ * therefore the set of values the normalizer maps recognized input ONTO, not
63
+ * a runtime-enforced closed vocabulary for the field.
64
+ */
65
+ exports.STATUS_LIFECYCLE_ENUM = Object.freeze([
66
+ 'unknown',
67
+ 'paused',
68
+ 'executing',
69
+ 'planning',
70
+ 'discussing',
71
+ 'verifying',
72
+ 'completed',
73
+ ]);
74
+ // ─── The one declaration ────────────────────────────────────────────────────
75
+ //
76
+ // Row order below is `FIELD_CLASSIFICATION`'s (`src/state-transition.cts`,
77
+ // pre-#3873) ORIGINAL literal order, verified by direct read and preserved
78
+ // deliberately: the `FIELD_CLASSIFICATION` projection built from this table
79
+ // (`state-transition.cts`) walks `Object.keys(STATE_FIELD_SCHEMA)` directly,
80
+ // so this row order IS that projection's key order, and key order is
81
+ // observable (the preservation dispatch loop iterates it). The two other
82
+ // projections (`FRONTMATTER_BODY_SOURCE`, `FRONTMATTER_KEY_TO_BODY_LABEL`) do
83
+ // NOT reuse this same order — their pre-#3873 literals were independently
84
+ // hand-written and already disagreed with each other and with this order (see
85
+ // each projection's own ordering constant in its home module) — so each
86
+ // projection module declares its OWN explicit key-order list rather than
87
+ // re-deriving order from this table's iteration, which would silently change
88
+ // two of the three tables' observable order out from under every consumer.
89
+ exports.STATE_FIELD_SCHEMA = Object.freeze(Object.assign(Object.create(null), {
90
+ // Schema
91
+ gsd_state_version: {
92
+ type: 'string', cardinality: 'one', source: 'free', preservation: 'derive', emitted: 'always',
93
+ },
94
+ // Milestone (external — from ROADMAP.md)
95
+ milestone: {
96
+ type: 'string', cardinality: 'optional', source: 'external', preservation: 'preserve-if-placeholder', emitted: 'when-present',
97
+ },
98
+ milestone_name: {
99
+ type: 'string', cardinality: 'optional', source: 'external', preservation: 'preserve-if-placeholder', emitted: 'when-present',
100
+ },
101
+ // Phase / plan position (body-derived)
102
+ current_phase: {
103
+ type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
104
+ bodySource: Object.freeze(['Current Phase']), bodyLabel: 'Current Phase', emitted: 'when-present',
105
+ },
106
+ current_phase_name: {
107
+ type: 'string', cardinality: 'optional', source: 'curated', preservation: 'preserve-when-unchanged',
108
+ bodySource: Object.freeze(['Current Phase Name']), bodyLabel: 'Current Phase Name', emitted: 'when-present',
109
+ },
110
+ current_plan: {
111
+ type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
112
+ bodySource: Object.freeze(['Current Plan']), bodyLabel: 'Current Plan',
113
+ // #3873 phase-3 test-matrix row 25 (verified by executing
114
+ // `advancePlanCore`, `src/state-transition.cts:1306`, not by reading
115
+ // its docstring): TODAY the `Current Plan` body field parses in
116
+ // exactly ONE shape — a bare number `N`, paired with a separate
117
+ // `Total Plans in Phase` field. The hybrid compound `N of M` written
118
+ // directly into `Current Plan` (no `Total Plans in Phase` sibling)
119
+ // does NOT parse: `legacyTotal` is absent, `planField` reads the
120
+ // DIFFERENT `Plan` field (also absent), so the function falls to its
121
+ // NaN/NaN error branch. Feeding `Current Plan: 2 of 5` WITH a
122
+ // `Total Plans in Phase` sibling present does not change this — it
123
+ // "succeeds" only because `parseInt("2 of 5", 10)` truncates to `2`
124
+ // and the sibling supplies the total; the `of 5` half is silently
125
+ // discarded, which is `parseInt` coincidence, not shape recognition.
126
+ // `Plan: N of M` (a DIFFERENT field name) DOES parse the hybrid shape,
127
+ // but `buildStateFrontmatter` never reads `Plan` into `current_plan`
128
+ // (verified: it calls `stateExtractField(bodyContent, 'Current Plan')`
129
+ // only), so that shape is out of scope for this row regardless.
130
+ //
131
+ // #3784 is the open issue for teaching `Current Plan` to read the
132
+ // hybrid shape; **PR #3791** ("fix(#3784): read the hybrid
133
+ // `Current Plan: N of M` shape, keep zero-padding, and name the
134
+ // accepted shapes on failure") is the in-flight fix. Do NOT widen
135
+ // this row speculatively — that would assert a shape the shipped
136
+ // parser does not accept, which is the exact defect class §8.8
137
+ // exists to make impossible. When #3791 merges, `acceptedShapes`
138
+ // MUST widen to `['N', 'N of M']` — until then, the row 23/24/25
139
+ // parser-shape tests (`tests/state-transition.test.cjs`) will go RED
140
+ // the moment the parser changes underneath it. That failure is the
141
+ // forcing function working as designed, not a broken test: it is
142
+ // what stops the schema and the parser from drifting apart silently.
143
+ acceptedShapes: Object.freeze(['N']),
144
+ emitted: 'when-present',
145
+ },
146
+ // Status / lifecycle (body-derived; #1230 delta heuristic applies)
147
+ // guard: the 'unknown' sentinel is the ONLY true executor-side guard in
148
+ // this table (stopped_at's `## Session` scoping is caller-side delta
149
+ // extraction, not an executor condition) — ADR-3408 Decision 1.
150
+ status: {
151
+ type: 'string', enum: exports.STATUS_LIFECYCLE_ENUM, cardinality: 'one', source: 'body', preservation: 'preserve-when-unchanged',
152
+ guard: 'non-sentinel-unknown', bodySource: Object.freeze(['Status']), bodyLabel: 'Status', emitted: 'always',
153
+ },
154
+ stopped_at: {
155
+ type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
156
+ bodySource: Object.freeze(['Stopped At', 'Stopped at']), bodyLabel: 'Stopped At', emitted: 'when-present',
157
+ },
158
+ paused_at: {
159
+ type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
160
+ bodySource: Object.freeze(['Paused At']), bodyLabel: 'Paused At', emitted: 'when-present',
161
+ },
162
+ // Activity log
163
+ last_updated: {
164
+ type: 'string', cardinality: 'one', source: 'free', preservation: 'derive', emitted: 'always',
165
+ }, // realClock.nowIso()
166
+ // #3873: THE LIVE DISAGREEMENT. Pre-schema, `FRONTMATTER_BODY_SOURCE`
167
+ // carried this key (`last_activity: ['Last Activity', 'Last activity']`)
168
+ // while `FRONTMATTER_KEY_TO_BODY_LABEL` did NOT — same field, two
169
+ // tables, two different answers to "does this key have a reportable
170
+ // body label". Resolved by DECLARATION, not by picking whichever table
171
+ // "looks right": `bodySource` is present below (this key IS derived from
172
+ // a body field and `buildStateFrontmatter` — `src/state.cts` — reads it
173
+ // via that exact two-case-variant fallback), and `bodyLabel` is
174
+ // deliberately ABSENT, because that is what ships TODAY —
175
+ // `last_activity`'s `preservation` is `'derive'`, never
176
+ // `'preserve-when-unchanged'`, so it can never reach `bodyLabelFor`'s
177
+ // (`src/state.cts`) `STATE_BODY_LABEL_UNWIRED_ROW` throw in the first
178
+ // place; the absent label is inert, not a latent bug. Pinned by
179
+ // `tests/state.test.cjs`'s pre-existing
180
+ // `lastActivityLabelResolutionMatchesShippedBehavior`. Do NOT "tidy"
181
+ // this by adding a label — that would be shipping a policy change
182
+ // disguised as a consolidation, exactly the #3427 failure this epic is
183
+ // named after.
184
+ last_activity: {
185
+ type: 'string', cardinality: 'optional', source: 'body', preservation: 'derive',
186
+ bodySource: Object.freeze(['Last Activity', 'Last activity']), emitted: 'when-present',
187
+ }, // always refresh on transition
188
+ last_activity_desc: {
189
+ type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
190
+ bodySource: Object.freeze(['Last Activity Description']), bodyLabel: 'Last Activity Description', emitted: 'when-present',
191
+ },
192
+ // Commit provenance (#2573) — ambient git read, recomputed on every write,
193
+ // exactly like last_updated. Never preserved: a stale stamp would claim
194
+ // STATE.md was written against a commit it wasn't.
195
+ state_head: {
196
+ type: 'string', cardinality: 'optional', source: 'free', preservation: 'derive', emitted: 'when-present',
197
+ }, // #2573
198
+ // Progress block (disk-derived, except the curated progress ratchet)
199
+ // mergeStrategy: 'progress-ratchet' — completed_plans/completed_phases
200
+ // only ever ratchet UP toward the derived value (#2969); everything
201
+ // else in the merge is either always-derived (#2440) or always-curated.
202
+ progress: {
203
+ type: 'object', cardinality: 'optional', source: 'curated', preservation: 'preserve-always',
204
+ mergeStrategy: 'progress-ratchet', emitted: 'when-present',
205
+ }, // #3242, #1446
206
+ 'progress.total_phases': {
207
+ type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
208
+ },
209
+ 'progress.completed_phases': {
210
+ type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
211
+ },
212
+ 'progress.total_plans': {
213
+ type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
214
+ },
215
+ 'progress.completed_plans': {
216
+ type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
217
+ },
218
+ 'progress.percent': {
219
+ type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
220
+ },
221
+ }));