@opengsd/gsd-core 1.9.1 → 1.11.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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -57,10 +57,16 @@ const phaseLifecycle = require("./phase-lifecycle.cjs");
57
57
  const { deriveProgressFromRoadmap } = phaseLifecycle;
58
58
  // eslint-disable-next-line @typescript-eslint/no-require-imports
59
59
  const stateDocument = require("./state-document.cjs");
60
- const { stateExtractField } = stateDocument;
60
+ const { stateFieldValue } = stateDocument;
61
61
  // eslint-disable-next-line @typescript-eslint/no-require-imports
62
62
  const phaseId = require("./phase-id.cjs");
63
- const { comparePhaseNum, extractPhaseToken, normalizePhaseName, phaseTokenMatches } = phaseId;
63
+ const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, stripProjectCodePrefix } = phaseId;
64
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
65
+ const stateMod = require("./state.cjs");
66
+ const { readStateHeadFreshness } = stateMod;
67
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
68
+ const unusableInput = require("./unusable-input.cjs");
69
+ const { warnUnusableInput, UNUSABLE_REASON } = unusableInput;
64
70
  // ─── Constants ────────────────────────────────────────────────────────────────
65
71
  /**
66
72
  * Staleness threshold for idle-stranded detection. A clean tree whose last
@@ -83,15 +89,6 @@ exports.SITUATIONS = Object.freeze([
83
89
  'unknown',
84
90
  ]);
85
91
  // ─── Detection ─────────────────────────────────────────────────────────────────
86
- /** Frontmatter scalar helper: prefer YAML frontmatter, fall back to body field. */
87
- function fmScalar(fm, body, key, bodyField) {
88
- const v = fm[key];
89
- if (typeof v === 'string' && v.trim())
90
- return v.trim();
91
- if (typeof v === 'number' || typeof v === 'boolean')
92
- return String(v);
93
- return stateExtractField(body, bodyField);
94
- }
95
92
  /** Read a scalar value from a nested frontmatter object (e.g. progress.total_phases). */
