@opengsd/gsd-core 1.15.0 → 1.16.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 (487) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +85 -5
  4. package/agents/gsd-code-fixer.compact.md +1 -1
  5. package/agents/gsd-code-fixer.md +1 -1
  6. package/agents/gsd-code-reviewer.compact.md +5 -3
  7. package/agents/gsd-code-reviewer.md +8 -6
  8. package/agents/gsd-debug-session-manager.compact.md +1 -1
  9. package/agents/gsd-debug-session-manager.md +1 -1
  10. package/agents/gsd-debugger.md +2 -2
  11. package/agents/gsd-eval-auditor.compact.md +1 -1
  12. package/agents/gsd-eval-auditor.md +1 -1
  13. package/agents/gsd-executor.md +4 -4
  14. package/agents/gsd-intel-updater.compact.md +1 -1
  15. package/agents/gsd-intel-updater.md +1 -1
  16. package/agents/gsd-mempalace-curator.md +2 -2
  17. package/agents/gsd-phase-researcher.md +1 -1
  18. package/agents/gsd-plan-checker.md +7 -2
  19. package/agents/gsd-planner.md +3 -3
  20. package/agents/gsd-project-researcher.compact.md +1 -1
  21. package/agents/gsd-project-researcher.md +1 -1
  22. package/agents/gsd-research-synthesizer.compact.md +1 -1
  23. package/agents/gsd-research-synthesizer.md +1 -1
  24. package/agents/gsd-ui-auditor.compact.md +21 -30
  25. package/agents/gsd-ui-auditor.md +42 -51
  26. package/agents/gsd-ui-researcher.compact.md +1 -1
  27. package/agents/gsd-ui-researcher.md +1 -1
  28. package/agents/gsd-verifier.md +31 -9
  29. package/bin/install.js +126 -196
  30. package/commands/gsd/add-tests.md +6 -1
  31. package/commands/gsd/ai-integration-phase.md +6 -1
  32. package/commands/gsd/audit-fix.md +5 -0
  33. package/commands/gsd/audit-milestone.md +6 -1
  34. package/commands/gsd/autonomous.md +5 -0
  35. package/commands/gsd/capture.md +8 -4
  36. package/commands/gsd/code-review.md +7 -2
  37. package/commands/gsd/complete-milestone.md +4 -0
  38. package/commands/gsd/config.md +7 -3
  39. package/commands/gsd/debug.md +11 -7
  40. package/commands/gsd/discuss-phase.md +7 -3
  41. package/commands/gsd/docs-update.md +12 -7
  42. package/commands/gsd/eval-review.md +6 -1
  43. package/commands/gsd/execute-phase.md +12 -7
  44. package/commands/gsd/extract-learnings.md +5 -0
  45. package/commands/gsd/fast.md +4 -0
  46. package/commands/gsd/forensics.md +5 -1
  47. package/commands/gsd/graphify.md +10 -6
  48. package/commands/gsd/health.md +5 -0
  49. package/commands/gsd/help.md +7 -2
  50. package/commands/gsd/import.md +7 -3
  51. package/commands/gsd/inbox.md +5 -0
  52. package/commands/gsd/ingest-docs.md +5 -1
  53. package/commands/gsd/manager.md +6 -1
  54. package/commands/gsd/map-codebase.md +7 -3
  55. package/commands/gsd/mempalace-capture.md +5 -1
  56. package/commands/gsd/mempalace-recall.md +5 -1
  57. package/commands/gsd/milestone-summary.md +5 -1
  58. package/commands/gsd/mvp-phase.md +8 -3
  59. package/commands/gsd/new-milestone.md +6 -1
  60. package/commands/gsd/new-project.md +5 -0
  61. package/commands/gsd/next.md +6 -1
  62. package/commands/gsd/ns-context.md +4 -0
  63. package/commands/gsd/ns-ideate.md +4 -0
  64. package/commands/gsd/ns-manage.md +4 -0
  65. package/commands/gsd/ns-project.md +4 -0
  66. package/commands/gsd/ns-review.md +4 -0
  67. package/commands/gsd/ns-workflow.md +4 -0
  68. package/commands/gsd/onboard.md +6 -1
  69. package/commands/gsd/pause-work.md +5 -1
  70. package/commands/gsd/phase.md +8 -4
  71. package/commands/gsd/plan-phase.md +6 -1
  72. package/commands/gsd/plan-review-convergence.md +5 -1
  73. package/commands/gsd/pr-branch.md +4 -0
  74. package/commands/gsd/profile-user.md +5 -1
  75. package/commands/gsd/progress.md +6 -1
  76. package/commands/gsd/quick-batch.md +20 -8
  77. package/commands/gsd/quick.md +12 -7
  78. package/commands/gsd/review.md +5 -1
  79. package/commands/gsd/secure-phase.md +6 -1
  80. package/commands/gsd/ship.md +5 -0
  81. package/commands/gsd/sketch.md +7 -2
  82. package/commands/gsd/spec-phase.md +5 -1
  83. package/commands/gsd/spike.md +8 -3
  84. package/commands/gsd/surface.md +5 -1
  85. package/commands/gsd/thread.md +4 -0
  86. package/commands/gsd/ui-phase.md +6 -1
  87. package/commands/gsd/ui-review.md +6 -1
  88. package/commands/gsd/ultraplan-phase.md +5 -1
  89. package/commands/gsd/undo.md +5 -1
  90. package/commands/gsd/update.md +6 -2
  91. package/commands/gsd/validate-phase.md +6 -1
  92. package/commands/gsd/verify-work.md +6 -1
  93. package/commands/gsd/workspace.md +7 -3
  94. package/gsd-core/bin/gsd-tools.cjs +142 -56
  95. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  96. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  97. package/gsd-core/bin/lib/audit.cjs +78 -44
  98. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  99. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  100. package/gsd-core/bin/lib/capability-registry.cjs +183 -103
  101. package/gsd-core/bin/lib/capability-validator.cjs +16 -0
  102. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  103. package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
  104. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  105. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  106. package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
  107. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  108. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  109. package/gsd-core/bin/lib/commands.cjs +173 -170
  110. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  111. package/gsd-core/bin/lib/config.cjs +43 -12
  112. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  113. package/gsd-core/bin/lib/coverage.cjs +4 -8
  114. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  115. package/gsd-core/bin/lib/drift.cjs +177 -42
  116. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  117. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  118. package/gsd-core/bin/lib/frontmatter.cjs +413 -234
  119. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  120. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  121. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  122. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  123. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  124. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  125. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  126. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  127. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  128. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  129. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  130. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  131. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  132. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  133. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  134. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  135. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  136. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  137. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  138. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  139. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  140. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  141. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  142. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  143. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  144. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  145. package/gsd-core/bin/lib/init.cjs +162 -96
  146. package/gsd-core/bin/lib/install-engine.cjs +19 -35
  147. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  148. package/gsd-core/bin/lib/io.cjs +122 -3
  149. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  150. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  151. package/gsd-core/bin/lib/milestone.cjs +19 -1
  152. package/gsd-core/bin/lib/model-resolver.cjs +18 -18
  153. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  154. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  155. package/gsd-core/bin/lib/pattern.cjs +10 -0
  156. package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
  157. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  158. package/gsd-core/bin/lib/phase-id.cjs +1 -1
  159. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  160. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  161. package/gsd-core/bin/lib/phase.cjs +280 -80
  162. package/gsd-core/bin/lib/plan-document.cjs +93 -19
  163. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  164. package/gsd-core/bin/lib/planning-document.cjs +273 -40
  165. package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
  166. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  167. package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
  168. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  169. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  170. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  171. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  172. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  173. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  174. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  175. package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
  176. package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
  177. package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
  178. package/gsd-core/bin/lib/roadmap.cjs +169 -72
  179. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +91 -157
  180. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
  181. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  182. package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
  183. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +307 -591
  184. package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
  185. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  186. package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
  187. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  188. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  189. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  190. package/gsd-core/bin/lib/state-document.cjs +100 -22
  191. package/gsd-core/bin/lib/state.cjs +214 -105
  192. package/gsd-core/bin/lib/surface.cjs +2 -1
  193. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
  194. package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
  195. package/gsd-core/bin/lib/uat.cjs +424 -7
  196. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
  197. package/gsd-core/bin/lib/vendor/README.md +31 -9
  198. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  199. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  200. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  201. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  202. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  203. package/gsd-core/bin/lib/verification.cjs +1165 -314
  204. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  205. package/gsd-core/bin/lib/verify.cjs +286 -567
  206. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  207. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  208. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  209. package/gsd-core/bin/lib/workstream.cjs +11 -2
  210. package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
  211. package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
  212. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  213. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  214. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  215. package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
  216. package/gsd-core/references/execute-phase-response-language.md +1 -1
  217. package/gsd-core/references/gsd-run-resolver.md +1 -1
  218. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  219. package/gsd-core/references/offer-next.md +1 -1
  220. package/gsd-core/references/planning-config.md +1 -1
  221. package/gsd-core/references/spidr-splitting.md +1 -1
  222. package/gsd-core/references/tdd.md +10 -4
  223. package/gsd-core/references/verifier-phase-gates.md +5 -2
  224. package/gsd-core/references/verify-mvp-mode.md +2 -2
  225. package/gsd-core/references/workstream-flag.md +33 -3
  226. package/gsd-core/templates/README.md +1 -1
  227. package/gsd-core/templates/UAT.md +17 -1
  228. package/gsd-core/templates/config.json +2 -11
  229. package/gsd-core/templates/verification-report.md +1 -1
  230. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  231. package/gsd-core/workflows/add-backlog.md +1 -1
  232. package/gsd-core/workflows/add-phase.md +8 -7
  233. package/gsd-core/workflows/add-tests.md +3 -2
  234. package/gsd-core/workflows/add-todo.md +4 -3
  235. package/gsd-core/workflows/ai-integration-phase.md +3 -2
  236. package/gsd-core/workflows/audit-fix.md +1 -1
  237. package/gsd-core/workflows/audit-milestone.md +4 -3
  238. package/gsd-core/workflows/audit-uat.md +1 -1
  239. package/gsd-core/workflows/autonomous.md +28 -18
  240. package/gsd-core/workflows/check-todos.md +6 -5
  241. package/gsd-core/workflows/cleanup.md +1 -1
  242. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  243. package/gsd-core/workflows/code-review-fix.md +5 -4
  244. package/gsd-core/workflows/code-review.md +84 -90
  245. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  246. package/gsd-core/workflows/complete-milestone.md +12 -7
  247. package/gsd-core/workflows/debug.md +30 -5
  248. package/gsd-core/workflows/diagnose-issues.md +3 -2
  249. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  250. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  252. package/gsd-core/workflows/discuss-phase.md +4 -3
  253. package/gsd-core/workflows/do.md +1 -1
  254. package/gsd-core/workflows/docs-update.md +4 -3
  255. package/gsd-core/workflows/edit-phase.md +4 -3
  256. package/gsd-core/workflows/eval-review.md +5 -3
  257. package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
  258. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
  259. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  260. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  261. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  263. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  264. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  265. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  266. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  267. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  268. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  269. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  270. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  271. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  272. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  273. package/gsd-core/workflows/execute-phase.md +78 -170
  274. package/gsd-core/workflows/execute-plan.md +13 -19
  275. package/gsd-core/workflows/explore.md +2 -2
  276. package/gsd-core/workflows/extract-learnings.md +3 -2
  277. package/gsd-core/workflows/fast.md +1 -1
  278. package/gsd-core/workflows/forensics.md +1 -1
  279. package/gsd-core/workflows/graduation.md +1 -1
  280. package/gsd-core/workflows/health.md +2 -1
  281. package/gsd-core/workflows/import.md +3 -2
  282. package/gsd-core/workflows/inbox.md +1 -1
  283. package/gsd-core/workflows/ingest-docs.md +1 -1
  284. package/gsd-core/workflows/insert-phase.md +4 -3
  285. package/gsd-core/workflows/list-seeds.md +1 -1
  286. package/gsd-core/workflows/list-workspaces.md +1 -1
  287. package/gsd-core/workflows/manager.md +5 -3
  288. package/gsd-core/workflows/map-codebase.md +4 -3
  289. package/gsd-core/workflows/milestone-summary.md +3 -2
  290. package/gsd-core/workflows/mvp-phase.md +14 -14
  291. package/gsd-core/workflows/new-milestone.md +10 -10
  292. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  293. package/gsd-core/workflows/new-project.md +1 -1
  294. package/gsd-core/workflows/new-workspace.md +1 -1
  295. package/gsd-core/workflows/next.md +1 -1
  296. package/gsd-core/workflows/pause-work.md +2 -2
  297. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  298. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  299. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  300. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  301. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
  302. package/gsd-core/workflows/plan-phase.md +27 -18
  303. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  304. package/gsd-core/workflows/plant-seed.md +1 -1
  305. package/gsd-core/workflows/pr-branch.md +1 -1
  306. package/gsd-core/workflows/profile-user.md +1 -1
  307. package/gsd-core/workflows/progress.md +19 -49
  308. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
  309. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  310. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  311. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  312. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  313. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  314. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  315. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  316. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  317. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  318. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  319. package/gsd-core/workflows/quick-batch.md +15 -10
  320. package/gsd-core/workflows/quick.md +42 -31
  321. package/gsd-core/workflows/remove-phase.md +3 -2
  322. package/gsd-core/workflows/remove-workspace.md +1 -1
  323. package/gsd-core/workflows/resume-project.md +3 -2
  324. package/gsd-core/workflows/review.md +3 -2
  325. package/gsd-core/workflows/scan.md +3 -2
  326. package/gsd-core/workflows/secure-phase.md +11 -12
  327. package/gsd-core/workflows/settings-advanced.md +1 -1
  328. package/gsd-core/workflows/settings-integrations.md +1 -1
  329. package/gsd-core/workflows/settings.md +1 -1
  330. package/gsd-core/workflows/ship.md +12 -12
  331. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  332. package/gsd-core/workflows/sketch.md +1 -1
  333. package/gsd-core/workflows/smart-entry.md +1 -1
  334. package/gsd-core/workflows/spec-phase.md +1 -1
  335. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  336. package/gsd-core/workflows/spike.md +1 -1
  337. package/gsd-core/workflows/stats.md +1 -1
  338. package/gsd-core/workflows/sync-skills.md +1 -1
  339. package/gsd-core/workflows/thread.md +2 -2
  340. package/gsd-core/workflows/transition.md +13 -23
  341. package/gsd-core/workflows/ui-phase.md +5 -4
  342. package/gsd-core/workflows/ui-review.md +4 -3
  343. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  344. package/gsd-core/workflows/undo.md +1 -1
  345. package/gsd-core/workflows/validate-phase.md +10 -12
  346. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  347. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  348. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  349. package/gsd-core/workflows/verify-work.md +68 -59
  350. package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
  351. package/hooks/dist/gsd-check-update-worker.js +8 -0
  352. package/hooks/dist/gsd-check-update.js +8 -0
  353. package/hooks/dist/gsd-context-monitor.js +31 -8
  354. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  355. package/hooks/dist/gsd-secret-read-guard.js +147 -16
  356. package/hooks/dist/gsd-statusline.js +15 -7
  357. package/hooks/dist/gsd-update-banner.js +8 -0
  358. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  359. package/hooks/dist/gsd-workflow-guard.js +5 -4
  360. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  361. package/hooks/dist/lib/cli-exit.js +12 -0
  362. package/hooks/dist/lib/git-probe.js +17 -1
  363. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  364. package/hooks/gsd-agent-isolation-guard.js +8 -0
  365. package/hooks/gsd-check-update-worker.js +8 -0
  366. package/hooks/gsd-check-update.js +8 -0
  367. package/hooks/gsd-context-monitor.js +31 -8
  368. package/hooks/gsd-cursor-subagent-start.js +8 -0
  369. package/hooks/gsd-secret-read-guard.js +147 -16
  370. package/hooks/gsd-statusline.js +15 -7
  371. package/hooks/gsd-update-banner.js +8 -0
  372. package/hooks/gsd-windsurf-pre-write.js +11 -2
  373. package/hooks/gsd-workflow-guard.js +5 -4
  374. package/hooks/gsd-worktree-path-guard.js +6 -2
  375. package/hooks/hooks.json +5 -5
  376. package/hooks/lib/cli-exit.js +12 -0
  377. package/hooks/lib/git-probe.js +17 -1
  378. package/hooks/lib/isolation-sentinel.js +2 -2
  379. package/package.json +21 -4
  380. package/scripts/changeset/parse.cjs +52 -4
  381. package/scripts/ci-timeout-report.cjs +770 -4
  382. package/scripts/command-contract-helpers.cjs +12 -8
  383. package/scripts/docs-guard-registry.cjs +6 -0
  384. package/scripts/gen-features.cjs +13 -8
  385. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  386. package/scripts/gen-loop-host-contract.cjs +10 -1
  387. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  388. package/scripts/gen-plugin-skills.cjs +87 -1
  389. package/scripts/gen-research-agents.cjs +24 -31
  390. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  391. package/scripts/gen-test-timings.cjs +32 -7
  392. package/scripts/lib/cli-exit.cjs +12 -0
  393. package/scripts/lib/macos-conformance-tier.generated.cjs +20 -2
  394. package/scripts/lib/ndjson-reporter.cjs +28 -3
  395. package/scripts/lib/platform-conformance-tier.generated.cjs +34 -5
  396. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  397. package/scripts/lib/vendor-bundle.cjs +59 -0
  398. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  399. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  400. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  401. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  402. package/scripts/lint-descriptions.cjs +7 -3
  403. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
  404. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  405. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  406. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  407. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  408. package/scripts/lint-phase-id-drift.cjs +29 -0
  409. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  410. package/scripts/lint-response-language-coverage.cjs +3 -0
  411. package/scripts/lint-skill-deps.cjs +7 -3
  412. package/scripts/lint-test-file-count.allowlist.json +11 -1
  413. package/scripts/lint-test-file-count.cjs +34 -1
  414. package/scripts/lint-vendored-deps.cjs +41 -5
  415. package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
  416. package/scripts/mutation-matrix.cjs +50 -5
  417. package/scripts/require-issue-link-policy.cjs +6 -2
  418. package/scripts/sync-runtime-launcher.cjs +184 -2
  419. package/scripts/verify-npm-publish.cjs +76 -20
  420. package/skills/gsd-add-tests/SKILL.md +6 -1
  421. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  422. package/skills/gsd-audit-fix/SKILL.md +5 -0
  423. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  424. package/skills/gsd-autonomous/SKILL.md +5 -0
  425. package/skills/gsd-capture/SKILL.md +8 -4
  426. package/skills/gsd-code-review/SKILL.md +7 -2
  427. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  428. package/skills/gsd-config/SKILL.md +8 -4
  429. package/skills/gsd-debug/SKILL.md +11 -7
  430. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  431. package/skills/gsd-docs-update/SKILL.md +12 -7
  432. package/skills/gsd-eval-review/SKILL.md +6 -1
  433. package/skills/gsd-execute-phase/SKILL.md +12 -7
  434. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  435. package/skills/gsd-fast/SKILL.md +4 -0
  436. package/skills/gsd-forensics/SKILL.md +5 -1
  437. package/skills/gsd-graphify/SKILL.md +10 -6
  438. package/skills/gsd-health/SKILL.md +5 -0
  439. package/skills/gsd-help/SKILL.md +7 -2
  440. package/skills/gsd-import/SKILL.md +7 -3
  441. package/skills/gsd-inbox/SKILL.md +5 -0
  442. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  443. package/skills/gsd-manager/SKILL.md +6 -1
  444. package/skills/gsd-map-codebase/SKILL.md +7 -3
  445. package/skills/gsd-mempalace-capture/SKILL.md +5 -1
  446. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  447. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  448. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  449. package/skills/gsd-new-milestone/SKILL.md +6 -1
  450. package/skills/gsd-new-project/SKILL.md +5 -0
  451. package/skills/gsd-next/SKILL.md +6 -1
  452. package/skills/gsd-ns-context/SKILL.md +4 -0
  453. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  454. package/skills/gsd-ns-manage/SKILL.md +4 -0
  455. package/skills/gsd-ns-project/SKILL.md +4 -0
  456. package/skills/gsd-ns-review/SKILL.md +4 -0
  457. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  458. package/skills/gsd-onboard/SKILL.md +6 -1
  459. package/skills/gsd-pause-work/SKILL.md +5 -1
  460. package/skills/gsd-phase/SKILL.md +8 -4
  461. package/skills/gsd-plan-phase/SKILL.md +6 -1
  462. package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
  463. package/skills/gsd-pr-branch/SKILL.md +4 -0
  464. package/skills/gsd-profile-user/SKILL.md +5 -1
  465. package/skills/gsd-progress/SKILL.md +6 -1
  466. package/skills/gsd-quick/SKILL.md +16 -10
  467. package/skills/gsd-quick-batch/SKILL.md +20 -8
  468. package/skills/gsd-review/SKILL.md +5 -1
  469. package/skills/gsd-review-backlog/SKILL.md +3 -2
  470. package/skills/gsd-secure-phase/SKILL.md +6 -1
  471. package/skills/gsd-ship/SKILL.md +5 -0
  472. package/skills/gsd-sketch/SKILL.md +7 -2
  473. package/skills/gsd-spec-phase/SKILL.md +5 -1
  474. package/skills/gsd-spike/SKILL.md +8 -3
  475. package/skills/gsd-surface/SKILL.md +5 -1
  476. package/skills/gsd-thread/SKILL.md +4 -0
  477. package/skills/gsd-ui-phase/SKILL.md +6 -1
  478. package/skills/gsd-ui-review/SKILL.md +6 -1
  479. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  480. package/skills/gsd-undo/SKILL.md +5 -1
  481. package/skills/gsd-update/SKILL.md +6 -2
  482. package/skills/gsd-validate-phase/SKILL.md +6 -1
  483. package/skills/gsd-verify-work/SKILL.md +6 -1
  484. package/skills/gsd-workspace/SKILL.md +7 -3
  485. package/skills/gsd-workstreams/SKILL.md +6 -6
  486. package/vscode/package.json +1 -1
  487. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
