@opengsd/gsd-core 1.14.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 (551) 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/README.ja-JP.md +3 -3
  5. package/README.ko-KR.md +3 -3
  6. package/README.pt-BR.md +3 -3
  7. package/README.zh-CN.md +3 -3
  8. package/agents/gsd-code-fixer.compact.md +7 -6
  9. package/agents/gsd-code-fixer.md +9 -8
  10. package/agents/gsd-code-reviewer.compact.md +5 -3
  11. package/agents/gsd-code-reviewer.md +8 -6
  12. package/agents/gsd-debug-session-manager.compact.md +17 -2
  13. package/agents/gsd-debug-session-manager.md +17 -2
  14. package/agents/gsd-debugger.md +3 -3
  15. package/agents/gsd-eval-auditor.compact.md +1 -1
  16. package/agents/gsd-eval-auditor.md +1 -1
  17. package/agents/gsd-executor.md +17 -12
  18. package/agents/gsd-intel-updater.compact.md +1 -1
  19. package/agents/gsd-intel-updater.md +1 -1
  20. package/agents/gsd-mempalace-curator.md +2 -2
  21. package/agents/gsd-phase-researcher.md +19 -11
  22. package/agents/gsd-plan-checker.md +15 -9
  23. package/agents/gsd-planner.md +15 -11
  24. package/agents/gsd-project-researcher.compact.md +1 -1
  25. package/agents/gsd-project-researcher.md +1 -1
  26. package/agents/gsd-research-synthesizer.compact.md +1 -1
  27. package/agents/gsd-research-synthesizer.md +1 -1
  28. package/agents/gsd-ui-auditor.compact.md +21 -30
  29. package/agents/gsd-ui-auditor.md +166 -37
  30. package/agents/gsd-ui-researcher.compact.md +1 -1
  31. package/agents/gsd-ui-researcher.md +1 -1
  32. package/agents/gsd-verifier.md +37 -14
  33. package/bin/install.js +764 -287
  34. package/commands/gsd/add-tests.md +6 -1
  35. package/commands/gsd/ai-integration-phase.md +6 -1
  36. package/commands/gsd/audit-fix.md +5 -0
  37. package/commands/gsd/audit-milestone.md +6 -1
  38. package/commands/gsd/autonomous.md +7 -2
  39. package/commands/gsd/capture.md +9 -5
  40. package/commands/gsd/code-review.md +7 -2
  41. package/commands/gsd/complete-milestone.md +4 -0
  42. package/commands/gsd/config.md +7 -3
  43. package/commands/gsd/debug.md +11 -7
  44. package/commands/gsd/discuss-phase.md +7 -3
  45. package/commands/gsd/docs-update.md +12 -7
  46. package/commands/gsd/eval-review.md +6 -1
  47. package/commands/gsd/execute-phase.md +12 -7
  48. package/commands/gsd/extract-learnings.md +5 -0
  49. package/commands/gsd/fast.md +4 -0
  50. package/commands/gsd/forensics.md +5 -1
  51. package/commands/gsd/graphify.md +10 -6
  52. package/commands/gsd/health.md +5 -0
  53. package/commands/gsd/help.md +7 -2
  54. package/commands/gsd/import.md +7 -3
  55. package/commands/gsd/inbox.md +5 -0
  56. package/commands/gsd/ingest-docs.md +5 -1
  57. package/commands/gsd/manager.md +6 -1
  58. package/commands/gsd/map-codebase.md +7 -3
  59. package/commands/gsd/mempalace-capture.md +12 -4
  60. package/commands/gsd/mempalace-recall.md +5 -1
  61. package/commands/gsd/milestone-summary.md +5 -1
  62. package/commands/gsd/mvp-phase.md +8 -3
  63. package/commands/gsd/new-milestone.md +6 -1
  64. package/commands/gsd/new-project.md +5 -0
  65. package/commands/gsd/next.md +6 -1
  66. package/commands/gsd/ns-context.md +4 -0
  67. package/commands/gsd/ns-ideate.md +4 -0
  68. package/commands/gsd/ns-manage.md +4 -0
  69. package/commands/gsd/ns-project.md +4 -0
  70. package/commands/gsd/ns-review.md +4 -0
  71. package/commands/gsd/ns-workflow.md +4 -0
  72. package/commands/gsd/onboard.md +6 -1
  73. package/commands/gsd/pause-work.md +5 -1
  74. package/commands/gsd/phase.md +8 -4
  75. package/commands/gsd/plan-phase.md +6 -1
  76. package/commands/gsd/plan-review-convergence.md +11 -7
  77. package/commands/gsd/pr-branch.md +4 -0
  78. package/commands/gsd/profile-user.md +5 -1
  79. package/commands/gsd/progress.md +7 -2
  80. package/commands/gsd/quick-batch.md +21 -9
  81. package/commands/gsd/quick.md +12 -7
  82. package/commands/gsd/review.md +7 -4
  83. package/commands/gsd/secure-phase.md +6 -1
  84. package/commands/gsd/ship.md +5 -0
  85. package/commands/gsd/sketch.md +7 -2
  86. package/commands/gsd/spec-phase.md +5 -1
  87. package/commands/gsd/spike.md +8 -3
  88. package/commands/gsd/surface.md +5 -1
  89. package/commands/gsd/thread.md +4 -0
  90. package/commands/gsd/ui-phase.md +6 -1
  91. package/commands/gsd/ui-review.md +6 -1
  92. package/commands/gsd/ultraplan-phase.md +5 -1
  93. package/commands/gsd/undo.md +5 -1
  94. package/commands/gsd/update.md +6 -2
  95. package/commands/gsd/validate-phase.md +6 -1
  96. package/commands/gsd/verify-work.md +6 -1
  97. package/commands/gsd/workspace.md +7 -3
  98. package/gsd-core/bin/gsd-tools.cjs +477 -78
  99. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  100. package/gsd-core/bin/lib/adr-parser.cjs +3 -1
  101. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  102. package/gsd-core/bin/lib/audit.cjs +144 -42
  103. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  104. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  105. package/gsd-core/bin/lib/capability-registry.cjs +197 -222
  106. package/gsd-core/bin/lib/capability-validator.cjs +16 -1
  107. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  108. package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
  109. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  110. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  111. package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
  112. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  113. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  114. package/gsd-core/bin/lib/commands.cjs +343 -207
  115. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  116. package/gsd-core/bin/lib/config-loader.cjs +65 -4
  117. package/gsd-core/bin/lib/config.cjs +76 -19
  118. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  119. package/gsd-core/bin/lib/coverage.cjs +4 -8
  120. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  121. package/gsd-core/bin/lib/decisions.cjs +30 -14
  122. package/gsd-core/bin/lib/drift.cjs +177 -42
  123. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  124. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  125. package/gsd-core/bin/lib/frontmatter.cjs +426 -234
  126. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  127. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  128. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  129. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  130. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  131. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  132. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  133. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  134. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  135. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  136. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  137. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  138. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  139. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  140. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  141. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  142. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  143. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  144. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  145. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  146. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  147. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  148. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  149. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  150. package/gsd-core/bin/lib/graphify.cjs +10 -2
  151. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  152. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  153. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  154. package/gsd-core/bin/lib/init.cjs +364 -132
  155. package/gsd-core/bin/lib/install-engine.cjs +30 -33
  156. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  157. package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
  158. package/gsd-core/bin/lib/io.cjs +122 -3
  159. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  160. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  161. package/gsd-core/bin/lib/milestone.cjs +37 -6
  162. package/gsd-core/bin/lib/model-resolver.cjs +171 -62
  163. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  164. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  165. package/gsd-core/bin/lib/pattern.cjs +10 -0
  166. package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
  167. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  168. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  169. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  170. package/gsd-core/bin/lib/phase-id.cjs +110 -8
  171. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  172. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  173. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  174. package/gsd-core/bin/lib/phase.cjs +489 -88
  175. package/gsd-core/bin/lib/plan-document.cjs +142 -20
  176. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  177. package/gsd-core/bin/lib/planning-document.cjs +692 -0
  178. package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
  179. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  180. package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
  181. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  182. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  183. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  184. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  185. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  186. package/gsd-core/bin/lib/project-root.cjs +41 -2
  187. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  188. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  189. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  190. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  191. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  192. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  193. package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
  194. package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
  195. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
  196. package/gsd-core/bin/lib/roadmap.cjs +405 -88
  197. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
  198. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
  199. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  200. package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
  201. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
  202. package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
  203. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  204. package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
  205. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  206. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  207. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  208. package/gsd-core/bin/lib/state-document.cjs +100 -22
  209. package/gsd-core/bin/lib/state-transition.cjs +39 -2
  210. package/gsd-core/bin/lib/state.cjs +256 -105
  211. package/gsd-core/bin/lib/surface.cjs +19 -2
  212. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
  213. package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
  214. package/gsd-core/bin/lib/uat.cjs +432 -7
  215. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  216. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
  217. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  218. package/gsd-core/bin/lib/vendor/README.md +31 -9
  219. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  220. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  221. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  222. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  223. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  224. package/gsd-core/bin/lib/verification.cjs +1378 -274
  225. package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
  226. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  227. package/gsd-core/bin/lib/verify.cjs +357 -531
  228. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  229. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  230. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  231. package/gsd-core/bin/lib/workstream.cjs +11 -2
  232. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  233. package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
  234. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
  235. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  236. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  237. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  238. package/gsd-core/references/checkpoints.md +5 -3
  239. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  240. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  241. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  242. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  243. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  244. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  245. package/gsd-core/references/edge-probe.md +195 -21
  246. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  247. package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
  248. package/gsd-core/references/execute-phase-response-language.md +1 -1
  249. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  250. package/gsd-core/references/gsd-run-resolver.md +1 -1
  251. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  252. package/gsd-core/references/model-profiles.md +1 -1
  253. package/gsd-core/references/offer-next.md +1 -1
  254. package/gsd-core/references/phase-argument-parsing.md +9 -7
  255. package/gsd-core/references/phase-id-convention.md +28 -0
  256. package/gsd-core/references/planner-gap-closure.md +2 -0
  257. package/gsd-core/references/planner-load-graph-context.md +24 -13
  258. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  259. package/gsd-core/references/planning-config.md +12 -3
  260. package/gsd-core/references/spidr-splitting.md +1 -1
  261. package/gsd-core/references/tdd.md +37 -8
  262. package/gsd-core/references/ui-consideration-probe.md +10 -5
  263. package/gsd-core/references/verifier-phase-gates.md +5 -2
  264. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  265. package/gsd-core/references/verify-mvp-mode.md +2 -2
  266. package/gsd-core/references/workstream-flag.md +33 -3
  267. package/gsd-core/references/worktree-path-safety.md +321 -0
  268. package/gsd-core/templates/README.md +1 -1
  269. package/gsd-core/templates/UAT.md +17 -1
  270. package/gsd-core/templates/config.json +2 -11
  271. package/gsd-core/templates/verification-report.md +1 -1
  272. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  273. package/gsd-core/workflows/add-backlog.md +1 -1
  274. package/gsd-core/workflows/add-phase.md +8 -7
  275. package/gsd-core/workflows/add-tests.md +4 -3
  276. package/gsd-core/workflows/add-todo.md +6 -5
  277. package/gsd-core/workflows/ai-integration-phase.md +13 -4
  278. package/gsd-core/workflows/audit-fix.md +1 -1
  279. package/gsd-core/workflows/audit-milestone.md +4 -3
  280. package/gsd-core/workflows/audit-uat.md +1 -1
  281. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  282. package/gsd-core/workflows/autonomous.md +43 -23
  283. package/gsd-core/workflows/check-todos.md +7 -6
  284. package/gsd-core/workflows/cleanup.md +2 -2
  285. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  286. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  287. package/gsd-core/workflows/code-review-fix.md +112 -25
  288. package/gsd-core/workflows/code-review.md +146 -135
  289. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  290. package/gsd-core/workflows/complete-milestone.md +13 -8
  291. package/gsd-core/workflows/debug.md +32 -7
  292. package/gsd-core/workflows/diagnose-issues.md +3 -2
  293. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  294. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  295. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  296. package/gsd-core/workflows/discuss-phase.md +4 -3
  297. package/gsd-core/workflows/do.md +2 -2
  298. package/gsd-core/workflows/docs-update.md +6 -5
  299. package/gsd-core/workflows/edit-phase.md +4 -3
  300. package/gsd-core/workflows/eval-review.md +14 -5
  301. package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
  302. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
  303. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  304. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  305. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
  306. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  307. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  308. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  309. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  310. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  311. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  312. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  313. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  314. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  315. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  316. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  317. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  318. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  319. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  320. package/gsd-core/workflows/execute-phase.md +96 -178
  321. package/gsd-core/workflows/execute-plan.md +18 -23
  322. package/gsd-core/workflows/explore.md +4 -4
  323. package/gsd-core/workflows/extract-learnings.md +4 -2
  324. package/gsd-core/workflows/fast.md +1 -1
  325. package/gsd-core/workflows/forensics.md +1 -1
  326. package/gsd-core/workflows/graduation.md +1 -1
  327. package/gsd-core/workflows/health.md +3 -2
  328. package/gsd-core/workflows/help/modes/full.compact.md +3 -3
  329. package/gsd-core/workflows/help/modes/full.md +5 -5
  330. package/gsd-core/workflows/help/modes/topic.md +15 -5
  331. package/gsd-core/workflows/import.md +4 -3
  332. package/gsd-core/workflows/inbox.md +2 -2
  333. package/gsd-core/workflows/ingest-docs.md +3 -3
  334. package/gsd-core/workflows/insert-phase.md +4 -3
  335. package/gsd-core/workflows/list-seeds.md +1 -1
  336. package/gsd-core/workflows/list-workspaces.md +1 -1
  337. package/gsd-core/workflows/manager.md +6 -4
  338. package/gsd-core/workflows/map-codebase.md +5 -4
  339. package/gsd-core/workflows/milestone-summary.md +3 -2
  340. package/gsd-core/workflows/mvp-phase.md +14 -14
  341. package/gsd-core/workflows/new-milestone.md +11 -11
  342. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  343. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  344. package/gsd-core/workflows/new-project.md +7 -7
  345. package/gsd-core/workflows/new-workspace.md +2 -2
  346. package/gsd-core/workflows/next.md +1 -1
  347. package/gsd-core/workflows/note.md +1 -1
  348. package/gsd-core/workflows/onboard.md +1 -1
  349. package/gsd-core/workflows/pause-work.md +2 -2
  350. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  351. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  352. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  353. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  354. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  355. package/gsd-core/workflows/plan-phase.md +50 -24
  356. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  357. package/gsd-core/workflows/plant-seed.md +62 -20
  358. package/gsd-core/workflows/pr-branch.md +113 -13
  359. package/gsd-core/workflows/profile-user.md +2 -2
  360. package/gsd-core/workflows/progress.md +19 -49
  361. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
  362. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  363. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  364. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  365. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  366. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  367. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  368. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  369. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  370. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  371. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  372. package/gsd-core/workflows/quick-batch.md +15 -10
  373. package/gsd-core/workflows/quick.md +62 -39
  374. package/gsd-core/workflows/reapply-patches.md +9 -3
  375. package/gsd-core/workflows/remove-phase.md +3 -2
  376. package/gsd-core/workflows/remove-workspace.md +2 -2
  377. package/gsd-core/workflows/resume-project.md +3 -2
  378. package/gsd-core/workflows/review.md +33 -17
  379. package/gsd-core/workflows/scan.md +3 -2
  380. package/gsd-core/workflows/secure-phase.md +13 -13
  381. package/gsd-core/workflows/settings-advanced.md +30 -10
  382. package/gsd-core/workflows/settings-integrations.md +2 -3
  383. package/gsd-core/workflows/settings.md +4 -4
  384. package/gsd-core/workflows/ship.md +14 -13
  385. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  386. package/gsd-core/workflows/sketch.md +1 -1
  387. package/gsd-core/workflows/smart-entry.md +2 -2
  388. package/gsd-core/workflows/spec-phase.md +15 -5
  389. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  390. package/gsd-core/workflows/spike.md +1 -1
  391. package/gsd-core/workflows/stats.md +1 -1
  392. package/gsd-core/workflows/sync-skills.md +5 -5
  393. package/gsd-core/workflows/thread.md +2 -2
  394. package/gsd-core/workflows/transition.md +13 -23
  395. package/gsd-core/workflows/ui-phase.md +48 -11
  396. package/gsd-core/workflows/ui-review.md +21 -6
  397. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  398. package/gsd-core/workflows/undo.md +339 -20
  399. package/gsd-core/workflows/update.md +7 -7
  400. package/gsd-core/workflows/validate-phase.md +12 -13
  401. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  402. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  403. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  404. package/gsd-core/workflows/verify-work.md +129 -55
  405. package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
  406. package/hooks/dist/gsd-check-update-worker.js +8 -0
  407. package/hooks/dist/gsd-check-update.js +8 -0
  408. package/hooks/dist/gsd-context-monitor.js +31 -8
  409. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  410. package/hooks/dist/gsd-secret-read-guard.js +161 -4
  411. package/hooks/dist/gsd-statusline.js +85 -20
  412. package/hooks/dist/gsd-update-banner.js +8 -0
  413. package/hooks/dist/gsd-validate-commit.sh +63 -4
  414. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  415. package/hooks/dist/gsd-workflow-guard.js +5 -4
  416. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  417. package/hooks/dist/lib/cli-exit.js +12 -0
  418. package/hooks/dist/lib/git-probe.js +17 -1
  419. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  420. package/hooks/gsd-agent-isolation-guard.js +32 -0
  421. package/hooks/gsd-check-update-worker.js +8 -0
  422. package/hooks/gsd-check-update.js +8 -0
  423. package/hooks/gsd-context-monitor.js +31 -8
  424. package/hooks/gsd-cursor-subagent-start.js +8 -0
  425. package/hooks/gsd-secret-read-guard.js +161 -4
  426. package/hooks/gsd-statusline.js +85 -20
  427. package/hooks/gsd-update-banner.js +8 -0
  428. package/hooks/gsd-validate-commit.sh +63 -4
  429. package/hooks/gsd-windsurf-pre-write.js +11 -2
  430. package/hooks/gsd-workflow-guard.js +5 -4
  431. package/hooks/gsd-worktree-path-guard.js +6 -2
  432. package/hooks/hooks.json +5 -5
  433. package/hooks/lib/cli-exit.js +12 -0
  434. package/hooks/lib/git-probe.js +17 -1
  435. package/hooks/lib/isolation-sentinel.js +2 -2
  436. package/package.json +22 -4
  437. package/scripts/build-hooks.js +15 -6
  438. package/scripts/changeset/parse.cjs +52 -4
  439. package/scripts/check-contract-drift.cjs +127 -11
  440. package/scripts/ci-timeout-report.cjs +770 -4
  441. package/scripts/command-contract-helpers.cjs +15 -8
  442. package/scripts/docs-guard-registry.cjs +34 -0
  443. package/scripts/gen-features.cjs +13 -8
  444. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  445. package/scripts/gen-loop-host-contract.cjs +79 -1
  446. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  447. package/scripts/gen-plugin-skills.cjs +87 -1
  448. package/scripts/gen-research-agents.cjs +24 -31
  449. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  450. package/scripts/gen-test-timings.cjs +32 -7
  451. package/scripts/lib/cli-exit.cjs +12 -0
  452. package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
  453. package/scripts/lib/ndjson-reporter.cjs +31 -5
  454. package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
  455. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  456. package/scripts/lib/vendor-bundle.cjs +59 -0
  457. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  458. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  459. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  460. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  461. package/scripts/lint-descriptions.cjs +7 -3
  462. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
  463. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  464. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  465. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  466. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  467. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  468. package/scripts/lint-phase-id-drift.cjs +319 -5
  469. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  470. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  471. package/scripts/lint-response-language-coverage.cjs +3 -0
  472. package/scripts/lint-retired-runtime-name.cjs +619 -0
  473. package/scripts/lint-skill-deps.cjs +7 -3
  474. package/scripts/lint-state-write-path-drift.cjs +93 -0
  475. package/scripts/lint-test-file-count.allowlist.json +38 -9
  476. package/scripts/lint-test-file-count.cjs +34 -1
  477. package/scripts/lint-vendored-deps.cjs +41 -5
  478. package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
  479. package/scripts/mutation-matrix.cjs +50 -5
  480. package/scripts/prompt-injection-scan.sh +4 -0
  481. package/scripts/release-tarball-smoke.cjs +194 -1
  482. package/scripts/require-issue-link-policy.cjs +6 -2
  483. package/scripts/sync-runtime-launcher.cjs +184 -2
  484. package/scripts/verify-npm-publish.cjs +76 -20
  485. package/skills/gsd-add-tests/SKILL.md +6 -1
  486. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  487. package/skills/gsd-audit-fix/SKILL.md +5 -0
  488. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  489. package/skills/gsd-autonomous/SKILL.md +7 -2
  490. package/skills/gsd-capture/SKILL.md +9 -5
  491. package/skills/gsd-code-review/SKILL.md +7 -2
  492. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  493. package/skills/gsd-config/SKILL.md +8 -4
  494. package/skills/gsd-debug/SKILL.md +11 -7
  495. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  496. package/skills/gsd-docs-update/SKILL.md +12 -7
  497. package/skills/gsd-eval-review/SKILL.md +6 -1
  498. package/skills/gsd-execute-phase/SKILL.md +12 -7
  499. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  500. package/skills/gsd-fast/SKILL.md +4 -0
  501. package/skills/gsd-forensics/SKILL.md +5 -1
  502. package/skills/gsd-graphify/SKILL.md +10 -6
  503. package/skills/gsd-health/SKILL.md +5 -0
  504. package/skills/gsd-help/SKILL.md +7 -2
  505. package/skills/gsd-import/SKILL.md +7 -3
  506. package/skills/gsd-inbox/SKILL.md +5 -0
  507. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  508. package/skills/gsd-manager/SKILL.md +6 -1
  509. package/skills/gsd-map-codebase/SKILL.md +7 -3
  510. package/skills/gsd-mempalace-capture/SKILL.md +12 -4
  511. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  512. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  513. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  514. package/skills/gsd-new-milestone/SKILL.md +6 -1
  515. package/skills/gsd-new-project/SKILL.md +5 -0
  516. package/skills/gsd-next/SKILL.md +6 -1
  517. package/skills/gsd-ns-context/SKILL.md +4 -0
  518. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  519. package/skills/gsd-ns-manage/SKILL.md +4 -0
  520. package/skills/gsd-ns-project/SKILL.md +4 -0
  521. package/skills/gsd-ns-review/SKILL.md +4 -0
  522. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  523. package/skills/gsd-onboard/SKILL.md +6 -1
  524. package/skills/gsd-pause-work/SKILL.md +5 -1
  525. package/skills/gsd-phase/SKILL.md +8 -4
  526. package/skills/gsd-plan-phase/SKILL.md +6 -1
  527. package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
  528. package/skills/gsd-pr-branch/SKILL.md +4 -0
  529. package/skills/gsd-profile-user/SKILL.md +5 -1
  530. package/skills/gsd-progress/SKILL.md +7 -2
  531. package/skills/gsd-quick/SKILL.md +16 -10
  532. package/skills/gsd-quick-batch/SKILL.md +21 -9
  533. package/skills/gsd-review/SKILL.md +7 -4
  534. package/skills/gsd-review-backlog/SKILL.md +3 -2
  535. package/skills/gsd-secure-phase/SKILL.md +6 -1
  536. package/skills/gsd-ship/SKILL.md +5 -0
  537. package/skills/gsd-sketch/SKILL.md +7 -2
  538. package/skills/gsd-spec-phase/SKILL.md +5 -1
  539. package/skills/gsd-spike/SKILL.md +8 -3
  540. package/skills/gsd-surface/SKILL.md +5 -1
  541. package/skills/gsd-thread/SKILL.md +4 -0
  542. package/skills/gsd-ui-phase/SKILL.md +6 -1
  543. package/skills/gsd-ui-review/SKILL.md +6 -1
  544. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  545. package/skills/gsd-undo/SKILL.md +5 -1
  546. package/skills/gsd-update/SKILL.md +6 -2
  547. package/skills/gsd-validate-phase/SKILL.md +6 -1
  548. package/skills/gsd-verify-work/SKILL.md +6 -1
  549. package/skills/gsd-workspace/SKILL.md +7 -3
  550. package/skills/gsd-workstreams/SKILL.md +6 -6
  551. package/vscode/package.json +1 -1