96
93
  function fmScalarKey(obj, key) {
97
94
  if (!obj || typeof obj !== 'object')
@@ -112,17 +109,115 @@ function parseIntOrNull(s) {
112
109
  }
113
110
  function phaseTokenFromDirName(name) {
114
111
  const token = extractPhaseToken(name);
115
- return /^\d+(?:[A-Z])?(?:\.\d+)*(?:-|$)/i.test(token) ? token : null;
112
+ // #2528: the shape probe runs on the PROJECT-CODE-STRIPPED token. A prefixed
113
+ // directory tokenizes to `MEM-05-80-20`, which does not start with a digit,
114
+ // so the unstripped probe rejected it and the entry was dropped before any
115
+ // resolution ran — every phase in a project-coded plan was invisible here.
116
+ // The full token is still what is returned: `comparePhaseNum` strips the
117
+ // prefix itself, so the sort is unaffected, and `matchPhaseDirs` needs the
118
+ // real directory name.
119
+ const probe = stripProjectCodePrefix(token);
120
+ return /^\d+(?:[A-Z])?(?:\.\d+)*(?:-|$)/i.test(probe) ? token : null;
116
121
  }
117
122
  /**
118
123
  * Parse a `last_activity` value that may be an ISO date or a free-form string
119
124
  * into an epoch-ms timestamp. Returns null when unparseable.
125
+ *
126
+ * #2570: `last_activity` routinely carries a trailing " — <description>" — the
127
+ * shape `templates/state.md` itself prescribes (`Last activity: [YYYY-MM-DD] —
128
+ * [What happened]`), which gsd-core's own STATE.md mirrors into frontmatter.
129
+ * `Date.parse` on the whole string returns NaN, and because `staleActivity`
130
+ * treats null as "not stale" (fails open), the ONLY idle/staleness detector
131
+ * never fired on any project whose last_activity retained its description.
132
+ * Be liberal in what we accept (Postel): read the leading ISO date/time token
133
+ * when the value carries one, so the description suffix — whatever separator
134
+ * (em dash or hyphen) it uses — no longer silently blinds the detector; fall
135
+ * back to a whole-string parse for any other shape a hand edit might use.
120
136
  */
137
+ /** Leading ISO date, with an optional time-of-day and offset. */
138
+ const ISO_LEADING_RE = /^(\d{4})-(\d{2})-(\d{2})((?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?)/;
139
+ /**
140
+ * A NAMED timezone designator at the start of the un-reconstructable remainder
141
+ * (#2571). ISO_LEADING_RE's offset group captures only `Z` / `±HH:MM`, so a
142
+ * named zone (GMT, EST, ...) is not in the leading token — it sits here.
143
+ * Reconstructing the token without it would let Date.parse read the time as
144
+ * LOCAL, shifting the instant by the host's offset (a wrong, host-dependent
145
+ * value), so a zone-shaped remainder must fail open (ADR-227).
146
+ *
147
+ * The shape is a short all-caps run (2–5 letters) that stands alone — the
148
+ * negative lookahead excludes the first letter of a Capitalised word like
149
+ * "Milestone", and an optional trailing offset is subsumed because the leading
150
+ * all-caps run already matches. Everything else — a lowercase or Capitalised
151
+ * description, a separator — is describable text and is reconstructed from the
152
+ * leading date.
153
+ *
154
+ * Consulted ONLY when the leading token captured a time-of-day (see the caller):
155
+ * a zone designator qualifies a clock time, so a BARE date can carry no zone
156
+ * hazard — reconstructing it is always just that date's UTC midnight, whatever
157
+ * trails it. Gating on the time keeps a description that merely opens with a
158
+ * tech acronym ("2026-06-08 CI green", "API refactor") on the reconstruct path
159
+ * instead of failing open. The prior "any letter" guard was too liberal — it
160
+ * failed open on every letter-led description and re-opened #2570.
161
+ */
162
+ const ZONE_DESIGNATOR_RE = /^\s*[A-Z]{2,5}(?![A-Za-z])/;
163
+ /**
164
+ * True only when y/m/d name a date that actually exists on the calendar.
165
+ *
166
+ * `Date.parse` validates shape but not value: it rolls an out-of-range day
167
+ * FORWARD rather than rejecting it (`2026-02-30` -> `2026-03-02`,
168
+ * `2026-04-31` -> `2026-05-01`). Shape-only validation would therefore
169
+ * propagate a different, wrong instant instead of failing safe — precisely
170
+ * what ADR-227 ("validate shape AND value; on failure of either layer coerce
171
+ * to the contract's safe default, never propagate") exists to prevent. A
172
+ * round-trip through Date.UTC detects the rollover: any component the
173
+ * constructor normalised comes back changed.
174
+ */
175
+ function isRealCalendarDate(year, month, day) {
176
+ if (month < 1 || month > 12 || day < 1 || day > 31)
177
+ return false;
178
+ const probe = new Date(Date.UTC(year, month - 1, day));
179
+ return (probe.getUTCFullYear() === year &&
180
+ probe.getUTCMonth() === month - 1 &&
181
+ probe.getUTCDate() === day);
182
+ }
121
183
  function parseActivityTimestamp(raw) {
122
184
  if (!raw)
123
185
  return null;
124
- const ms = Date.parse(raw);
125
- return Number.isNaN(ms) ? null : ms;
186
+ const trimmed = raw.trim();
187
+ const iso = trimmed.match(ISO_LEADING_RE);
188
+ if (iso) {
189
+ const [, year, month, day, time] = iso;
190
+ // Reject an impossible calendar date outright rather than letting
191
+ // Date.parse substitute a rolled-forward one. null = "no activity signal",
192
+ // the safe default staleActivity already fails open on.
193
+ if (!isRealCalendarDate(Number(year), Number(month), Number(day)))
194
+ return null;
195
+ // The date is real, so stay as liberal as before (Postel): a whole-string
196
+ // parse still wins when the engine can make sense of the value. Reading the
197
+ // token first would silently DROP a trailing zone name -- "2026-06-08
198
+ // 12:34:56 GMT" parses whole as 12:34:56Z but as local time from the token,
199
+ // shifting the instant by the host's UTC offset.
200
+ const whole = Date.parse(trimmed);
201
+ if (!Number.isNaN(whole))
202
+ return whole;
203
+ // Whole-string failed: the value carries a suffix the engine can't read as
204
+ // one instant (#2570). Reconstruct from the leading token UNLESS the remainder
205
+ // is a named zone the token dropped (GMT, EST, ...): reconstructing without it
206
+ // reads the time as LOCAL and shifts the instant by the host's offset, so a
207
+ // zone-shaped remainder fails open (ADR-227: never propagate a wrong instant;
208
+ // null is the base's not-stale default). An ordinary description -- the #2570
209
+ // template's " -- description", or a hand edit's bare-space/tab/colon suffix --
210
+ // carries no zone and IS reconstructed. See ZONE_DESIGNATOR_RE for the shape;
211
+ // the earlier "any letter" guard failed open on every description and re-opened
212
+ // #2570 for whitespace-separated suffixes.
213
+ const rest = trimmed.slice(iso[0].length);
214
+ if (time && ZONE_DESIGNATOR_RE.test(rest))
215
+ return null;
216
+ const ms = Date.parse(`${year}-${month}-${day}${time}`);
217
+ return Number.isNaN(ms) ? null : ms;
218
+ }
219
+ const direct = Date.parse(trimmed);
220
+ return Number.isNaN(direct) ? null : direct;
126
221
  }
127
222
  /** Read-only git signals. Any git error is swallowed → "no git signal". */
128
223
  function readGitSignals(cwd) {
@@ -134,6 +229,7 @@ function readGitSignals(cwd) {
134
229
  maxBuffer: 4 * 1024 * 1024,
135
230
  windowsHide: true,
136
231
  stdio: ['pipe', 'pipe', 'pipe'],
232
+ timeout: 10_000,
137
233
  });
138
234
  }
139
235
  catch {
@@ -183,7 +279,16 @@ function detectVerifyFailed(cwd, currentPhaseRaw) {
183
279
  let targetDir;
184
280
  if (phaseToken) {
185
281
  const normalized = normalizePhaseName(phaseToken);
186
- targetDir = entries.find((name) => phaseTokenMatches(name, normalized));
282
+ // #2528: the fourth directory-resolution site, and the one where a miss is
283
+ // silent — a phase whose directory cannot be found reports "not failed",
284
+ // which reads identically to a healthy phase. It must therefore apply the
285
+ // same canonical selection as the locator and the two command scans, or a
286
+ // dir like `05-80-20-cleanup` (phase 5 named "80/20 Cleanup") never
287
+ // surfaces its own failed verification. `entries` is already sorted, and
288
+ // `matchPhaseDirs` filters without reordering, so taking the first match
289
+ // preserves the previous `.find()` selection exactly.
290
+ const { matches } = matchPhaseDirs(entries, normalized);
291
+ targetDir = matches[0];
187
292
  if (!targetDir)
188
293
  return false;
189
294
  }
@@ -250,6 +355,9 @@ function detectSignals(cwd, now = Date.now) {
250
355
  stale_activity: false,
251
356
  roadmap_total_phases: null,
252
357
  roadmap_completed_phases: null,
358
+ // No STATE.md (or unreadable) → no stamp to compare. Unknown, not fresh.
359
+ state_commits_behind: null,
360
+ state_commit_stale: null,
253
361
  };
254
362
  if (!hasPlanning)
255
363
  return empty;
@@ -263,21 +371,29 @@ function detectSignals(cwd, now = Date.now) {
263
371
  // - body prose: `Phase: 3`, `**Status:** verifying`, `Total Phases: 5`
264
372
  // Read each field across every form, scalar-first then nested then body, so
265
373
  // the classifier works on real STATE.md files written by current GSD.
266
- const statusRaw = fmScalar(fm, body, 'status', 'Status');
267
- const pausedAtRaw = fmScalar(fm, body, 'paused_at', 'Paused At');
268
- const lastActivityRaw = fmScalar(fm, body, 'last_activity', 'Last Activity');
374
+ const statusRaw = stateFieldValue(fm, body, 'status', 'Status').value;
375
+ const pausedAtRaw = stateFieldValue(fm, body, 'paused_at', 'Paused At').value;
376
+ const lastActivityRaw = stateFieldValue(fm, body, 'last_activity', 'Last Activity').value;
269
377
  // current_phase: scalar fm → nested (none) → body "Current Phase" → body "Phase".
270
378
  // The body `Phase:` field is the canonical location in prose-form STATE.md
271
379
  // (e.g. "Phase: 3" or "Phase: 3 — ui-review"); parse the leading number.
272
- const currentPhaseRaw = fmScalar(fm, body, 'current_phase', 'Current Phase') ??
273
- stateExtractField(body, 'Phase');
380
+ // #3187 / ADR-3180 Amendment 3 ("0.x split"): this read is DELIBERATELY
381
+ // unscoped — smart-entry classifies `gsd next` routing over the whole body,
382
+ // whereas state.cts's copies of this same field scope it to `## Current
383
+ // Position` (#1776/#2956). Those are two different questions sharing a
384
+ // name; folding the scoped read in here would silently change smart-entry's
385
+ // routing, an undisclosed Tier-2 change (design's Rejected #3). Both owner
386
+ // calls below intentionally pass the unscoped `body`, never a Current-
387
+ // Position slice.
388
+ const currentPhaseRaw = stateFieldValue(fm, body, 'current_phase', 'Current Phase').value ??
389
+ stateFieldValue(fm, body, null, 'Phase').value;
274
390
  // total_phases & percent: nested `progress:` object takes precedence in the
275
391
  // nested schema; scalar fm / body fields cover the flat schema.
276
392
  const progressFm = typeof fm.progress === 'object' ? fm.progress : null;
277
393
  const totalPhasesRaw = fmScalarKey(progressFm, 'total_phases') ??
278
- fmScalar(fm, body, 'total_phases', 'Total Phases');
394
+ stateFieldValue(fm, body, 'total_phases', 'Total Phases').value;
279
395
  const progressRaw = fmScalarKey(progressFm, 'percent') ??
280
- fmScalar(fm, body, 'progress', 'Progress');
396
+ stateFieldValue(fm, body, 'progress', 'Progress').value;
281
397
  // Blockers list: `- <text>` items under a `## Blockers` heading.
282
398
  const blockers = [];
283
399
  const blockersSection = (0, markdown_sectionizer_cjs_1.collectSection)(body, (h) => h.level === 2 && h.text.trim().toLowerCase() === 'blockers', { levelBounded: true });
@@ -290,7 +406,16 @@ function detectSignals(cwd, now = Date.now) {
290
406
  // Stale = no recorded activity for IDLE_STALE_MS. Used only by idle-stranded.
291
407
  // Computed here (with the clock seam) so the pure classify() stays a function
292
408
  // of (signals, staleActivity) and detectSignals owns all disk reads.
409
+ // #3099 (ADR-1411 amendment): if last_activity is present but unparseable,
410
+ // emit a diagnostic so the silent fallback (stale_activity: false) is visible.
411
+ // The fallback itself stays — continuity is correct, the silence was the defect.
293
412
  const lastActivityMs = parseActivityTimestamp(lastActivityRaw);
413
+ if (lastActivityRaw && lastActivityMs === null) {
414
+ warnUnusableInput({
415
+ reason: UNUSABLE_REASON.LAST_ACTIVITY_UNPARSEABLE,
416
+ source: paths.state,
417
+ });
418
+ }
294
419
  const staleActivity = lastActivityMs !== null && now() - lastActivityMs > IDLE_STALE_MS;
295
420
  // Verify-failed may be signalled either by STATE.md status or by a failed
296
421
  // STATUS: marker on the current phase's summary/verify artifact.
@@ -314,6 +439,11 @@ function detectSignals(cwd, now = Date.now) {
314
439
  /* ROADMAP.md unreadable — leave null; isComplete falls back to legacy. */
315
440
  }
316
441
  }
442
+ // #2573: commit-age freshness proxy. Derived through state.cjs's
443
+ // readStateHeadFreshness so the tri-state and the hash fence stay identical
444
+ // to validate.health's W024 — one derivation, two surfaces.
445
+ const stateHeadRaw = stateFieldValue(fm, body, 'state_head', 'State Head').value;
446
+ const freshness = readStateHeadFreshness(cwd, stateHeadRaw);
317
447
  return {
318
448
  current_phase: parseIntOrNull(currentPhaseRaw),
319
449
  total_phases: parseIntOrNull(totalPhasesRaw),
@@ -330,6 +460,8 @@ function detectSignals(cwd, now = Date.now) {
330
460
  stale_activity: staleActivity,
331
461
  roadmap_total_phases: roadmapTotalPhases,
332
462
  roadmap_completed_phases: roadmapCompletedPhases,
463
+ state_commits_behind: freshness.commits_behind,
464
+ state_commit_stale: freshness.commit_stale,
333
465
  };
334
466
  }
335
467
  // ─── Situation classification ─────────────────────────────────────────────────
@@ -144,7 +144,11 @@ function routeStateCommand({ state, args, cwd, raw, error }) {
144
144
  'signal-resume': () => state.cmdSignalResume(cwd, raw),
145
145
  'planned-phase': () => {
146
146
  const a = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['phase', 'name', 'plans']);
147
- state.cmdStatePlannedPhase(cwd, strArg(a, 'phase'), parsePlans(strArg(a, 'plans')), raw);
147
+ // #3395: --name was parsed here but never forwarded (the StateModule
148
+ // signature had no channel for it), so the argument was silently
149
+ // dropped. It now persists into the Current Position `Phase:` line and
150
+ // the authoritative current_phase_name, mirroring begin-phase.
151
+ state.cmdStatePlannedPhase(cwd, strArg(a, 'phase'), strArg(a, 'name'), parsePlans(strArg(a, 'plans')), raw);
148
152
  },
149
153
  validate: () => state.cmdStateValidate(cwd, raw),
150
154
  sync: () => {
@@ -10,8 +10,11 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = void 0;
12
12
  exports.stateExtractField = stateExtractField;
13
+ exports.stateFieldValue = stateFieldValue;
14
+ exports.stateCurrentPositionSlice = stateCurrentPositionSlice;
13
15
  exports.stateReplaceField = stateReplaceField;
14
16
  exports.stateReplaceFieldWithFallback = stateReplaceFieldWithFallback;
17
+ exports.stateReplaceFieldInSession = stateReplaceFieldInSession;
15
18
  exports.normalizeStateStatus = normalizeStateStatus;
16
19
  exports.computeProgressPercent = computeProgressPercent;
17
20
  exports.shouldPreserveExistingProgress = shouldPreserveExistingProgress;
@@ -19,10 +22,12 @@ exports.normalizeProgressNumbers = normalizeProgressNumbers;
19
22
  exports.isStateTemplateDefault = isStateTemplateDefault;
20
23
  exports.stateReplaceFieldIfTemplate = stateReplaceFieldIfTemplate;
21
24
  const markdown_table_cjs_1 = require("./markdown-table.cjs");
22
- // Internal helpers
23
- function escapeRegex(str) {
24
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
- }
25
+ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
26
+ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
27
+ const pattern_cjs_1 = require("./pattern.cjs");
28
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
29
+ const planningScopeMod = require("./planning-scope.cjs");
30
+ const { SCOPE } = planningScopeMod;
26
31
  function toFiniteNumber(value) {
27
32
  const number = Number(value);
28
33
  return Number.isFinite(number) ? number : null;
@@ -204,7 +209,7 @@ function locateFieldRow(content, fieldName) {
204
209
  return null;
205
210
  }
206
211
  function stateExtractField(content, fieldName) {
207
- const escaped = escapeRegex(fieldName);
212
+ const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
208
213
  // Bold inline format: **FieldName:** value
209
214
  const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i');
210
215
  const boldMatch = content.match(boldPattern);
@@ -222,8 +227,107 @@ function stateExtractField(content, fieldName) {
222
227
  return hit.rawValue.trim();
223
228
  return null;
224
229
  }
230
+ /**
231
+ * Single owner of the #1760 STATE.md field-extraction fallback chain: "read
232
+ * field F, preferring the YAML frontmatter scalar, falling back to the body
233
+ * field." Added for #3187 (epic #3180, ADR-3180 §7.7) to collapse three
234
+ * independent re-derivations of this chain — `src/smart-entry.cts`'s
235
+ * `fmScalar` closure, and `src/state.cts`'s `cmdStateSnapshot` and
236
+ * `cmdStatePrune` — onto one function, per ADR-3180 Decision 1 ("keep N
237
+ * copies with a parity test" is rejected: a parity test proves today's
238
+ * agreement, not that copy N+1 won't happen).
239
+ *
240
+ * Takes ALREADY-PARSED `fm` and `body` rather than raw STATE.md content: the
241
+ * heaviest caller, `cmdStateSnapshot`, reads roughly ten fields off one parse
242
+ * and must not re-parse frontmatter per field.
243
+ *
244
+ * `stateExtractField` (above) is deliberately left untouched — it has 20
245
+ * direct callers and a CRITICAL blast radius (ADR-3180 §7.7's Rejected #1) —
246
+ * so this function is additive: it calls `stateExtractField` rather than
247
+ * replacing it or changing its signature.
248
+ *
249
+ * Fallback ladder (unchanged from every prior copy this replaces):
250
+ * 1. `fm[fmKey]` is a non-empty (post-`.trim()`) string → that trimmed
251
+ * string.
252
+ * 2. `fm[fmKey]` is a `number` or `boolean` → `String(fm[fmKey])`, so `0`
253
+ * and `false` are VALUES, not absence.
254
+ * 3. Anything else (`null`, `undefined`, an object, an array, or an
255
+ * empty/whitespace-only string) → fall through to
256
+ * `stateExtractField(body, bodyField)`.
257
+ *
258
+ * `fmKey === null` skips steps 1–2 outright: for a caller whose chain has no
259
+ * frontmatter side for this particular field (e.g. `state.cts`'s body-only
260
+ * `Last Activity` / `Last activity` case-variant pair, which sits inside a
261
+ * function that DOES own a ladder for its other fields, so per this phase's
262
+ * function-scoped guard it must still route through this owner).
263
+ * `bodyField === null` skips step 3: for a caller whose "no frontmatter
264
+ * value" case falls through to an already-computed value instead of a fresh
265
+ * extractor call (e.g. `cmdStateSnapshot`'s `last_activity`, which falls to
266
+ * its already-parsed prose date rather than re-extracting the body).
267
+ *
268
+ * `scope` reports whether the chain ran over inputs it could actually
269
+ * consult (ADR-3180 Decision 2/§7.7 — mirrors `scanPhasePlans`'s
270
+ * scope-carrying result in `plan-scan.cts`; see `planning-scope.cjs`). This
271
+ * function's own ladder always runs to completion on whatever `fm`/`body` it
272
+ * is given, INCLUDING when the answer is `null` — a genuinely absent field is
273
+ * a real answer, not a failure to look (§7.7 behavior table row 4). So
274
+ * `scope` defaults to `SCOPE.COMPLETE` and is only ever something else when
275
+ * the CALLER passes `opts.scope`, because only the caller knows whether an
276
+ * input it handed in was itself degraded — e.g. `fm` came back `{}` from an
277
+ * unterminated frontmatter fence (`extractFrontmatter` swallows that parse
278
+ * failure), or `body` is an unscoped whole-document fallback because a
279
+ * required `## Current Position` section was not found (#2956). This
280
+ * function never invents a new `SCOPE` member — the enum is frozen at
281
+ * COMPLETE/TRUNCATED/UNSCOPED/UNREADABLE (`planning-scope.cjs`).
282
+ *
283
+ * #1760 is the fallback chain's origin.
284
+ */
285
+ function stateFieldValue(fm, body, fmKey, bodyField, opts) {
286
+ const v = fmKey === null ? undefined : fm[fmKey];
287
+ let value;
288
+ if (typeof v === 'string' && v.trim()) {
289
+ value = v.trim();
290
+ }
291
+ else if (typeof v === 'number' || typeof v === 'boolean') {
292
+ value = String(v);
293
+ }
294
+ else {
295
+ value = bodyField === null ? null : stateExtractField(body, bodyField);
296
+ }
297
+ return { value, scope: opts?.scope ?? SCOPE.COMPLETE };
298
+ }
299
+ /**
300
+ * Match the "Current Position" section body from a STATE.md body. #2956: this
301
+ * is the Phase analogue of state.cts's matchSessionSection. `Phase` canonically
302
+ * lives under `## Current Position` (gsd-core/templates/state.md), so — like
303
+ * Stopped At / Paused At under `## Session` — it must be extracted from THAT
304
+ * section, not from the first `Phase:` / `**Phase:**` line anywhere in the
305
+ * body. Without the scope, a historical `Phase:` line in an archive section
306
+ * silently shadows the real one on every read/write, and because callers use
307
+ * this for routing (state.cts's current_phase) and for drift detection
308
+ * (gsd-tools.cjs's `drift-guard phase-status` CLI seam), a stale match either
309
+ * routes work to the wrong phase or fabricates a drift finding.
310
+ *
311
+ * Level-flexible: the canonical template uses an h2 `## Current Position`, the
312
+ * bootstrap template an h3 `### Current Position` (templates/state.md). Both
313
+ * must match — mirroring how matchSessionSection recognises `## Session` and
314
+ * `## Session Continuity`. Exact 'current position' text match (case-
315
+ * insensitive) excludes unrelated headings. Built on the `collectSection`
316
+ * seam, so it inherits that seam's CRLF tolerance (#2444 fix).
317
+ *
318
+ * This is the single owner of the scope — state.cts's private
319
+ * `matchCurrentPositionSection` delegates here rather than duplicating the
320
+ * logic, so the two consumers cannot drift apart.
321
+ *
322
+ * Returns the section body, or null (caller falls back to full-body search).
323
+ */
324
+ function stateCurrentPositionSlice(body) {
325
+ const isCurrentPosition = (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position';
326
+ const section = (0, markdown_sectionizer_cjs_1.collectSection)(body, isCurrentPosition, { levelBounded: true });
327
+ return section ? section.body : null;
328
+ }
225
329
  function stateReplaceField(content, fieldName, newValue) {
226
- const escaped = escapeRegex(fieldName);
330
+ const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
227
331
  // Bold inline format: **FieldName:** value
228
332
  const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
229
333
  if (boldPattern.test(content)) {
@@ -253,6 +357,33 @@ function stateReplaceFieldWithFallback(content, primary, fallback, value) {
253
357
  }
254
358
  return content;
255
359
  }
360
+ /**
361
+ * #3374: session-scoped variant of stateReplaceFieldWithFallback for the
362
+ * `## Session` continuity fields. The post-sync harvest (state.cts's
363
+ * matchSessionSection → buildStateFrontmatter) reads these fields ONLY from
364
+ * the session section, so a writer that refreshes one must target the same
365
+ * scope — a whole-body replace lets a decoy `**Stopped at:**` line in an
366
+ * unrelated (e.g. archive) section absorb the refresh while the harvested
367
+ * session value stays stale.
368
+ *
369
+ * Section preference mirrors the reader exactly: the normalized `## Session`
370
+ * block wins over the bootstrap `## Session Continuity` heading when both
371
+ * exist (legacy duplicate files); the continuity heading is only consulted
372
+ * when no canonical `## Session` section exists. `levelBounded` heading
373
+ * matching also excludes `## Session Continuity Archive` (the #2444 scoping).
374
+ *
375
+ * Replace-only (no insertion): returns `content` unchanged when no session
376
+ * section exists or the field is absent from it, so a STATE.md layout without
377
+ * the line keeps its shape and the post-sync preservation pass decides the
378
+ * frontmatter value (see #3374).
379
+ */
380
+ function stateReplaceFieldInSession(content, primary, fallback, value) {
381
+ const isSession = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session';
382
+ const isSessionContinuity = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session continuity';
383
+ const hasCanonicalSession = (0, markdown_sectionizer_cjs_1.collectSection)(content, isSession, { levelBounded: true }) !== null;
384
+ const target = hasCanonicalSession ? isSession : isSessionContinuity;
385
+ return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) => stateReplaceFieldWithFallback(sectionBody, primary, fallback, value));
386
+ }
256
387
  function normalizeStateStatus(status, pausedAt) {
257
388
  let normalizedStatus = status || 'unknown';
258
389
  const statusLower = (status || '').toLowerCase();
@@ -279,7 +410,20 @@ function normalizeStateStatus(status, pausedAt) {
279
410
  }
280
411
  return normalizedStatus;
281
412
  }
282
- function computeProgressPercent(completedPlans, totalPlans, completedPhases, totalPhases) {
413
+ /**
414
+ * ADR-3180 §7.6 rule 4 (#3217): `scope` is the `listMilestonePhaseDirs`-owner
415
+ * discriminator for the phase/plan set these four counts were derived from.
416
+ * A caller that cannot vouch for `scope === SCOPE.COMPLETE` must pass the
417
+ * scope it actually has — this function refuses to compose a percentage
418
+ * from counts whose scope says they are not a trustworthy answer, returning
419
+ * `null` (never `0`; see the module's already-existing "no data" `null`
420
+ * below, which this generalizes) exactly like its pre-existing "no data"
421
+ * case. `scope` is REQUIRED (no default) so a caller cannot silently opt out
422
+ * of rule 4 by omission.
423
+ */
424
+ function computeProgressPercent(completedPlans, totalPlans, completedPhases, totalPhases, scope) {
425
+ if (scope !== SCOPE.COMPLETE)
426
+ return null;
283
427
  const hasPlanData = totalPlans !== null && totalPlans > 0 && completedPlans !== null;
284
428
  const hasPhaseData = totalPhases !== null && totalPhases > 0 && completedPhases !== null;
285
429
  if (!hasPlanData && !hasPhaseData)
@@ -288,7 +432,7 @@ function computeProgressPercent(completedPlans, totalPlans, completedPhases, tot
288
432
  // cannot track through intermediate boolean variables).
289
433
  const planFraction = hasPlanData ? (completedPlans ?? 0) / (totalPlans ?? 1) : 1;
290
434
  const phaseFraction = hasPhaseData ? (completedPhases ?? 0) / (totalPhases ?? 1) : 1;
291
- return Math.min(100, Math.round(Math.min(planFraction, phaseFraction) * 100));
435
+ return (0, phase_lifecycle_cjs_1.clampPercentFromFraction)(Math.min(planFraction, phaseFraction));
292
436
  }
293
437
  function shouldPreserveExistingProgress(existingProgress, derivedProgress) {
294
438
  if (!existingProgress || typeof existingProgress !== 'object')