@@ -6,7 +6,7 @@
6
6
  * the legacy `## Task N` heading fallback — including the optional `tracker-id`
7
7
  * attribute, ADR-3646 Phase 1, read verbatim and never split here), planned-file
8
8
  * extraction, and the frontmatter-derived scheduling metadata (`wave`,
9
- * `depends_on`, `autonomous`, `agent_hint`, `files_modified`).
9
+ * `depends_on`, `autonomous`, `agent_hint`, `files_modified`, `gap_closure`).
10
10
  *
11
11
  * WHY THIS IS A LEAF MODULE. This logic was written inline inside
12
12
  * `cmdPhasePlanIndex` (`src/phase.cts`). Two commands in two different families
@@ -32,9 +32,23 @@
32
32
  * ADR-457 build-at-publish: source in src/plan-document.cts, compiled to
33
33
  * gsd-core/bin/lib/plan-document.cjs (gitignored).
34
34
  */
35
- // eslint-disable-next-line @typescript-eslint/no-require-imports
36
- const frontmatterMod = require("./frontmatter.cjs");
37
- const { extractFrontmatter } = frontmatterMod;
35
+ // #5026 / ADR-4910 §1 absorption: the 7 frontmatter-derived scheduling fields
36
+ // below, plus `objective` as an 8th (see `frontmatterField`'s own docblock),
37
+ // read through `planning-document.cts`'s seam
38
+ // (`readFrontmatterFieldsFromSource`) rather than calling `frontmatter.cts`'s
39
+ // `extractFrontmatter` directly. This module has no canonical `.planning/`-root
40
+ // artifact basename to gate `parsePlanningDoc` on (`*-PLAN.md` lives nested
41
+ // under `.planning/phase/*/plans/`, and two of the five real callers hold only
42
+ // in-memory content with no path at all), so it uses the entry point shaped
43
+ // for exactly that: content in hand, no filename, no other `PlanningDoc`
44
+ // capability (sections/tables/checklists) this module needs. The BULK form
45
+ // (`readFrontmatterFieldsFromSource`, not the single-key
46
+ // `readFrontmatterFieldFromSource`) is used deliberately: `parsePlanDocument`
47
+ // reads several keys off the SAME document, and the single-key entry point
48
+ // would independently re-detect the frontmatter span and re-parse the full
49
+ // YAML once per key. See both functions' docblocks in `planning-document.cts`
50
+ // for the full reasoning.
51
+ const planning_document_cjs_1 = require("./planning-document.cjs");
38
52
  // ─── Frozen vocabularies ──────────────────────────────────────────────────────