@@ -0,0 +1,692 @@
1
+ "use strict";
2
+ /**
3
+ * Planning Document — the parse -> mutate -> serialize seam for a `.planning/`
4
+ * root artifact BODY (ADR-4910, epic #4906 Phase 1, #4917).
5
+ *
6
+ * Composes the existing structural seams — never reimplements them:
7
+ * - `markdown-sectionizer.cjs` (`tokenizeHeadings`, `collectSections`,
8
+ * `scanFencedBlocks`, `scanInlineCodeSpans`) for headings/sections and
9
+ * fence/inline-code awareness.
10
+ * - `markdown-table.cjs` (`splitTableRow`, `isDelimiterRow`,
11
+ * `parseMarkdownTable`) for GFM table detection and validation.
12
+ * - `artifacts.cjs` (`isCanonicalPlanningFile`) for the artifact-kind gate.
13
+ *
14
+ * This phase migrates NO call site — it is purely additive (ADR-4910 §7).
15
+ * Only `boldField` nodes are writable; `table`/`checklist` nodes parse and
16
+ * read only. Phase 3 (#4958) checked its own evidence (#4736, #4793) and
17
+ * found neither needed a table/checklist writer here — see ADR-4910's
18
+ * 2026-09-24 amendment. A writer for either kind is unclaimed until a real
19
+ * call site names it.
20
+ *
21
+ * Hyrum's Law commitment (row 3 of the design's behaviour table): `serialize`
22
+ * with zero staged edits returns `doc.source` BYTE-IDENTICAL — never a
23
+ * re-render (#4499's root cause). Every byte outside an edited `valueSpan` is
24
+ * the ORIGINAL source, spliced, never regenerated.
25
+ *
26
+ * ADR-457 build-at-publish: source in src/planning-document.cts, compiled to
27
+ * gsd-core/bin/lib/planning-document.cjs (gitignored).
28
+ */
29
+ var __importDefault = (this && this.__importDefault) || function (mod) {
30
+ return (mod && mod.__esModule) ? mod : { "default": mod };
31
+ };
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.PLANNING_ARTIFACTS = void 0;
34
+ exports.parsePlanningDoc = parsePlanningDoc;
35
+ exports.findField = findField;
36
+ exports.readNode = readNode;
37
+ exports.readFrontmatterField = readFrontmatterField;
38
+ exports.readFrontmatterFieldFromSource = readFrontmatterFieldFromSource;
39
+ exports.readFrontmatterFieldsFromSource = readFrontmatterFieldsFromSource;
40
+ exports.setFieldValue = setFieldValue;
41
+ exports.hasUnreadableNodes = hasUnreadableNodes;
42
+ exports.serialize = serialize;
43
+ exports.replaceProse = replaceProse;
44
+ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
45
+ const markdown_table_cjs_1 = require("./markdown-table.cjs");
46
+ const artifacts_cjs_1 = require("./artifacts.cjs");
47
+ // `frontmatter.cts` uses `export =` (CJS-style single export object), so it
48
+ // is imported as a default import (esModuleInterop), not a named import.
49
+ const frontmatter_cjs_1 = __importDefault(require("./frontmatter.cjs"));
50
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
51
+ const { extractFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatter_cjs_1.default;
52
+ /**
53
+ * Canonical `.planning/` root artifact basenames this seam recognises,
54
+ * derived from the SAME registry `isCanonicalPlanningFile` consults
55
+ * (`artifacts.cts`'s `CANONICAL_EXACT`) — never a second, independently
56
+ * maintained list.
57
+ *
58
+ * Filtered to `.md` names only: `CANONICAL_EXACT` also carries non-markdown
59
+ * artifacts (`config.json`, `state.json`, `milestone.lock`, …) that this
60
+ * parser has no grammar for. Handing that JSON/lock content to the markdown
61
+ * parser below returns a successful EMPTY document (`nodes: []`), which reads
62
+ * as "this document records nothing" when the truth is "wrong kind entirely"
63
+ * — the empty-vs-error confusion #4917 / ADR-4910 §5 exists to eliminate. Do
64
+ * NOT remove this filter to "restore" the full registry.
65
+ */
66
+ exports.PLANNING_ARTIFACTS = Object.freeze(Array.from(artifacts_cjs_1.CANONICAL_EXACT).filter((name) => name.endsWith('.md')));
67
+ // ─── Internal helpers ───────────────────────────────────────────────────────
68
+ let nodeCounter = 0;
69
+ function mintId(kind) {
70
+ nodeCounter += 1;
71
+ return `${kind}-${nodeCounter}-${Math.random().toString(36).slice(2, 8)}`;
72
+ }
73
+ function splitLinesInfo(source) {
74
+ const out = [];
75
+ let offset = 0;
76
+ const rawLines = source.split('\n');
77
+ for (let i = 0; i < rawLines.length; i++) {
78
+ const raw = rawLines[i];
79
+ const hasCR = raw.endsWith('\r');
80
+ const text = hasCR ? raw.slice(0, -1) : raw;
81
+ out.push({ text, start: offset, end: offset + text.length });
82
+ offset += raw.length + 1; // +1 for the '\n' split on ('\r' already counted in raw.length)
83
+ }
84
+ return out;
85
+ }
86
+ /**
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).
91
+ *
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.
98
+ */
99
+ function findFrontmatterSpan(source) {
100
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(source);
101
+ if (!fence)
102
+ return null;
103
+ const start = fence.bom.length;
104
+ if (!fence.closed) {
105
+ return { span: { start, end: source.length }, terminated: false };
106
+ }
107
+ const end = fence.closingFenceEnd + (source[fence.closingFenceEnd] === '\r' ? 1 : 0);
108
+ return { span: { start, end }, terminated: true };
109
+ }
110
+ /** Build the set of 0-based line indices that fall inside a fenced code
111
+ * block (opening/closing delimiter lines included), so `**Label:**`/table/
112
+ * checklist scanning never treats fenced content as a node (rows 9/14). */
113
+ function fencedLineIndices(lines) {
114
+ const raw = lines.map((l) => l.text);
115
+ const blocks = (0, markdown_sectionizer_cjs_1.scanFencedBlocks)(raw);
116
+ const set = new Set();
117
+ for (const b of blocks) {
118
+ const end = b.closeLineIdx === -1 ? raw.length - 1 : b.closeLineIdx;
119
+ for (let i = b.openLineIdx; i <= end; i++)
120
+ set.add(i);
121
+ }
122
+ return set;
123
+ }
124
+ /** Matches both shipped bold-field spellings: colon-inside (`**Label:**`,
125
+ * the original grammar) and colon-outside (`**Label**:`, the canonical form
126
+ * used throughout `templates/roadmap.md`). Each alternative's trailing
127
+ * marker is exactly 3 characters (`:**` or `**:`), so `token.slice(2, -3)`
128
+ * in `parseBoldFieldLine` strips the leading `**` and the spelling-specific
129
+ * trailing marker identically for both, yielding the same `label` either
130
+ * way. Deliberately excludes a bare unbolded `Label:` form — see Phase 1's
131
+ * prose-vs-field disambiguation design. */
132
+ const BOLD_FIELD_RE = /^(\s*)(\*\*[^*\r\n]+(?::\*\*|\*\*:))([ \t]*)([^\r\n]*)$/;
133
+ /** Boundary marking a hand-written trailing annotation on a field line —
134
+ * the token owner must never destroy prose past this separator. */
135
+ const TRAILING_SEPARATOR_RE = / — /;
136
+ function parseBoldFieldLine(line) {
137
+ const m = BOLD_FIELD_RE.exec(line.text);
138
+ if (!m)
139
+ return null;
140
+ const [, leading, token, spacing, rest] = m;
141
+ const labelStart = line.start + leading.length;
142
+ const labelSpan = { start: labelStart, end: labelStart + token.length };
143
+ const label = token.slice(2, -3);
144
+ const restStart = labelSpan.end + spacing.length;
145
+ const sepMatch = TRAILING_SEPARATOR_RE.exec(rest);
146
+ const valueRaw = sepMatch ? rest.slice(0, sepMatch.index) : rest;
147
+ const trimmedValue = valueRaw.replace(/\s+$/, '');
148
+ const valueSpan = { start: restStart, end: restStart + trimmedValue.length };
149
+ const trailingSpan = { start: valueSpan.end, end: line.end };
150
+ return {
151
+ kind: 'boldField',
152
+ id: mintId('boldField'),
153
+ span: { start: labelSpan.start, end: line.end },
154
+ error: null,
155
+ label,
156
+ labelSpan,
157
+ valueSpan,
158
+ trailingSpan,
159
+ value: trimmedValue,
160
+ };
161
+ }
162
+ /** A checklist line is one whose SOLE bullet, per `iterateBullets` (the same
163
+ * grammar the repo's other bullet consumers use), is a checkbox marker, OR
164
+ * whose bullet TEXT begins with a task-list marker.
165
+ *
166
+ * `iterateBullets` owns bullet *structure* — is this a bullet, where does its
167
+ * text start — and continues to own that here unchanged. It only classifies
168
+ * `-`-prefixed bullets as `checkbox-checked`/`checkbox-unchecked`; GFM also
169
+ * permits `*` and `+` as bullet markers, and `* [ ] x` / `+ [x] y` are valid
170
+ * GFM task-list items that `iterateBullets` reports as plain `dash`-family
171
+ * bullets with the `[ ]`/`[x]` left in the bullet's own text. Widening
172
+ * `iterateBullets` itself is forbidden by ADR-2143 §2's extend-never-mutate
173
+ * lock (inherited by this epic), so the task-list-marker interpretation is
174
+ * layered on here, over the bullet's already-extracted text — never by
175
+ * re-scanning the raw line with a new hand-rolled regex.
176
+ *
177
+ * Known limit inherited from `iterateBullets`, not introduced here:
178
+ * `-\t[ ] text` (a tab between the marker and the text) is not recognised as
179
+ * a bullet at all, so it can never become a checklist line. That is a
180
+ * pre-existing `markdown-sectionizer` boundary affecting every consumer of
181
+ * `iterateBullets`, and fixing it would mean altering the locked seam. */
182
+ function isChecklistLine(text) {
183
+ const items = (0, markdown_sectionizer_cjs_1.iterateBullets)(text);
184
+ if (items.length !== 1)
185
+ return false;
186
+ const item = items[0];
187
+ if (item.marker === 'checkbox-checked' || item.marker === 'checkbox-unchecked')
188
+ return true;
189
+ return /^\[[ xX]\] /.test(item.text);
190
+ }
191
+ /**
192
+ * Scan the document body (everything outside the frontmatter block and
193
+ * outside fenced code) for `boldField`, `table`, and `checklist` nodes, in
194
+ * document order.
195
+ */
196
+ function scanBodyNodes(source, lines, frontmatterEnd) {
197
+ const fenced = fencedLineIndices(lines);
198
+ const nodes = [];
199
+ let i = 0;
200
+ while (i < lines.length) {
201
+ const line = lines[i];
202
+ if (fenced.has(i) || line.start < frontmatterEnd) {
203
+ i += 1;
204
+ continue;
205
+ }
206
+ const trimmed = line.text.trim();
207
+ // Table: a pipe-shaped header line followed by a valid delimiter row.
208
+ if (trimmed.startsWith('|') && trimmed.indexOf('|', 1) !== -1 && i + 1 < lines.length) {
209
+ const delimiterLine = lines[i + 1];
210
+ const delimiterCells = (0, markdown_table_cjs_1.splitTableRow)(delimiterLine.text);
211
+ const headerCells = (0, markdown_table_cjs_1.splitTableRow)(line.text);
212
+ if (delimiterLine.text.trim().startsWith('|')
213
+ && (0, markdown_table_cjs_1.isDelimiterRow)(delimiterCells)
214
+ && delimiterCells.length === headerCells.length
215
+ && !fenced.has(i + 1)) {
216
+ let last = i + 1;
217
+ while (last + 1 < lines.length && lines[last + 1].text.trim().startsWith('|') && !fenced.has(last + 1)) {
218
+ last += 1;
219
+ }
220
+ const span = { start: line.start, end: lines[last].end };
221
+ const tableText = source.slice(span.start, span.end);
222
+ const parsed = (0, markdown_table_cjs_1.parseMarkdownTable)(tableText);
223
+ nodes.push(parsed.ok
224
+ ? {
225
+ kind: 'table',
226
+ id: mintId('table'),
227
+ span,
228
+ error: null,
229
+ columns: parsed.value.columns,
230
+ }
231
+ : {
232
+ kind: 'table',
233
+ id: mintId('table'),
234
+ span,
235
+ error: { reason: parsed.reason, span },
236
+ columns: null,
237
+ });
238
+ i = last + 1;
239
+ continue;
240
+ }
241
+ }
242
+ // Checklist: a contiguous run of checkbox-bullet lines.
243
+ if (isChecklistLine(line.text)) {
244
+ let last = i;
245
+ let count = 0;
246
+ while (last < lines.length && !fenced.has(last) && isChecklistLine(lines[last].text)) {
247
+ count += 1;
248
+ last += 1;
249
+ }
250
+ last -= 1;
251
+ const span = { start: line.start, end: lines[last].end };
252
+ nodes.push({ kind: 'checklist', id: mintId('checklist'), span, error: null, items: count });
253
+ i = last + 1;
254
+ continue;
255
+ }
256
+ // Bold field.
257
+ const field = parseBoldFieldLine(line);
258
+ if (field) {
259
+ nodes.push(field);
260
+ i += 1;
261
+ continue;
262
+ }
263
+ i += 1;
264
+ }
265
+ return nodes;
266
+ }
267
+ // ─── Public API ─────────────────────────────────────────────────────────────
268
+ /**
269
+ * Parse `source` (the raw text of a `.planning/` root artifact) into a
270
+ * `PlanningDoc`. Document-level `Result` failure is reserved for: `artifact`
271
+ * not a recognised planning artifact kind, `source` not a readable string, or
272
+ * an opened-but-never-closed frontmatter fence (ADR-4910 §5's reservation).
273
+ * A malformed SUB-structure (a ragged table, say) never fails the whole
274
+ * document — it is recorded as that one node's `error`, and every sibling
275
+ * node stays readable (row 7). `nodes: []` on a genuinely empty document is
276
+ * success, not an error (row 15).
277
+ */
278
+ function parsePlanningDoc(source, artifact) {
279
+ if (typeof source !== 'string') {
280
+ return { ok: false, reason: 'unreadable: source is not a string' };
281
+ }
282
+ if (typeof artifact !== 'string' ||
283
+ !(0, artifacts_cjs_1.isCanonicalPlanningFile)(artifact) ||
284
+ !exports.PLANNING_ARTIFACTS.includes(artifact)) {
285
+ return {
286
+ ok: false,
287
+ reason: `not a markdown planning document (artifact: ${String(artifact)})`,
288
+ };
289
+ }
290
+ const nodes = [];
291
+ let frontmatterEnd = 0;
292
+ const fm = findFrontmatterSpan(source);
293
+ if (fm) {
294
+ if (!fm.terminated) {
295
+ return { ok: false, reason: 'no frontmatter terminator' };
296
+ }
297
+ nodes.push({ kind: 'frontmatter', id: mintId('frontmatter'), span: fm.span, error: null });
298
+ frontmatterEnd = fm.span.end;
299
+ }
300
+ if (source.length === 0) {
301
+ return {
302
+ ok: true,
303
+ value: { source, artifact, nodes: [], staged: new Map() },
304
+ };
305
+ }
306
+ const lines = splitLinesInfo(source);
307
+ // Sections: one per heading, in document order — every heading is its own
308
+ // boundary (`collectSections(source, () => true)`), so a nested `####`
309
+ // still gets its own SectionNode rather than being folded into its parent.
310
+ const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(source);
311
+ if (headings.length > 0) {
312
+ const sections = (0, markdown_sectionizer_cjs_1.collectSections)(source, () => true);
313
+ for (const s of sections) {
314
+ nodes.push({
315
+ kind: 'section',
316
+ id: mintId('section'),
317
+ span: { start: s.heading.offset, end: s.bodyEnd },
318
+ error: null,
319
+ heading: s.heading.text,
320
+ level: s.heading.level,
321
+ });
322
+ }
323
+ }
324
+ nodes.push(...scanBodyNodes(source, lines, frontmatterEnd));
325
+ nodes.sort((a, b) => a.span.start - b.span.start);
326
+ return {
327
+ ok: true,
328
+ value: { source, artifact, nodes, staged: new Map() },
329
+ };
330
+ }
331
+ /** Find the id of the (first, document-order) `boldField` node whose label
332
+ * exactly matches `label`, or `null` when none does. */
333
+ function findField(doc, label) {
334
+ for (const n of doc.nodes) {
335
+ if (n.kind === 'boldField' && n.label === label)
336
+ return n.id;
337
+ }
338
+ return null;
339
+ }
340
+ /** Read a node by id. Node-scoped failure only — an unknown id or a node
341
+ * that failed to parse never throws. */
342
+ function readNode(doc, id) {
343
+ const node = doc.nodes.find((n) => n.id === id);
344
+ if (!node) {
345
+ return { ok: false, reason: 'unknown node id', span: { start: 0, end: 0 } };
346
+ }
347
+ if (node.error) {
348
+ return { ok: false, reason: node.error.reason, span: node.error.span };
349
+ }
350
+ if (node.kind === 'boldField') {
351
+ return { ok: true, value: doc.staged.get(id) ?? node.value };
352
+ }
353
+ return { ok: true, value: doc.source.slice(node.span.start, node.span.end) };
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
+ }
512
+ /**
513
+ * Stage a new value for a `boldField` node, returning a NEW `PlanningDoc`
514
+ * (immutable — `doc` itself is never mutated). Refuses an id this doc did
515
+ * not mint, and refuses any node kind other than `boldField` — only
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.
555
+ */
556
+ function setFieldValue(doc, id, value) {
557
+ const node = doc.nodes.find((n) => n.id === id);
558
+ if (!node) {
559
+ return { ok: false, reason: 'unknown node id' };
560
+ }
561
+ if (node.kind !== 'boldField') {
562
+ return { ok: false, reason: `node kind '${node.kind}' is not writable this phase` };
563
+ }
564
+ // #4917 / ADR-4910 Decision 2 & 4: a boldField's token boundary is a LINE
565
+ // boundary, not just an offset range — a value containing \n or \r escapes
566
+ // the field's own span and reparses as sibling structure (a forged field)
567
+ // once spliced back into the source. Decision 4 licenses refusal for any
568
+ // value the grammar cannot represent; Phase 3 may widen this to escaping,
569
+ // but Phase 1 refuses outright. Do not remove this as an over-restriction.
570
+ if (/[\r\n]/.test(value)) {
571
+ return { ok: false, reason: 'field value must not contain a line break (\\r or \\n)' };
572
+ }
573
+ // #4917 / ADR-4910 Decision 4: "a value that cannot be represented in the
574
+ // grammar is refused by the writer, with a report." This is a GENERAL
575
+ // round-trip representability check, not a blacklist of forbidden
576
+ // substrings — the `\r`/`\n` guard above is a narrower special case kept
577
+ // for its clearer message, but THIS check is the backstop. It rebuilds the
578
+ // line exactly as it would be written (existing leading/label/spacing +
579
+ // the new value + the existing trailing text) and re-parses that line
580
+ // through the SAME `parseBoldFieldLine` grammar the reader uses. If the
581
+ // value the grammar reads back is not byte-identical to what the caller
582
+ // staged, the grammar cannot represent this value (e.g. it contains the
583
+ // ` — ` trailing-separator token, which would silently reclassify the
584
+ // rest of the value as trailing prose) and the write is refused. Do NOT
585
+ // replace this with a list of forbidden characters/substrings — the next
586
+ // separator the grammar grows would silently slip past a blacklist.
587
+ const leadingText = doc.source.slice(node.span.start, node.labelSpan.start);
588
+ const tokenText = doc.source.slice(node.labelSpan.start, node.labelSpan.end);
589
+ const spacingText = doc.source.slice(node.labelSpan.end, node.valueSpan.start);
590
+ const trailingText = doc.source.slice(node.trailingSpan.start, node.trailingSpan.end);
591
+ const candidateLine = `${leadingText}${tokenText}${spacingText}${value}${trailingText}`;
592
+ const candidateInfo = { text: candidateLine, start: 0, end: candidateLine.length };
593
+ const reparsed = parseBoldFieldLine(candidateInfo);
594
+ if (!reparsed || reparsed.value !== value) {
595
+ return {
596
+ ok: false,
597
+ reason: 'field value is not representable in the boldField grammar (would not round-trip)',
598
+ };
599
+ }
600
+ const staged = new Map(doc.staged);
601
+ staged.set(id, value);
602
+ return {
603
+ ok: true,
604
+ value: { source: doc.source, artifact: doc.artifact, nodes: doc.nodes, staged },
605
+ };
606
+ }
607
+ /** True when any node in `doc` failed to parse. */
608
+ function hasUnreadableNodes(doc) {
609
+ return doc.nodes.some((n) => n.error !== null);
610
+ }
611
+ /**
612
+ * Splice every staged edit into `doc.source` and return the resulting text.
613
+ * With zero staged edits, returns `doc.source` BYTE-IDENTICAL — never a
614
+ * re-render (row 3). Refuses outright — even with zero staged edits — when
615
+ * `hasUnreadableNodes(doc)` is true (the ADR-4910 amendment): `serialize`
616
+ * re-emits the WHOLE document, so the refusal is document-scoped, not
617
+ * mutation-scoped.
618
+ */
619
+ function serialize(doc) {
620
+ if (hasUnreadableNodes(doc)) {
621
+ return {
622
+ ok: false,
623
+ reason: 'unreadable-nodes',
624
+ nodes: doc.nodes
625
+ .filter((n) => n.error !== null)
626
+ .map((n) => ({ id: n.id, kind: n.kind, span: n.error.span, reason: n.error.reason })),
627
+ };
628
+ }
629
+ if (doc.staged.size === 0) {
630
+ return { ok: true, value: doc.source };
631
+ }
632
+ const edits = [];
633
+ for (const [id, value] of doc.staged) {
634
+ const node = doc.nodes.find((n) => n.id === id);
635
+ if (!node || node.kind !== 'boldField')
636
+ continue; // unreachable: setFieldValue already gated this
637
+ edits.push({ start: node.valueSpan.start, end: node.valueSpan.end, value });
638
+ }
639
+ edits.sort((a, b) => a.start - b.start);
640
+ let out = '';
641
+ let cursor = 0;
642
+ for (const e of edits) {
643
+ out += doc.source.slice(cursor, e.start) + e.value;
644
+ cursor = e.end;
645
+ }
646
+ out += doc.source.slice(cursor);
647
+ return { ok: true, value: out };
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
+ }
691
+ // Consumers: require('../gsd-core/bin/lib/planning-document.cjs')
692
+ // Named CJS exports are the canonical surface (ADR-457 .cts → .cjs build-at-publish).