@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,494 @@
1
+ "use strict";
2
+ /**
3
+ * frontmatter-splice.cts — the frontmatter WRITER (#5105): `spliceFrontmatter` and the
4
+ * machinery only it uses — per-key layout slicing, comment and tail-line classification, the
5
+ * parse budget, key quoting for a regenerated key, the read-back post-condition and the
6
+ * write-refusal vocabulary (`FrontmatterWriteRefusedError` / `isFrontmatterWriteRefusal`).
7
+ *
8
+ * Split out of `frontmatter.cts` by module ownership (#5105): the reader — fence region,
9
+ * guarded parse, comment channel, `reconstructFrontmatter` — stays there; the decision whether
10
+ * and how a changed frontmatter block may be written lives here. `frontmatter.cts` re-exports
11
+ * this module's public names, so every caller's `require('./frontmatter.cjs')` is unchanged.
12
+ * Behavior is unchanged: every function below moved verbatim.
13
+ *
14
+ * Import direction: this module requires `frontmatter.cjs` at load time (the parser it builds
15
+ * on, through `frontmatter.spliceSeam`); `frontmatter.cjs` requires this module LAZILY — from
16
+ * its re-export getters and its set/merge commands, never while it is itself loading — so
17
+ * whichever of the two a caller loads first, the other is complete before it is read.
18
+ *
19
+ * Source in src/frontmatter-splice.cts, compiled to gsd-core/bin/lib/frontmatter-splice.cjs
20
+ * (gitignored), per the repo's ADR-457 build-at-publish convention.
21
+ */
22
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
23
+ const frontmatterMod = require("./frontmatter.cjs");
24
+ const text_lines_cjs_1 = require("./text-lines.cjs");
25
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
26
+ const js_yaml_cjs_1 = require("./vendor/js-yaml.cjs");
27
+ const { extractFrontmatter, reconstructFrontmatter, escapeDoubleQuotedScalar, frontmatterBlock, FRONTMATTER_UNPARSEABLE, spliceSeam } = frontmatterMod;
28
+ const { FULL_LINE_COMMENTS, YAML_LOAD_OPTS, commentPathKey, channelKeyLine, segmentKeyOf, escapeNullBytesForParse, unparseableResult, frontmatterDeepEqual, } = spliceSeam;
29
+ /**
30
+ * A line that can sit after a key's value without belonging to it (see `FrontmatterSegment`):
31
+ * a blank line or a column-0 full-line comment, and nothing else. Every other line after a
32
+ * key line — an indented line, a `- item`, and a column-0 continuation of a multi-line
33
+ * quoted scalar or of a flow collection split across lines (`tags: [a,` / `b]`) — is part of
34
+ * the key's own value and is replaced with it (found while implementing #5105).
35
+ */
36
+ function isSegmentTailLine(line) {
37
+ return line.trim() === '' || line.startsWith('#');
38
+ }
39
+ /**
40
+ * The most YAML text, in characters (each parse also counts one for its own line break), one
41
+ * `spliceFrontmatter` call may parse while classifying lines — `segmentTailStart`,
42
+ * `isSegmentComment` and `inlineCommentStart` each decide a line by re-parsing the key's lines
43
+ * without it, so the work grows with (lines × `#`/tail lines × line length). A real planning
44
+ * document stays far below it (across every tracked `.md` file, splicing any key to a changed
45
+ * value parses at most ~60,000 characters — 0.3% of it, in the adversarial `huge-bounded.md`
46
+ * fixture); a pathological block — thousands of blank lines after a `|+` key,
47
+ * or hundreds of lines each holding many ` #` — is refused instead of stalling the writer
48
+ * (found while implementing #5105).
49
+ */
50
+ const SPLICE_PARSE_BUDGET_CHARS = 20_000_000;
51
+ /**
52
+ * Parse one key's lines on their own, or null when they do not parse as YAML. Charges the
53
+ * text to `budget`, refusing with `FRONTMATTER_TOO_COMPLEX` once it is spent.
54
+ */
55
+ function loadSegmentValue(lines, budget) {
56
+ const text = lines.join('\n');
57
+ budget.remaining -= text.length + 1;
58
+ if (budget.remaining < 0) {
59
+ throw new FrontmatterWriteRefusedError('FRONTMATTER_TOO_COMPLEX', 'frontmatter: refusing to write — telling this frontmatter block\'s comments and trailing blank ' +
60
+ `lines apart from its values would take more than ${budget.limit} characters of YAML ` +
61
+ 'parsing (a very large block with many `#` or trailing blank lines), so the writer stops rather ' +
62
+ 'than stall. Edit the file directly.');
63
+ }
64
+ try {
65
+ return { value: (0, js_yaml_cjs_1.load)(escapeNullBytesForParse(text), YAML_LOAD_OPTS) };
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ /**
72
+ * Where a key's trailing tail starts. The candidate tail is the trailing run of blank and
73
+ * column-0 `#` lines, but such a line can still be value text: a `#` line inside a
74
+ * multi-line quoted scalar, or a blank line a block scalar keeps. The parser decides — a
75
+ * tail line is only a tail line when the key's lines parse to the same value without it.
76
+ * When the key's lines do not parse on their own, the lexical candidate stands; the
77
+ * read-back check in `spliceFrontmatter` still refuses any block that would misread.
78
+ */
79
+ function segmentTailStart(lines, budget) {
80
+ let cut = lines.length;
81
+ while (cut > 1 && isSegmentTailLine(lines[cut - 1]))
82
+ cut--;
83
+ if (cut === lines.length)
84
+ return cut;
85
+ const whole = loadSegmentValue(lines, budget);
86
+ if (!whole)
87
+ return cut;
88
+ while (cut < lines.length) {
89
+ const body = loadSegmentValue(lines.slice(0, cut), budget);
90
+ if (body && frontmatterDeepEqual(body.value, whole.value))
91
+ return cut;
92
+ cut++;
93
+ }
94
+ return cut;
95
+ }
96
+ /**
97
+ * Slice a frontmatter YAML body into its `preamble` (lines before the first top-level key,
98
+ * e.g. a leading comment) and per-top-level-key raw text segments. Each segment runs from a
99
+ * column-0 key line through the line before the next column-0 key (or the end), capturing
100
+ * all nested indented content. Used by `spliceFrontmatter` for per-key identity
101
+ * preservation (#1572): a structurally-unchanged key keeps its original raw text, so the
102
+ * lossy `reconstructFrontmatter` never touches object-lists the caller did not modify (e.g.
103
+ * must_haves.artifacts / .prohibitions). Every input line lands in exactly one of
104
+ * `preamble`, a segment `body` or a segment `tail` — nothing is discarded here.
105
+ */
106
+ function sliceFrontmatterLayout(yaml, budget) {
107
+ const preamble = [];
108
+ const segments = [];
109
+ let current = null;
110
+ const close = (seg) => {
111
+ const cut = segmentTailStart(seg.lines, budget);
112
+ segments.push({
113
+ key: seg.key,
114
+ valueStart: seg.valueStart,
115
+ raw: seg.lines.join('\n'),
116
+ body: seg.lines.slice(0, cut),
117
+ tail: seg.lines.slice(cut),
118
+ });
119
+ };
120
+ for (const line of (0, text_lines_cjs_1.splitLines)(yaml)) {
121
+ const key = segmentKeyOf(line);
122
+ if (key) {
123
+ if (current)
124
+ close(current);
125
+ current = { key: key.key, valueStart: key.valueStart, lines: [line] };
126
+ }
127
+ else if (current) {
128
+ current.lines.push(line);
129
+ }
130
+ else {
131
+ preamble.push(line);
132
+ }
133
+ }
134
+ if (current)
135
+ close(current);
136
+ return { preamble, segments };
137
+ }
138
+ /**
139
+ * Regenerate one frontmatter key's serialization, fail-closed if the lossy
140
+ * `reconstructFrontmatter` cannot represent the value (#1572 codex review). Object-list
141
+ * items (e.g. must_haves.artifacts `{path, provides}` maps) serialize as the literal
142
+ * string "[object Object]"; rather than silently emit that and destroy the data, refuse
143
+ * so the caller (cmdFrontmatterSet/Merge) errors out WITHOUT writing — directing the
144
+ * user to edit the file directly. The reported #1572 case (mutating an UNRELATED field)
145
+ * is unaffected: unchanged keys preserve their original raw text and never reach here.
146
+ */
147
+ function regenerateFrontmatterKey(key, value, comments) {
148
+ // Computed key: a key named `__proto__` is an own data property here, never the prototype.
149
+ const single = { [key]: value };
150
+ // `comments` are the comments written inside this key's own value lines, keyed by exact
151
+ // key path (`segmentComments`), so `reconstructFrontmatter` re-emits each beside the key it
152
+ // belongs to. The key's own leading comment and the block's trailing comments are not part
153
+ // of the value: they sit in the splice's preamble or a neighbour's tail, which is re-emitted
154
+ // verbatim.
155
+ if (comments)
156
+ single[FULL_LINE_COMMENTS] = comments;
157
+ const rendered = reconstructFrontmatter(single);
158
+ if (/\[object Object\]/.test(rendered)) {
159
+ throw new Error(`frontmatter: cannot faithfully serialize key "${key}" — it contains a nested object-list ` +
160
+ `(e.g. must_haves.artifacts) the frontmatter writer cannot represent, and serializing it would ` +
161
+ `emit "[object Object]". Edit the file directly instead of using frontmatter set/merge.`);
162
+ }
163
+ // `reconstructFrontmatter` emits keys bare. A key the parser would not read back as
164
+ // itself when bare (`a:b`, `a: b`, `#x`, `- k`, a trailing space) is emitted double-quoted
165
+ // instead, so a regenerated or appended key never re-parses as a different key.
166
+ if (rendered.startsWith(`${key}:`) && !/^[\p{L}\p{N}_][\p{L}\p{N}_.-]*$/u.test(key)) {
167
+ return `"${escapeDoubleQuotedScalar(key)}"` + rendered.slice(key.length);
168
+ }
169
+ return rendered;
170
+ }
171
+ class FrontmatterWriteRefusedError extends Error {
172
+ code;
173
+ constructor(code, message) {
174
+ super(message);
175
+ this.name = 'FrontmatterWriteRefusedError';
176
+ this.code = code;
177
+ }
178
+ }
179
+ function isFrontmatterWriteRefusal(err) {
180
+ return err instanceof FrontmatterWriteRefusedError;
181
+ }
182
+ /** Does a closed frontmatter block's parse mean "unparseable" for a writer? */
183
+ function frontmatterBlockUnparseable(parsed, innerYaml) {
184
+ if (parsed[FRONTMATTER_UNPARSEABLE] === true)
185
+ return true;
186
+ if (Object.keys(parsed).length > 0)
187
+ return false;
188
+ // `{}` from a block that still carries content: a bare scalar (`status:complete`) or a
189
+ // sequence at the top level. Only blank lines and comments make a genuinely empty block.
190
+ return (0, text_lines_cjs_1.splitLines)(innerYaml).some((line) => line.trim() !== '' && !line.trim().startsWith('#'));
191
+ }
192
+ /**
193
+ * What `extractFrontmatter` reads back for `value` once the writer has written it: every
194
+ * scalar is a string under `FAILSAFE_SCHEMA` (`2` → `'2'`, `true` → `'true'`), a
195
+ * null/undefined mapping entry is not written at all (`undefined` here), and a
196
+ * null/undefined list item reads back as `''`. The intended object a write must read back as.
197
+ */
198
+ function readBackProjection(value, inArray = false) {
199
+ if (value === null || value === undefined)
200
+ return inArray ? '' : undefined;
201
+ if (Array.isArray(value))
202
+ return value.map((item) => readBackProjection(item, true));
203
+ if (typeof value === 'object') {
204
+ const out = {};
205
+ for (const [k, v] of Object.entries(value)) {
206
+ const projected = readBackProjection(v);
207
+ // `defineProperty`, never `out[k] =`: a key named `__proto__` stays an own data property.
208
+ if (projected !== undefined)
209
+ Object.defineProperty(out, k, { value: projected, writable: true, enumerable: true, configurable: true });
210
+ }
211
+ return out;
212
+ }
213
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
214
+ return String(value);
215
+ }
216
+ /**
217
+ * The write post-condition every splice path ends with: the document about to be returned
218
+ * must carry a frontmatter block that parses — through `extractFrontmatter` itself, the same
219
+ * reader every consumer uses — to exactly the intended object. Anything the serializer cannot
220
+ * represent faithfully (a nested key with `: ` in it, a value that re-types) is refused here,
221
+ * so no caller can write a block that does not read back as intended (found while
222
+ * implementing #5105).
223
+ */
224
+ function verifyReadsBackAs(out, newObj) {
225
+ const reread = extractFrontmatter(out);
226
+ if (reread[FRONTMATTER_UNPARSEABLE] === true ||
227
+ !frontmatterDeepEqual(reread, readBackProjection(newObj))) {
228
+ throw new FrontmatterWriteRefusedError('FRONTMATTER_SPLICE_VERIFY_FAILED', 'frontmatter: refusing to write — the rewritten frontmatter block would not read back as the ' +
229
+ 'requested values (a value or nested key the frontmatter writer cannot represent faithfully), so ' +
230
+ 'writing it would store something other than what was asked. Edit the file directly.');
231
+ }
232
+ return out;
233
+ }
234
+ /**
235
+ * Is `lines[i]` a full-line comment the parser ignores — do the key's lines parse to the
236
+ * same value without it? A `#` line inside a block scalar or a multi-line quoted scalar is
237
+ * value text, not a comment. When the lines do not parse on their own (`whole` null) every
238
+ * `#` line counts, so the comment post-condition fails closed.
239
+ */
240
+ function isSegmentComment(lines, i, whole, budget) {
241
+ if (!/^\s*#/.test(lines[i]))
242
+ return false;
243
+ if (!whole)
244
+ return true;
245
+ const without = loadSegmentValue([...lines.slice(0, i), ...lines.slice(i + 1)], budget);
246
+ return without !== null && frontmatterDeepEqual(without.value, whole.value);
247
+ }
248
+ /**
249
+ * Where the inline comment on `lines[i]` starts (the whitespace before its `#`), or -1. A
250
+ * ` #` is a comment only when the key's lines parse to the same value with the line cut
251
+ * there, so a `#` inside a quoted scalar or a block scalar never counts. When the lines do
252
+ * not parse on their own (`whole` null) the first ` #` counts.
253
+ */
254
+ function inlineCommentStart(lines, i, whole, budget) {
255
+ const line = lines[i];
256
+ const hashes = /[ \t]#/g;
257
+ for (let m = hashes.exec(line); m !== null; m = hashes.exec(line)) {
258
+ let start = m.index;
259
+ while (start > 0 && (line[start - 1] === ' ' || line[start - 1] === '\t'))
260
+ start--;
261
+ if (!whole)
262
+ return start;
263
+ const cut = loadSegmentValue([...lines.slice(0, i), line.slice(0, start), ...lines.slice(i + 1)], budget);
264
+ if (cut !== null && frontmatterDeepEqual(cut.value, whole.value))
265
+ return start;
266
+ }
267
+ return -1;
268
+ }
269
+ function segmentComments(key, lines, budget) {
270
+ const whole = loadSegmentValue(lines, budget);
271
+ const leading = Object.create(null);
272
+ const inline = Object.create(null);
273
+ const unattached = [];
274
+ const stack = [];
275
+ let pending = [];
276
+ const loosen = (comments) => { for (const c of comments)
277
+ unattached.push(c.line.trim()); };
278
+ for (let i = 0; i < lines.length; i++) {
279
+ const line = lines[i];
280
+ if (i > 0 && line.trim() === '')
281
+ continue;
282
+ if (i > 0 && isSegmentComment(lines, i, whole, budget)) {
283
+ pending.push({ indent: /^\s*/.exec(line)?.[0].length ?? 0, line });
284
+ continue;
285
+ }
286
+ const at = inlineCommentStart(lines, i, whole, budget);
287
+ let owner = i === 0 ? [key] : null;
288
+ if (i > 0) {
289
+ const k = channelKeyLine(at === -1 ? line : line.slice(0, at));
290
+ if (k && k.indent > 0 && k.key.length > 0) {
291
+ while (stack.length > 0 && stack[stack.length - 1].indent >= k.indent)
292
+ stack.pop();
293
+ owner = [key, ...stack.map((e) => e.key), k.key];
294
+ const same = pending.filter((c) => c.indent === k.indent);
295
+ if (same.length > 0)
296
+ leading[commentPathKey(owner)] = same.map((c) => c.line);
297
+ loosen(pending.filter((c) => c.indent !== k.indent));
298
+ stack.push({ indent: k.indent, key: k.key });
299
+ }
300
+ else {
301
+ loosen(pending);
302
+ }
303
+ pending = [];
304
+ }
305
+ if (at !== -1) {
306
+ // A ` #` on lines that do not parse on their own may be value text: never re-emit it.
307
+ if (owner && whole)
308
+ inline[commentPathKey(owner)] = line.slice(at);
309
+ else
310
+ unattached.push(line.slice(at).trim());
311
+ }
312
+ }
313
+ loosen(pending);
314
+ return { channel: { leading, trailing: [], inline }, unattached };
315
+ }
316
+ /**
317
+ * The comment post-condition of regenerating a changed key: every comment its original value
318
+ * lines held — full-line or inline — appears in the regenerated text beside the SAME key path
319
+ * (compared trimmed — `reconstructFrontmatter` re-indents a nested comment to its key's
320
+ * depth). Compared per exact path, never as a global count, so a comment dropped in one place
321
+ * can never be balanced by an identical one elsewhere. A comment that could not be re-attached
322
+ * is refused, never silently dropped (found while implementing #5105).
323
+ */
324
+ function assertCommentsKept(key, original, regenerated, budget) {
325
+ const placed = (c) => {
326
+ const counts = new Map();
327
+ const add = (entry) => { counts.set(entry, (counts.get(entry) ?? 0) + 1); };
328
+ for (const [path, lines] of Object.entries(c.channel.leading))
329
+ for (const l of lines)
330
+ add(JSON.stringify(['above', path, l.trim()]));
331
+ for (const [path, text] of Object.entries(c.channel.inline))
332
+ add(JSON.stringify(['beside', path, text.trim()]));
333
+ for (const text of c.unattached)
334
+ add(JSON.stringify(['loose', '', text]));
335
+ return counts;
336
+ };
337
+ const before = placed(original);
338
+ const after = placed(segmentComments(key, (0, text_lines_cjs_1.splitLines)(regenerated), budget));
339
+ const differs = (entry) => (before.get(entry) ?? 0) !== (after.get(entry) ?? 0);
340
+ const lost = [...before.keys()].filter(differs);
341
+ if (lost.length === 0 && ![...after.keys()].some(differs))
342
+ return;
343
+ const describe = (entry) => {
344
+ const [where, path, text] = JSON.parse(entry);
345
+ const keyPath = path === '' ? '' : JSON.parse(path).map((s) => JSON.stringify(s)).join(' › ');
346
+ return where === 'loose' ? `${JSON.stringify(text)} (not beside any key)` : `${JSON.stringify(text)} ${where} ${keyPath}`;
347
+ };
348
+ throw new FrontmatterWriteRefusedError('FRONTMATTER_COMMENT_WOULD_BE_LOST', `frontmatter: refusing to write — the new value of "${key}" cannot keep the comment(s) written ` +
349
+ `inside it (${(lost.length > 0 ? lost : [...after.keys()].filter(differs)).map(describe).join(', ')}): a ` +
350
+ 'comment above or beside a key the new value removes or no longer holds as a key, on or between list ' +
351
+ 'items, or after the last line of the value has nowhere to go. Edit the file directly.');
352
+ }
353
+ function spliceFrontmatter(content, newObj, { parseBudgetChars = SPLICE_PARSE_BUDGET_CHARS } = {}) {
354
+ if (!Number.isSafeInteger(parseBudgetChars) || parseBudgetChars < 0 || parseBudgetChars > SPLICE_PARSE_BUDGET_CHARS) {
355
+ throw new TypeError(`spliceFrontmatter: parseBudgetChars must be a non-negative safe integer no greater than ${SPLICE_PARSE_BUDGET_CHARS} (the option can only lower the limit), got ${String(parseBudgetChars)}`);
356
+ }
357
+ // The block is located through `frontmatterBlock` (the one fence owner's block), so the
358
+ // writer and every reader agree on where it is: BOM (#2977), CRLF, an empty block.
359
+ const located = frontmatterBlock(content);
360
+ if (located) {
361
+ const { bom, block: fmBlock, rest } = located;
362
+ const fmLines = (0, text_lines_cjs_1.splitLines)(fmBlock);
363
+ const innerLines = fmLines.slice(1, -1); // drop the opening `---` and closing `---`
364
+ const inner = innerLines.join('\n');
365
+ let originalParsed;
366
+ try {
367
+ originalParsed = extractFrontmatter(fmBlock);
368
+ }
369
+ catch {
370
+ originalParsed = unparseableResult();
371
+ }
372
+ // Fail closed on an unparseable block BEFORE anything else — never regenerate over it.
373
+ if (frontmatterBlockUnparseable(originalParsed, inner)) {
374
+ throw new FrontmatterWriteRefusedError('FRONTMATTER_UNPARSEABLE', 'frontmatter: refusing to write — the existing frontmatter block is not parseable YAML, so ' +
375
+ 'rewriting it would discard or hide the fields the author wrote. Fix the YAML syntax error in ' +
376
+ 'the frontmatter block first, then re-run.');
377
+ }
378
+ // Whole-document no-op guard: a true no-op returns content verbatim (byte-exact,
379
+ // including any formatting the lossy serializer would normalize). Compared on what the
380
+ // new values read back as, so `wave: 2` (a number) over `wave: 2` (text) is a no-op.
381
+ if (frontmatterDeepEqual(originalParsed, readBackProjection(newObj))) {
382
+ return content;
383
+ }
384
+ // Per-key identity preservation (#1572). `reconstructFrontmatter` is a deliberately
385
+ // lossy serializer — it cannot faithfully re-emit nested object-list items (e.g.
386
+ // must_haves.artifacts / .prohibitions, whose items are `{ path, provides }` /
387
+ // `{ statement, status }` maps; `extractFrontmatter` flattens those to scalar
388
+ // strings, so a round-trip drops `provides:` and collapses the list to a malformed
389
+ // inline array). For any top-level key whose value is STRUCTURALLY UNCHANGED between
390
+ // the original parse and `newObj`, preserve that key's ORIGINAL raw text verbatim;
391
+ // regenerate only keys that actually changed. This generalizes the whole-document
392
+ // no-op guard above to per-key fidelity, so mutating `wave` no longer destroys an
393
+ // unrelated `must_haves` block. Keys absent from the original (genuinely new) are
394
+ // regenerated and appended; keys absent from `newObj` are preserved (never silently
395
+ // deleted by a set/merge).
396
+ //
397
+ // A write never silently drops a line it did not parse: the preamble (e.g. a comment
398
+ // above the first key) and every segment's tail (blank lines, full-line comments) are
399
+ // re-emitted in place whatever happens to the key they sit next to.
400
+ // An adjacent empty block (`---\n---`) has NO lines between its fences, while a block
401
+ // holding one blank line has one empty line: both join to ''. Laying out '' yields that one
402
+ // blank line, so the empty block would gain a blank line above its first key (found while
403
+ // implementing #5105).
404
+ // One parse allowance for the whole call: every line classification below draws on it.
405
+ const budget = { remaining: parseBudgetChars, limit: parseBudgetChars };
406
+ const { preamble, segments } = innerLines.length === 0
407
+ ? { preamble: [], segments: [] }
408
+ : sliceFrontmatterLayout(inner, budget);
409
+ const hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
410
+ // Every parsed key must own exactly one key line, and every key line must be a parsed
411
+ // key — otherwise a per-key splice could emit a key twice or lose one. Refuse instead.
412
+ const parsedKeys = Object.keys(originalParsed);
413
+ const segmentKeys = segments.map((s) => s.key);
414
+ if (new Set(segmentKeys).size !== segmentKeys.length ||
415
+ segmentKeys.length !== parsedKeys.length ||
416
+ !segmentKeys.every((k) => hasOwn(originalParsed, k))) {
417
+ throw new FrontmatterWriteRefusedError('FRONTMATTER_KEYS_UNRECONCILABLE', 'frontmatter: refusing to write — the frontmatter block\'s top-level key lines cannot be matched ' +
418
+ 'one-to-one to its parsed keys (a duplicate key, an explicit "? key", a flow mapping, or a quoted ' +
419
+ 'key using a YAML-only escape), so rewriting it could duplicate or drop a key. Edit the file directly.');
420
+ }
421
+ const emitted = [...preamble];
422
+ const seen = new Set();
423
+ for (const seg of segments) {
424
+ seen.add(seg.key);
425
+ if (hasOwn(newObj, seg.key)) {
426
+ // Key is in newObj: preserve original raw text if structurally unchanged,
427
+ // otherwise regenerate. The key SET is defined by newObj — keys that were in
428
+ // the original but are absent from newObj are intentionally dropped (the real
429
+ // cmdSet/cmdMerge flow always passes the full merged object, so this only
430
+ // matters for direct unit callers and matches spliceFrontmatter's contract:
431
+ // the result frontmatter IS newObj).
432
+ if (frontmatterDeepEqual(readBackProjection(newObj[seg.key]), originalParsed[seg.key])) {
433
+ emitted.push(seg.raw); // unchanged → preserve original raw text verbatim
434
+ }
435
+ else {
436
+ // changed → regenerate (fail-closed on object-lists), keeping the tail. A value
437
+ // that is not written at all (null) regenerates to '' — no line, only the tail:
438
+ // the key is deleted, and the comments inside its value go with it (#3257 AC5).
439
+ // Otherwise every comment inside the value — full-line or inline — is re-emitted
440
+ // beside the key it belongs to or the write is refused.
441
+ const comments = segmentComments(seg.key, seg.body, budget);
442
+ const regenerated = regenerateFrontmatterKey(seg.key, newObj[seg.key], comments.channel);
443
+ if (regenerated !== '')
444
+ assertCommentsKept(seg.key, comments, regenerated, budget);
445
+ emitted.push(...(regenerated === '' ? [] : [regenerated]), ...seg.tail);
446
+ }
447
+ }
448
+ else {
449
+ // Parsed key absent from newObj → drop its body; its tail is not its value.
450
+ emitted.push(...seg.tail);
451
+ }
452
+ }
453
+ // Append genuinely-new keys not present in the original frontmatter.
454
+ for (const k of Object.keys(newObj)) {
455
+ if (!seen.has(k)) {
456
+ const regenerated = regenerateFrontmatterKey(k, newObj[k]);
457
+ if (regenerated !== '')
458
+ emitted.push(regenerated);
459
+ }
460
+ }
461
+ // Re-emit the whole block with the document's own line ending (the opening fence's, as
462
+ // the one fence owner reads it), so a CRLF document never gains LF-only lines.
463
+ const eol = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content)?.eol ?? '\n';
464
+ const block = ['---', ...emitted, '---'].join('\n').split('\n').join(eol);
465
+ return bom + verifyReadsBackAs(block + rest, newObj);
466
+ }
467
+ // No existing frontmatter — generate from scratch, fail-closed on unrepresentable values.
468
+ if (/\[object Object\]/.test(reconstructFrontmatter(newObj))) {
469
+ throw new Error('frontmatter: cannot faithfully serialize the requested frontmatter — it contains a nested ' +
470
+ 'object-list (e.g. must_haves.artifacts) the writer cannot represent. Edit the file directly.');
471
+ }
472
+ // Key by key through `regenerateFrontmatterKey`, so a key the parser would misread bare
473
+ // (`a: b`, `#x`, one holding a line break) is quoted exactly as on the existing-block path.
474
+ const keyLines = Object.keys(newObj)
475
+ .map((k) => regenerateFrontmatterKey(k, newObj[k]))
476
+ .filter((line) => line !== '');
477
+ // #2977: a leading BOM stays the document's first character, ahead of the new fence.
478
+ const bom = content.charCodeAt(0) === 0xFEFF ? content.slice(0, 1) : '';
479
+ return bom + verifyReadsBackAs(`---\n${keyLines.join('\n')}\n---\n\n` + content.slice(bom.length), newObj);
480
+ }
481
+ module.exports = {
482
+ spliceFrontmatter,
483
+ // The one owner of "may a writer splice this frontmatter block?" — callers catch
484
+ // `isFrontmatterWriteRefusal(err)` and surface `err.message`/`err.code`.
485
+ FrontmatterWriteRefusedError,
486
+ isFrontmatterWriteRefusal,
487
+ // The parse allowance past which `spliceFrontmatter` refuses with FRONTMATTER_TOO_COMPLEX —
488
+ // exported so its boundary can be exercised exactly.
489
+ SPLICE_PARSE_BUDGET_CHARS,
490
+ // `frontmatter.cts`'s set/merge #1660 lossy-field check (`objectListFieldWouldLoseData`)
491
+ // asks the writer what a key regenerates to and what a value reads back as.
492
+ regenerateFrontmatterKey,
493
+ readBackProjection,
494
+ };