39
53
  /**
40
54
  * How a task row was expressed in the document. `auto` is the ordinary
@@ -203,23 +217,80 @@ function extractObjective(content) {
203
217
  function planIdFromFile(planFile) {
204
218
  return planFile.replace('-PLAN.md', '').replace('PLAN.md', '');
205
219
  }
220
+ /**
221
+ * The frontmatter keys `parsePlanDocument` reads: the 7 scheduling fields
222
+ * this absorption originally scoped (`wave`, `depends_on`, `autonomous`,
223
+ * `files_modified`/`files-modified`, `files_deleted`/`files-deleted`,
224
+ * `agent_hint`, `type` — 9 key spellings across those 7 fields), PLUS
225
+ * `objective` as an 8th field. `objective` is read through this SAME
226
+ * frontmatter-key-read path — not a deliberate exclusion, as an earlier
227
+ * revision of this comment claimed — because `parsePlanDocument`'s return
228
+ * statement below falls back to `frontmatterField(content, 'objective')`
229
+ * whenever the `<objective>` XML tag (`extractObjective`, unrelated and
230
+ * untouched by this absorption) is absent; it is included here simply
231
+ * because reading it is the exact same pattern as the other seven, and the
232
+ * migration below naturally covers it. `gap_closure` (#4924) is a 9th field,
233
+ * absorbed onto this same bulk-read seam by this fix rather than left on the
234
+ * removed `fm[key]` object-property read it was rebased in against.
235
+ */
236
+ const FRONTMATTER_READ_KEYS = [
237
+ 'wave',
238
+ 'depends_on',
239
+ 'autonomous',
240
+ 'files_modified',
241
+ 'files-modified',
242
+ 'files_deleted',
243
+ 'files-deleted',
244
+ 'agent_hint',
245
+ 'type',
246
+ 'objective',
247
+ 'gap_closure',
248
+ ];
249
+ /**
250
+ * Read one top-level frontmatter key out of an already-bulk-read
251
+ * `Record<string, NodeRead>` (`readFrontmatterFieldsFromSource`'s return
252
+ * value, computed ONCE per `parsePlanDocument` call — see
253
+ * `FRONTMATTER_READ_KEYS`), unwrapped to the SAME shape a direct `fm[key]`
254
+ * object-property read would give: the field's value, or `undefined` when it
255
+ * is absent, the document has no frontmatter, or the frontmatter is
256
+ * unparseable — `readFrontmatterFieldsFromSource` reports all three of those
257
+ * as an `ok: false` result per key, and `extractFrontmatter`'s own object
258
+ * would likewise simply lack the key in every one of those cases.
259
+ */
260
+ function frontmatterField(fields, key) {
261
+ const read = fields[key];
262
+ return read?.ok ? read.value : undefined;
263
+ }
206
264
  /**
207
265
  * Parse one plan document.
208
266
  *
209
267
  * @param content Raw `*-PLAN.md` text.
210
- * @param planPath Optional path, used only to name the file in `extractFrontmatter`'s
211
- * truncated-frontmatter diagnostic (#1882). Callers that do not have
212
- * one omit it — this default IS the shape production uses from the
213
- * read-only query path.
268
+ * @param planPath Historically the path passed to `extractFrontmatter`'s
269
+ * truncated-frontmatter diagnostic (#1882) — a stderr-only
270
+ * side channel, never part of this function's return value.
271
+ * #5026: the 9 frontmatter fields below (7 scheduling fields,
272
+ * plus `objective` and `gap_closure`) now read through
273
+ * `readFrontmatterFieldFromSource`, which has no path
274
+ * parameter, so this diagnostic's file-naming/dedup key is a
275
+ * documented, accepted side-effect-only regression (falls
276
+ * back to content-digest dedup, the same fallback
277
+ * `extractFrontmatter` already uses for the two real callers
278
+ * that never had a path to give it). Kept for call-site
279
+ * signature compatibility only.
214
280
  */
