@opengsd/gsd-core 1.10.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 (544) 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 +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -9,9 +9,17 @@
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;
18
+ exports.stateFieldValue = stateFieldValue;
19
+ exports.stateCurrentPositionSlice = stateCurrentPositionSlice;
13
20
  exports.stateReplaceField = stateReplaceField;
14
21
  exports.stateReplaceFieldWithFallback = stateReplaceFieldWithFallback;
22
+ exports.stateReplaceFieldInSession = stateReplaceFieldInSession;
15
23
  exports.normalizeStateStatus = normalizeStateStatus;
16
24
  exports.computeProgressPercent = computeProgressPercent;
17
25
  exports.shouldPreserveExistingProgress = shouldPreserveExistingProgress;
@@ -19,10 +27,21 @@ exports.normalizeProgressNumbers = normalizeProgressNumbers;
19
27
  exports.isStateTemplateDefault = isStateTemplateDefault;
20
28
  exports.stateReplaceFieldIfTemplate = stateReplaceFieldIfTemplate;
21
29
  const markdown_table_cjs_1 = require("./markdown-table.cjs");
22
- // Internal helpers
23
- function escapeRegex(str) {
24
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
- }
30
+ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
31
+ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
32
+ const pattern_cjs_1 = require("./pattern.cjs");
33
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
34
+ const planningScopeMod = require("./planning-scope.cjs");
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
+ */
26
45
  function toFiniteNumber(value) {
27
46
  const number = Number(value);
28
47
  return Number.isFinite(number) ? number : null;
@@ -203,8 +222,180 @@ function locateFieldRow(content, fieldName) {
203
222
  }
204
223
  return null;
205
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
+ }
206
397
  function stateExtractField(content, fieldName) {
207
- const escaped = escapeRegex(fieldName);
398
+ const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
208
399
  // Bold inline format: **FieldName:** value
209
400
  const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i');
210
401
  const boldMatch = content.match(boldPattern);
@@ -222,8 +413,107 @@ function stateExtractField(content, fieldName) {
222
413
  return hit.rawValue.trim();
223
414
  return null;
224
415
  }
416
+ /**
417
+ * Single owner of the #1760 STATE.md field-extraction fallback chain: "read
418
+ * field F, preferring the YAML frontmatter scalar, falling back to the body
419
+ * field." Added for #3187 (epic #3180, ADR-3180 §7.7) to collapse three
420
+ * independent re-derivations of this chain — `src/smart-entry.cts`'s
421
+ * `fmScalar` closure, and `src/state.cts`'s `cmdStateSnapshot` and
422
+ * `cmdStatePrune` — onto one function, per ADR-3180 Decision 1 ("keep N
423
+ * copies with a parity test" is rejected: a parity test proves today's
424
+ * agreement, not that copy N+1 won't happen).
425
+ *
426
+ * Takes ALREADY-PARSED `fm` and `body` rather than raw STATE.md content: the
427
+ * heaviest caller, `cmdStateSnapshot`, reads roughly ten fields off one parse
428
+ * and must not re-parse frontmatter per field.
429
+ *
430
+ * `stateExtractField` (above) is deliberately left untouched — it has 20
431
+ * direct callers and a CRITICAL blast radius (ADR-3180 §7.7's Rejected #1) —
432
+ * so this function is additive: it calls `stateExtractField` rather than
433
+ * replacing it or changing its signature.
434
+ *
435
+ * Fallback ladder (unchanged from every prior copy this replaces):
436
+ * 1. `fm[fmKey]` is a non-empty (post-`.trim()`) string → that trimmed
437
+ * string.
438
+ * 2. `fm[fmKey]` is a `number` or `boolean` → `String(fm[fmKey])`, so `0`
439
+ * and `false` are VALUES, not absence.
440
+ * 3. Anything else (`null`, `undefined`, an object, an array, or an
441
+ * empty/whitespace-only string) → fall through to
442
+ * `stateExtractField(body, bodyField)`.
443
+ *
444
+ * `fmKey === null` skips steps 1–2 outright: for a caller whose chain has no
445
+ * frontmatter side for this particular field (e.g. `state.cts`'s body-only
446
+ * `Last Activity` / `Last activity` case-variant pair, which sits inside a
447
+ * function that DOES own a ladder for its other fields, so per this phase's
448
+ * function-scoped guard it must still route through this owner).
449
+ * `bodyField === null` skips step 3: for a caller whose "no frontmatter
450
+ * value" case falls through to an already-computed value instead of a fresh
451
+ * extractor call (e.g. `cmdStateSnapshot`'s `last_activity`, which falls to
452
+ * its already-parsed prose date rather than re-extracting the body).
453
+ *
454
+ * `scope` reports whether the chain ran over inputs it could actually
455
+ * consult (ADR-3180 Decision 2/§7.7 — mirrors `scanPhasePlans`'s
456
+ * scope-carrying result in `plan-scan.cts`; see `planning-scope.cjs`). This
457
+ * function's own ladder always runs to completion on whatever `fm`/`body` it
458
+ * is given, INCLUDING when the answer is `null` — a genuinely absent field is
459
+ * a real answer, not a failure to look (§7.7 behavior table row 4). So
460
+ * `scope` defaults to `SCOPE.COMPLETE` and is only ever something else when
461
+ * the CALLER passes `opts.scope`, because only the caller knows whether an
462
+ * input it handed in was itself degraded — e.g. `fm` came back `{}` from an
463
+ * unterminated frontmatter fence (`extractFrontmatter` swallows that parse
464
+ * failure), or `body` is an unscoped whole-document fallback because a
465
+ * required `## Current Position` section was not found (#2956). This
466
+ * function never invents a new `SCOPE` member — the enum is frozen at
467
+ * COMPLETE/TRUNCATED/UNSCOPED/UNREADABLE (`planning-scope.cjs`).
468
+ *
469
+ * #1760 is the fallback chain's origin.
470
+ */
471
+ function stateFieldValue(fm, body, fmKey, bodyField, opts) {
472
+ const v = fmKey === null ? undefined : fm[fmKey];
473
+ let value;
474
+ if (typeof v === 'string' && v.trim()) {
475
+ value = v.trim();
476
+ }
477
+ else if (typeof v === 'number' || typeof v === 'boolean') {
478
+ value = String(v);
479
+ }
480
+ else {
481
+ value = bodyField === null ? null : stateExtractField(body, bodyField);
482
+ }
483
+ return { value, scope: opts?.scope ?? SCOPE.COMPLETE };
484
+ }
485
+ /**
486
+ * Match the "Current Position" section body from a STATE.md body. #2956: this
487
+ * is the Phase analogue of state.cts's matchSessionSection. `Phase` canonically
488
+ * lives under `## Current Position` (gsd-core/templates/state.md), so — like
489
+ * Stopped At / Paused At under `## Session` — it must be extracted from THAT
490
+ * section, not from the first `Phase:` / `**Phase:**` line anywhere in the
491
+ * body. Without the scope, a historical `Phase:` line in an archive section
492
+ * silently shadows the real one on every read/write, and because callers use
493
+ * this for routing (state.cts's current_phase) and for drift detection
494
+ * (gsd-tools.cjs's `drift-guard phase-status` CLI seam), a stale match either
495
+ * routes work to the wrong phase or fabricates a drift finding.
496
+ *
497
+ * Level-flexible: the canonical template uses an h2 `## Current Position`, the
498
+ * bootstrap template an h3 `### Current Position` (templates/state.md). Both
499
+ * must match — mirroring how matchSessionSection recognises `## Session` and
500
+ * `## Session Continuity`. Exact 'current position' text match (case-
501
+ * insensitive) excludes unrelated headings. Built on the `collectSection`
502
+ * seam, so it inherits that seam's CRLF tolerance (#2444 fix).
503
+ *
504
+ * This is the single owner of the scope — state.cts's private
505
+ * `matchCurrentPositionSection` delegates here rather than duplicating the
506
+ * logic, so the two consumers cannot drift apart.
507
+ *
508
+ * Returns the section body, or null (caller falls back to full-body search).
509
+ */
510
+ function stateCurrentPositionSlice(body) {
511
+ const isCurrentPosition = (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position';
512
+ const section = (0, markdown_sectionizer_cjs_1.collectSection)(body, isCurrentPosition, { levelBounded: true });
513
+ return section ? section.body : null;
514
+ }
225
515
  function stateReplaceField(content, fieldName, newValue) {
226
- const escaped = escapeRegex(fieldName);
516
+ const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
227
517
  // Bold inline format: **FieldName:** value
228
518
  const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
229
519
  if (boldPattern.test(content)) {
@@ -253,6 +543,33 @@ function stateReplaceFieldWithFallback(content, primary, fallback, value) {
253
543
  }
254
544
  return content;
255
545
  }
546
+ /**
547
+ * #3374: session-scoped variant of stateReplaceFieldWithFallback for the
548
+ * `## Session` continuity fields. The post-sync harvest (state.cts's
549
+ * matchSessionSection → buildStateFrontmatter) reads these fields ONLY from
550
+ * the session section, so a writer that refreshes one must target the same
551
+ * scope — a whole-body replace lets a decoy `**Stopped at:**` line in an
552
+ * unrelated (e.g. archive) section absorb the refresh while the harvested
553
+ * session value stays stale.
554
+ *
555
+ * Section preference mirrors the reader exactly: the normalized `## Session`
556
+ * block wins over the bootstrap `## Session Continuity` heading when both
557
+ * exist (legacy duplicate files); the continuity heading is only consulted
558
+ * when no canonical `## Session` section exists. `levelBounded` heading
559
+ * matching also excludes `## Session Continuity Archive` (the #2444 scoping).
560
+ *
561
+ * Replace-only (no insertion): returns `content` unchanged when no session
562
+ * section exists or the field is absent from it, so a STATE.md layout without
563
+ * the line keeps its shape and the post-sync preservation pass decides the
564
+ * frontmatter value (see #3374).
565
+ */
566
+ function stateReplaceFieldInSession(content, primary, fallback, value) {
567
+ const isSession = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session';
568
+ const isSessionContinuity = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session continuity';
569
+ const hasCanonicalSession = (0, markdown_sectionizer_cjs_1.collectSection)(content, isSession, { levelBounded: true }) !== null;
570
+ const target = hasCanonicalSession ? isSession : isSessionContinuity;
571
+ return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) => stateReplaceFieldWithFallback(sectionBody, primary, fallback, value));
572
+ }
256
573
  function normalizeStateStatus(status, pausedAt) {
257
574
  let normalizedStatus = status || 'unknown';
258
575
  const statusLower = (status || '').toLowerCase();
@@ -279,7 +596,20 @@ function normalizeStateStatus(status, pausedAt) {
279
596
  }
280
597
  return normalizedStatus;
281
598
  }
282
- function computeProgressPercent(completedPlans, totalPlans, completedPhases, totalPhases) {
599
+ /**
600
+ * ADR-3180 §7.6 rule 4 (#3217): `scope` is the `listMilestonePhaseDirs`-owner
601
+ * discriminator for the phase/plan set these four counts were derived from.
602
+ * A caller that cannot vouch for `scope === SCOPE.COMPLETE` must pass the
603
+ * scope it actually has — this function refuses to compose a percentage
604
+ * from counts whose scope says they are not a trustworthy answer, returning
605
+ * `null` (never `0`; see the module's already-existing "no data" `null`
606
+ * below, which this generalizes) exactly like its pre-existing "no data"
607
+ * case. `scope` is REQUIRED (no default) so a caller cannot silently opt out
608
+ * of rule 4 by omission.
609
+ */
610
+ function computeProgressPercent(completedPlans, totalPlans, completedPhases, totalPhases, scope) {
611
+ if (scope !== SCOPE.COMPLETE)
612
+ return null;
283
613
  const hasPlanData = totalPlans !== null && totalPlans > 0 && completedPlans !== null;
284
614
  const hasPhaseData = totalPhases !== null && totalPhases > 0 && completedPhases !== null;
285
615
  if (!hasPlanData && !hasPhaseData)
@@ -288,7 +618,7 @@ function computeProgressPercent(completedPlans, totalPlans, completedPhases, tot
288
618
  // cannot track through intermediate boolean variables).
289
619
  const planFraction = hasPlanData ? (completedPlans ?? 0) / (totalPlans ?? 1) : 1;
290
620
  const phaseFraction = hasPhaseData ? (completedPhases ?? 0) / (totalPhases ?? 1) : 1;
291
- return Math.min(100, Math.round(Math.min(planFraction, phaseFraction) * 100));
621
+ return (0, phase_lifecycle_cjs_1.clampPercentFromFraction)(Math.min(planFraction, phaseFraction));
292
622
  }
293
623
  function shouldPreserveExistingProgress(existingProgress, derivedProgress) {
294
624
  if (!existingProgress || typeof existingProgress !== 'object')
@@ -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
+ }));