215
- function parsePlanDocument(content, planPath = '') {
216
- const fm = extractFrontmatter(content, planPath);
281
+ function parsePlanDocument(content, _planPath = '') {
217
282
  const xmlTasks = parseXmlTasks(content);
218
283
  const tasks = xmlTasks.length > 0 ? xmlTasks : parseMarkdownTasks(content);
219
- const parsedWave = parseInt(fm['wave'], 10);
284
+ // Detect the frontmatter span and parse its YAML ONCE (#5026 follow-up: the
285
+ // prior per-key `frontmatterField(content, key)` calls each independently
286
+ // re-detected the span and re-parsed the full YAML from scratch — 10x
287
+ // redundant detection+parse per call). Every `frontmatterField` read below
288
+ // shares this SAME parsed result.
289
+ const fields = (0, planning_document_cjs_1.readFrontmatterFieldsFromSource)(content, FRONTMATTER_READ_KEYS);
290
+ const parsedWave = parseInt(frontmatterField(fields, 'wave'), 10);
220
291
  const declaredWave = Number.isNaN(parsedWave) ? null : parsedWave;
221
292
  let dependsOn = [];
222
- const fmDeps = fm['depends_on'];
293
+ const fmDeps = frontmatterField(fields, 'depends_on');
223
294
  if (Array.isArray(fmDeps)) {
224
295
  dependsOn = fmDeps.map(String);
225
296
  }
@@ -227,37 +298,38 @@ function parsePlanDocument(content, planPath = '') {
227
298
  dependsOn = [fmDeps];
228
299
  }
229
300
  let autonomous = true;
230
- if (fm['autonomous'] !== undefined) {
301
+ const fmAutonomous = frontmatterField(fields, 'autonomous');
302
+ if (fmAutonomous !== undefined) {
231
303
  // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue comparison
232
- autonomous = fm['autonomous'] === 'true' || String(fm['autonomous']) === 'true';
304
+ autonomous = fmAutonomous === 'true' || String(fmAutonomous) === 'true';
233
305
  }
234
306
  let filesModified = [];
235
- const fmFiles = fm['files_modified'] || fm['files-modified'];
307
+ const fmFiles = frontmatterField(fields, 'files_modified') || frontmatterField(fields, 'files-modified');
236
308
  if (fmFiles) {
237
309
  // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
238
310
  filesModified = Array.isArray(fmFiles) ? fmFiles.map(String) : [String(fmFiles)];
239
311
  }
240
312
  let filesDeleted = [];
241
- const fmDeleted = fm['files_deleted'] || fm['files-deleted'];
313
+ const fmDeleted = frontmatterField(fields, 'files_deleted') || frontmatterField(fields, 'files-deleted');
242
314
  if (fmDeleted) {
243
315
  // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
244
316
  filesDeleted = Array.isArray(fmDeleted) ? fmDeleted.map(String) : [String(fmDeleted)];
245
317
  }
246
318
  let agentHint = null;
247
- const fmAgentHint = fm['agent_hint'];
319
+ const fmAgentHint = frontmatterField(fields, 'agent_hint');
248
320
  if (fmAgentHint !== undefined) {
249
321
  // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
250
322
  const hintStr = String(fmAgentHint).trim();
251
323
  agentHint = hintStr !== '' ? hintStr : null;
252
324
  }
253
325
  let planType = null;
254
- const fmType = fm['type'];
326
+ const fmType = frontmatterField(fields, 'type');
255
327
  if (fmType !== undefined) {
256
328
  // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
257
329
  planType = String(fmType);
258
330
  }
259
331
  return {
260
- objective: extractObjective(content) || fm['objective'] || null,
332
+ objective: extractObjective(content) || frontmatterField(fields, 'objective') || null,
261
333
  type: planType,
262
334
  declaredWave,
263
335
  dependsOn,
@@ -265,6 +337,8 @@ function parsePlanDocument(content, planPath = '') {
265
337
  agentHint,
266
338
  filesModified,
267
339
  filesDeleted,
340
+ // extractFrontmatter yields every scalar as a string, so YAML `true` is 'true'.
341
+ gapClosure: frontmatterField(fields, 'gap_closure') === 'true',
268
342
  tasks,
269
343
  taskCount: tasks.length,
270
344
  };
@@ -137,6 +137,11 @@ const PHASE_STATUS_RANKS = Object.freeze({
137
137
  'phase complete': 2,
138
138
  // ROADMAP.md "## Progress" table Status column vocabulary
139
139
  'not started': 0,
140
+ // #5060: `roadmap update-plan-progress`'s own written token (Phase Status
141
+ // Module's `toRoadmapStatusCell`) for a phase with plans but no summaries
142
+ // yet — rank 1, the same "work has started" rank as STATE's "ready to
143
+ // execute" / "in progress".
144
+ 'planned': 1,
140
145
  'complete': 2,
141
146
  'deferred': 0,
142
147
  });
@@ -34,16 +34,21 @@ exports.PLANNING_ARTIFACTS = void 0;
34
34
  exports.parsePlanningDoc = parsePlanningDoc;
35
35
  exports.findField = findField;
36
36
  exports.readNode = readNode;
37
+ exports.readFrontmatterField = readFrontmatterField;
38
+ exports.readFrontmatterFieldFromSource = readFrontmatterFieldFromSource;
39
+ exports.readFrontmatterFieldsFromSource = readFrontmatterFieldsFromSource;
37
40
  exports.setFieldValue = setFieldValue;
38
41
  exports.hasUnreadableNodes = hasUnreadableNodes;
39
42
  exports.serialize = serialize;
43
+ exports.replaceProse = replaceProse;
40
44
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
41
45
  const markdown_table_cjs_1 = require("./markdown-table.cjs");
42
46
  const artifacts_cjs_1 = require("./artifacts.cjs");
43
47
  // `frontmatter.cts` uses `export =` (CJS-style single export object), so it
44
48
  // is imported as a default import (esModuleInterop), not a named import.
45
49
  const frontmatter_cjs_1 = __importDefault(require("./frontmatter.cjs"));
46
- const { frontmatterRegion } = frontmatter_cjs_1.default;
50
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
51
+ const { extractFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatter_cjs_1.default;
47
52
  /**
48
53
  * Canonical `.planning/` root artifact basenames this seam recognises,
49
54
  * derived from the SAME registry `isCanonicalPlanningFile` consults
@@ -79,46 +84,28 @@ function splitLinesInfo(source) {
79
84
  return out;
80
85
  }
81
86
  /**
82
- * Locate the frontmatter block, if any, by COMPOSING `frontmatter.cts`'s
83
- * `frontmatterRegion` — the fence-detection grammar (byte-0 rule, BOM strip,
84
- * `\n---` search, CR handling) lives there, once, and this seam never
85
- * re-derives it (ADR-4910 Decision 1).
87
+ * Locate the frontmatter block, if any, from `locateFrontmatterFence` — the
88
+ * one owner of the fence grammar (byte-0 rule, BOM tolerance, whole-line
89
+ * closing fence, an adjacent empty block), which every frontmatter reader and
90
+ * writer also reads; this seam never re-derives it (ADR-4910 Decision 1).
86
91
  *
87
- * `frontmatterRegion` reports the YAML body's own bounds (`region`,
88
- * `terminated`, and the possibly BOM-stripped `content`), not this seam's
89
- * `Span` shape (an absolute byte range into the UNSTRIPPED `source`,
90
- * inclusive of both fences). This adapter translates one into the other by
91
- * reading ONLY the two boundary characters `frontmatterRegion` already
92
- * anchored (whether the YAML end / closing fence sit on a CRLF line) — it
93
- * does not re-scan for the fences themselves.
92
+ * The span is an absolute range into `source`, from the opening fence (after
93
+ * any BOM) through the closing fence line's text, plus that line's CR when it
94
+ * ends in CRLF. Found while implementing #5105: the previous adapter re-derived
95
+ * the closing fence's end from the YAML region's length and was one character
96
+ * long on an adjacent empty block (`---\n---\n`), which has no line of its own
97
+ * before the closer.
94
98
  */
95
99
  function findFrontmatterSpan(source) {
96
- const found = frontmatterRegion(source);
97
- if (!found)
100
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(source);
101
+ if (!fence)
98
102
  return null;
99
- // `found.content` may be `source` with a single leading BOM stripped;
100
- // every offset below is relative to `found.content`, so translate back to
101
- // `source` coordinates by the same delta.
102
- const bomDelta = source.length - found.content.length;
103
- const content = found.content;
104
- if (!found.terminated) {
105
- return { span: { start: bomDelta, end: bomDelta + content.length }, terminated: false };
103
+ const start = fence.bom.length;
104
+ if (!fence.closed) {
105
+ return { span: { start, end: source.length }, terminated: false };
106
106
  }
107
- // `frontmatterRegion` already did fence DETECTION — `found` being non-null
108
- // and `terminated` IS that result. It reports only the YAML body's bounds
109
- // (`region`), not an absolute span, so recover the closing fence's end
110
- // from `region`'s length. The one thing still read directly here is the
111
- // opening fence's fixed-width line ending (`\n` vs `\r\n`), needed to
112
- // translate `region`'s length into a `content` offset — not a re-scan for
113
- // the fence itself.
114
- const headerEnd = content.startsWith('---\r\n') ? 5 : 4;
115
- const yamlEnd = headerEnd + found.region.length;
116
- const closingLineStart = content[yamlEnd] === '\r' ? yamlEnd + 1 : yamlEnd;
117
- const fenceLineStart = closingLineStart + 1;
118
- let fenceEnd = fenceLineStart + 3;
119
- if (content[fenceEnd] === '\r')
120
- fenceEnd += 1;
121
- return { span: { start: bomDelta, end: bomDelta + fenceEnd }, terminated: true };
107
+ const end = fence.closingFenceEnd + (source[fence.closingFenceEnd] === '\r' ? 1 : 0);
108
+ return { span: { start, end }, terminated: true };
122
109
  }
123
110
  /** Build the set of 0-based line indices that fall inside a fenced code
124
111
  * block (opening/closing delimiter lines included), so `**Label:**`/table/
@@ -311,7 +298,10 @@ function parsePlanningDoc(source, artifact) {
311
298
  frontmatterEnd = fm.span.end;
312
299
  }
313
300
  if (source.length === 0) {
314
- return { ok: true, value: { source, artifact, nodes: [], staged: new Map() } };
301
+ return {
302
+ ok: true,
303
+ value: { source, artifact, nodes: [], staged: new Map() },
304
+ };
315
305
  }
316
306
  const lines = splitLinesInfo(source);
317
307
  // Sections: one per heading, in document order — every heading is its own
@@ -333,7 +323,10 @@ function parsePlanningDoc(source, artifact) {
333
323
  }
334
324
  nodes.push(...scanBodyNodes(source, lines, frontmatterEnd));
335
325
  nodes.sort((a, b) => a.span.start - b.span.start);
336
- return { ok: true, value: { source, artifact, nodes, staged: new Map() } };
326
+ return {
327
+ ok: true,
328
+ value: { source, artifact, nodes, staged: new Map() },
329
+ };
337
330
  }
338
331
  /** Find the id of the (first, document-order) `boldField` node whose label
339
332
  * exactly matches `label`, or `null` when none does. */
@@ -359,11 +352,206 @@ function readNode(doc, id) {
359
352
  }
360
353
  return { ok: true, value: doc.source.slice(node.span.start, node.span.end) };
361
354
  }
355
+ /**
356
+ * Parse a frontmatter block's own span TEXT (fences included) via
357
+ * `frontmatter.cts`'s `extractFrontmatter`, exactly ONCE, returning either the
358
+ * parsed object or a `{ ok: false }` marker for the `FRONTMATTER_UNPARSEABLE`
359
+ * case. Split out of the single-key lookup below (#5026 follow-up) so a
360
+ * caller reading MULTIPLE keys off the SAME region
361
+ * (`readFrontmatterFieldsFromSource`) detects the span and parses its YAML
362
+ * once, not once per key — this function is the one and only place that
363
+ * detect-then-parse step happens; every reader (single-key or bulk) composes
364
+ * it rather than re-deriving it.
365
+ */
366
+ function parseFrontmatterRegion(regionText) {
367
+ // A CRLF block's span ends on its closing fence line's CR (`findFrontmatterSpan`). That CR is
368
+ // half a line ending, not part of the fence: a `---` line ended by a lone CR does not close a
369
+ // block, so the text is parsed without it.
370
+ const fm = extractFrontmatter(regionText.endsWith('\r') ? regionText.slice(0, -1) : regionText);
371
+ if (fm[FRONTMATTER_UNPARSEABLE] === true) {
372
+ return { ok: false };
373
+ }
374
+ return { ok: true, value: fm };
375
+ }
376
+ /**
377
+ * Shared core of `readFrontmatterField` / `readFrontmatterFieldFromSource` /
378
+ * `readFrontmatterFieldsFromSource`: given an ALREADY-PARSED frontmatter
379
+ * result (`parseFrontmatterRegion`'s output) and the `Span` to report on
380
+ * failure, shape one `key`'s lookup as a `NodeRead`. Every reader composes
381
+ * this SAME shaping step — neither may duplicate it (the exact
382
+ * `DEFECT.GENERATIVE-FIX` class this epic exists to close).
383
+ *
384
+ * A region that failed to parse as YAML (`extractFrontmatter` reports this by
385
+ * returning `{}` carrying the `FRONTMATTER_UNPARSEABLE` Symbol, per that
386
+ * module's own documented contract) → `{ ok: false, reason:
387
+ * 'unparseable-frontmatter', span }`. A parseable region whose key is simply
388
+ * absent → `{ ok: false, reason: 'field-not-found', span }` —
389
+ * `extractFrontmatter` itself has no notion of "field not found" (a missing
390
+ * key just reads `undefined` off its returned object), so this reason string
391
+ * is this seam's own, not a passthrough of an upstream contract.
392
+ */
393
+ function shapeFrontmatterField(parsed, span, key) {
394
+ if (!parsed.ok) {
395
+ return { ok: false, reason: 'unparseable-frontmatter', span };
396
+ }
397
+ const value = parsed.value[key];
398
+ if (value === undefined) {
399
+ return { ok: false, reason: 'field-not-found', span };
400
+ }
401
+ return { ok: true, value };
402
+ }
403
+ /** Single-key lookup: parse `regionText` once (`parseFrontmatterRegion`) and
404
+ * shape `key`'s result (`shapeFrontmatterField`). Used by `readFrontmatterField`
405
+ * and `readFrontmatterFieldFromSource`, which each already have the region's
406
+ * own text and span in hand; a caller reading several keys off one region
407
+ * should use `readFrontmatterFieldsFromSource` instead, to avoid re-parsing
408
+ * once per key. */
409
+ function lookupFrontmatterField(regionText, span, key) {
410
+ return shapeFrontmatterField(parseFrontmatterRegion(regionText), span, key);
411
+ }
412
+ /**
413
+ * Read one top-level frontmatter key off an already-parsed `PlanningDoc`,
414
+ * composing `frontmatter.cts`'s `extractFrontmatter` rather than
415
+ * reimplementing YAML parsing (ADR-4910 Decision 1's precedent — the same
416
+ * composition `frontmatterRegion` already uses).
417
+ *
418
+ * No `FrontmatterNode` in `doc.nodes` (there is at most one per document) →
419
+ * `{ ok: false, reason: 'no-frontmatter', span: {0,0} }`, mirroring
420
+ * `findField`'s own not-found span convention (`readNode`'s unknown-id case).
421
+ *
422
+ * A `FrontmatterNode` is only ever pushed onto `doc.nodes` when its fence was
423
+ * terminated (`parsePlanningDoc` fails the whole document, before any node
424
+ * exists, on an unterminated fence) — so the "opened but never closed" case
425
+ * `extractFrontmatter` also handles is never reachable from an already-parsed
426
+ * `PlanningDoc` through this function. See `lookupFrontmatterField` for the
427
+ * unparseable-frontmatter / field-not-found / present result shapes.
428
+ */
429
+ function readFrontmatterField(doc, key) {
430
+ const node = doc.nodes.find((n) => n.kind === 'frontmatter');
431
+ if (!node) {
432
+ return { ok: false, reason: 'no-frontmatter', span: { start: 0, end: 0 } };
433
+ }
434
+ const raw = doc.source.slice(node.span.start, node.span.end);
435
+ return lookupFrontmatterField(raw, node.span, key);
436
+ }
437
+ /**
438
+ * Read one top-level frontmatter key directly off raw `source` text, with no
439
+ * `PlanningDoc`/artifact-kind gate involved (#5026). `parsePlanningDoc`'s
440
+ * artifact-kind gate exists to distinguish "this document records nothing"
441
+ * from "wrong kind entirely" for a caller that might hand it any
442
+ * `.planning/`-root file, including a non-markdown one (config.json,
443
+ * state.json, …) — a risk that does not exist for a caller (`plan-document.
444
+ * cts`) that is ONLY ever invoked on real `*-PLAN.md` content, never on
445
+ * anything else, and is never given a canonical root-artifact basename to
446
+ * gate on in the first place (`*-PLAN.md` lives nested under
447
+ * `.planning/phase/*\/plans/`, never at the `.planning/` root
448
+ * `PLANNING_ARTIFACTS` enumerates — confirmed by execution:
449
+ * `isCanonicalPlanningFile('01-PLAN.md')` is `false`). This entry point
450
+ * routes around that gate rather than through it, for exactly that caller
451
+ * shape: content in hand, no filename to check, no need for any other
452
+ * `PlanningDoc` capability (sections/tables/checklists) this seam offers.
453
+ *
454
+ * Locates the frontmatter span via `findFrontmatterSpan` — the SAME
455
+ * `locateFrontmatterFence`-reading helper `parsePlanningDoc` itself uses to
456
+ * build a `FrontmatterNode` — so this is not a second detection mechanism,
457
+ * only a bypass of the node-parsing pipeline neither this caller nor its
458
+ * content needs.
459
+ *
460
+ * No frontmatter fence at byte 0 at all → `{ ok: false, reason:
461
+ * 'no-frontmatter', span: {0,0} }`, the same shape `readFrontmatterField`
462
+ * returns for its own not-found case. An OPENED-but-never-closed fence is
463
+ * reachable here (unlike `readFrontmatterField`, which can only ever see an
464
+ * already-terminated frontmatter node): `extractFrontmatter` treats that
465
+ * region as if it had none (`{}`, no `FRONTMATTER_UNPARSEABLE` marker), so
466
+ * every key on it comes back `field-not-found` — matching exactly what a
467
+ * direct `extractFrontmatter(source)[key] === undefined` check already
468
+ * produces for that same input today. See `lookupFrontmatterField` for the
469
+ * unparseable-frontmatter / field-not-found / present result shapes.
470
+ */
471
+ function readFrontmatterFieldFromSource(source, key) {
472
+ const found = findFrontmatterSpan(source);
473
+ if (!found) {
474
+ return { ok: false, reason: 'no-frontmatter', span: { start: 0, end: 0 } };
475
+ }
476
+ const raw = source.slice(found.span.start, found.span.end);
477
+ return lookupFrontmatterField(raw, found.span, key);
478
+ }
479
+ /**
480
+ * Read MULTIPLE top-level frontmatter keys off raw `source` text in ONE pass
481
+ * — the bulk sibling of `readFrontmatterFieldFromSource`, for a caller (added
482
+ * for `plan-document.cts`'s `parsePlanDocument`, #5026 follow-up) that needs
483
+ * several keys off the SAME document. Calling `readFrontmatterFieldFromSource`
484
+ * once per key each independently re-detects the frontmatter span AND
485
+ * re-parses the full frontmatter YAML from scratch (`findFrontmatterSpan` +
486
+ * `parseFrontmatterRegion`, both non-trivial scans) — this function detects
487
+ * the span and parses the YAML exactly ONCE, then shapes every requested key
488
+ * off that SAME parsed result.
489
+ *
490
+ * This is a second ENTRY POINT into the one shared detect-span +
491
+ * parse-YAML + shape-a-key pipeline (`findFrontmatterSpan` /
492
+ * `parseFrontmatterRegion` / `shapeFrontmatterField`), never a second parser:
493
+ * per-key result shapes are byte-identical to calling
494
+ * `readFrontmatterFieldFromSource` once per key (same `no-frontmatter` /
495
+ * `unparseable-frontmatter` / `field-not-found` / present shapes, same span).
496
+ */
497
+ function readFrontmatterFieldsFromSource(source, keys) {
498
+ const found = findFrontmatterSpan(source);
499
+ const out = {};
500
+ if (!found) {
501
+ const span = { start: 0, end: 0 };
502
+ for (const key of keys)
503
+ out[key] = { ok: false, reason: 'no-frontmatter', span };
504
+ return out;
505
+ }
506
+ const raw = source.slice(found.span.start, found.span.end);
507
+ const parsed = parseFrontmatterRegion(raw);
508
+ for (const key of keys)
509
+ out[key] = shapeFrontmatterField(parsed, found.span, key);
510
+ return out;
511
+ }
362
512
  /**
363
513
  * Stage a new value for a `boldField` node, returning a NEW `PlanningDoc`
364
514
  * (immutable — `doc` itself is never mutated). Refuses an id this doc did
365
- * not mint, and refuses any node kind other than `boldField` — only the
515
+ * not mint, and refuses any node kind other than `boldField` — only
366
516
  * `valueSpan` is ever writable this phase (ADR-4910 §1).
517
+ *
518
+ * #5007 / ADR-4910 Phase 6: a prior amendment here added a `{ allowSeparator:
519
+ * true }` escape hatch that spliced the caller's value across the FULL
520
+ * rest-of-line span (`valueSpan.start`..`trailingSpan.end`) to let a value
521
+ * legitimately containing the grammar's ` — ` trailing-separator token
522
+ * (`TRAILING_SEPARATOR_RE`) be written without triggering the round-trip
523
+ * refusal below. That option was REMOVED (still #5007, same phase) after a
524
+ * failing-first reproduction proved it only avoided the refusal AT WRITE
525
+ * TIME: the written bytes are correct, but `parseBoldFieldLine` splits on
526
+ * ` — ` unconditionally and without any escaping/metadata to distinguish
527
+ * "atomic value containing the token" from "value plus hand-annotation" —
528
+ * the same input shape either way. So the NEXT fresh `parsePlanningDoc` of
529
+ * that exact text (not the in-memory `doc` the option's own tests checked)
530
+ * silently re-truncates the value and demotes the rest to `trailingSpan`,
531
+ * with `findField`/`readNode` reporting a confident, wrong `ok: true`
532
+ * result and no error — reproduced live: staging `"Phase — COMPLETE"` this
533
+ * way, serializing, and re-parsing the output through a fresh
534
+ * `parsePlanningDoc` read back `"Phase"` via `readNode`, silently losing
535
+ * ` — COMPLETE`. This is exactly the #4917 finding-2 corruption this
536
+ * module's round-trip check exists to prevent, just moved one parse cycle
537
+ * downstream of where the check could still catch it. There is no escaping
538
+ * convention anywhere in this grammar (`BOLD_FIELD_RE`/`TRAILING_SEPARATOR_
539
+ * RE` are unconditional, unversioned regexes with no metadata channel), and
540
+ * `TRAILING_SEPARATOR_RE`'s split is relied on by every other reader of this
541
+ * seam (`findField`/`readNode`, used today for ROADMAP.md's `Plans`/
542
+ * `Depends on` fields) — narrowing or version-gating it here would be a
543
+ * grammar change with its own blast radius, not a local bug fix. Widening
544
+ * `setFieldValue`'s PUBLIC, shared contract to include a write path that is
545
+ * only safe for a caller who never reads the field back through this same
546
+ * module is an attractive nuisance: nothing stops a future `findField`/
547
+ * `readNode` caller from reaching for it and hitting this exact corruption.
548
+ * The one real caller (`stateReplaceField`, src/state-document.cts) never
549
+ * reads STATE.md fields back through `parsePlanningDoc`/`findField` (it uses
550
+ * `stateExtractField`'s own non-splitting regex instead), so it does its own
551
+ * local full-rest-of-line splice directly against `content`, using this
552
+ * module only to LOCATE the field's spans — keeping the dangerous affordance
553
+ * out of this shared seam's public surface entirely, rather than fixing it
554
+ * with a narrower version of the same false-safety option.
367
555
  */
368
556
  function setFieldValue(doc, id, value) {
369
557
  const node = doc.nodes.find((n) => n.id === id);
@@ -411,7 +599,10 @@ function setFieldValue(doc, id, value) {
411
599
  }
412
600
  const staged = new Map(doc.staged);
413
601
  staged.set(id, value);
414
- return { ok: true, value: { source: doc.source, artifact: doc.artifact, nodes: doc.nodes, staged } };
602
+ return {
603
+ ok: true,
604
+ value: { source: doc.source, artifact: doc.artifact, nodes: doc.nodes, staged },
605
+ };
415
606
  }
416
607
  /** True when any node in `doc` failed to parse. */
417
608
  function hasUnreadableNodes(doc) {
@@ -455,5 +646,47 @@ function serialize(doc) {
455
646
  out += doc.source.slice(cursor);
456
647
  return { ok: true, value: out };
457
648
  }
649
+ /**
650
+ * Replace every occurrence of the single-line literal `from` with `to` in the
651
+ * document's prose — the seam's answer to a verbatim cross-reference rewrite
652
+ * (ADR-5057 §6, Phase 13, #5217: the migration's "Phase 1:" -> "Phase 1-01:"
653
+ * substitution in PROJECT.md / STATE.md).
654
+ *
655
+ * Fenced code blocks are never rewritten (rows 9/14: fenced content is not a
656
+ * node), every other byte — each line's own terminator included — is copied
657
+ * from `doc.source`, and a substitution that changes nothing returns a doc
658
+ * whose `source` is byte-identical. The result is re-parsed so its node spans
659
+ * describe the new text. Refuses (Result failure, nothing written) when `from`
660
+ * is empty or spans a line break, or when `doc` already carries staged
661
+ * field edits (their spans address the pre-substitution text).
662
+ */
663
+ function replaceProse(doc, from, to) {
664
+ if (typeof from !== 'string' || from.length === 0) {
665
+ return { ok: false, reason: 'replaceProse: `from` must be a non-empty string' };
666
+ }
667
+ if (typeof to !== 'string' || /[\r\n]/.test(from) || /[\r\n]/.test(to)) {
668
+ return { ok: false, reason: 'replaceProse: `from` and `to` must be single-line strings' };
669
+ }
670
+ if (doc.staged.size > 0) {
671
+ return { ok: false, reason: 'replaceProse: refused while field edits are staged' };
672
+ }
673
+ const lines = splitLinesInfo(doc.source);
674
+ const fenced = fencedLineIndices(lines);
675
+ let out = '';
676
+ let cursor = 0;
677
+ let changed = false;
678
+ for (let i = 0; i < lines.length; i++) {
679
+ const line = lines[i];
680
+ if (fenced.has(i) || !line.text.includes(from))
681
+ continue;
682
+ out += doc.source.slice(cursor, line.start) + line.text.split(from).join(to);
683
+ cursor = line.end;
684
+ changed = true;
685
+ }
686
+ if (!changed)
687
+ return { ok: true, value: doc };
688
+ out += doc.source.slice(cursor);
689
+ return parsePlanningDoc(out, doc.artifact);
690
+ }
458
691
  // Consumers: require('../gsd-core/bin/lib/planning-document.cjs')
459
692
  // Named CJS exports are the canonical surface (ADR-457 .cts → .cjs build-at-publish).