@opengsd/gsd-core 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +85 -5
  4. package/agents/gsd-code-fixer.compact.md +1 -1
  5. package/agents/gsd-code-fixer.md +1 -1
  6. package/agents/gsd-code-reviewer.compact.md +5 -3
  7. package/agents/gsd-code-reviewer.md +8 -6
  8. package/agents/gsd-debug-session-manager.compact.md +1 -1
  9. package/agents/gsd-debug-session-manager.md +1 -1
  10. package/agents/gsd-debugger.md +2 -2
  11. package/agents/gsd-eval-auditor.compact.md +1 -1
  12. package/agents/gsd-eval-auditor.md +1 -1
  13. package/agents/gsd-executor.md +4 -4
  14. package/agents/gsd-intel-updater.compact.md +1 -1
  15. package/agents/gsd-intel-updater.md +1 -1
  16. package/agents/gsd-mempalace-curator.md +2 -2
  17. package/agents/gsd-phase-researcher.md +1 -1
  18. package/agents/gsd-plan-checker.md +7 -2
  19. package/agents/gsd-planner.md +3 -3
  20. package/agents/gsd-project-researcher.compact.md +1 -1
  21. package/agents/gsd-project-researcher.md +1 -1
  22. package/agents/gsd-research-synthesizer.compact.md +1 -1
  23. package/agents/gsd-research-synthesizer.md +1 -1
  24. package/agents/gsd-ui-auditor.compact.md +21 -30
  25. package/agents/gsd-ui-auditor.md +42 -51
  26. package/agents/gsd-ui-researcher.compact.md +1 -1
  27. package/agents/gsd-ui-researcher.md +1 -1
  28. package/agents/gsd-verifier.md +31 -9
  29. package/bin/install.js +126 -196
  30. package/commands/gsd/add-tests.md +6 -1
  31. package/commands/gsd/ai-integration-phase.md +6 -1
  32. package/commands/gsd/audit-fix.md +5 -0
  33. package/commands/gsd/audit-milestone.md +6 -1
  34. package/commands/gsd/autonomous.md +5 -0
  35. package/commands/gsd/capture.md +8 -4
  36. package/commands/gsd/code-review.md +7 -2
  37. package/commands/gsd/complete-milestone.md +4 -0
  38. package/commands/gsd/config.md +7 -3
  39. package/commands/gsd/debug.md +11 -7
  40. package/commands/gsd/discuss-phase.md +7 -3
  41. package/commands/gsd/docs-update.md +12 -7
  42. package/commands/gsd/eval-review.md +6 -1
  43. package/commands/gsd/execute-phase.md +12 -7
  44. package/commands/gsd/extract-learnings.md +5 -0
  45. package/commands/gsd/fast.md +4 -0
  46. package/commands/gsd/forensics.md +5 -1
  47. package/commands/gsd/graphify.md +10 -6
  48. package/commands/gsd/health.md +5 -0
  49. package/commands/gsd/help.md +7 -2
  50. package/commands/gsd/import.md +7 -3
  51. package/commands/gsd/inbox.md +5 -0
  52. package/commands/gsd/ingest-docs.md +5 -1
  53. package/commands/gsd/manager.md +6 -1
  54. package/commands/gsd/map-codebase.md +7 -3
  55. package/commands/gsd/mempalace-capture.md +5 -1
  56. package/commands/gsd/mempalace-recall.md +5 -1
  57. package/commands/gsd/milestone-summary.md +5 -1
  58. package/commands/gsd/mvp-phase.md +8 -3
  59. package/commands/gsd/new-milestone.md +6 -1
  60. package/commands/gsd/new-project.md +5 -0
  61. package/commands/gsd/next.md +6 -1
  62. package/commands/gsd/ns-context.md +4 -0
  63. package/commands/gsd/ns-ideate.md +4 -0
  64. package/commands/gsd/ns-manage.md +4 -0
  65. package/commands/gsd/ns-project.md +4 -0
  66. package/commands/gsd/ns-review.md +4 -0
  67. package/commands/gsd/ns-workflow.md +4 -0
  68. package/commands/gsd/onboard.md +6 -1
  69. package/commands/gsd/pause-work.md +5 -1
  70. package/commands/gsd/phase.md +8 -4
  71. package/commands/gsd/plan-phase.md +6 -1
  72. package/commands/gsd/plan-review-convergence.md +5 -1
  73. package/commands/gsd/pr-branch.md +4 -0
  74. package/commands/gsd/profile-user.md +5 -1
  75. package/commands/gsd/progress.md +6 -1
  76. package/commands/gsd/quick-batch.md +20 -8
  77. package/commands/gsd/quick.md +12 -7
  78. package/commands/gsd/review.md +5 -1
  79. package/commands/gsd/secure-phase.md +6 -1
  80. package/commands/gsd/ship.md +5 -0
  81. package/commands/gsd/sketch.md +7 -2
  82. package/commands/gsd/spec-phase.md +5 -1
  83. package/commands/gsd/spike.md +8 -3
  84. package/commands/gsd/surface.md +5 -1
  85. package/commands/gsd/thread.md +4 -0
  86. package/commands/gsd/ui-phase.md +6 -1
  87. package/commands/gsd/ui-review.md +6 -1
  88. package/commands/gsd/ultraplan-phase.md +5 -1
  89. package/commands/gsd/undo.md +5 -1
  90. package/commands/gsd/update.md +6 -2
  91. package/commands/gsd/validate-phase.md +6 -1
  92. package/commands/gsd/verify-work.md +6 -1
  93. package/commands/gsd/workspace.md +7 -3
  94. package/gsd-core/bin/gsd-tools.cjs +142 -56
  95. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  96. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  97. package/gsd-core/bin/lib/audit.cjs +78 -44
  98. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  99. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  100. package/gsd-core/bin/lib/capability-registry.cjs +183 -103
  101. package/gsd-core/bin/lib/capability-validator.cjs +16 -0
  102. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  103. package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
  104. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  105. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  106. package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
  107. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  108. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  109. package/gsd-core/bin/lib/commands.cjs +173 -170
  110. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  111. package/gsd-core/bin/lib/config.cjs +43 -12
  112. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  113. package/gsd-core/bin/lib/coverage.cjs +4 -8
  114. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  115. package/gsd-core/bin/lib/drift.cjs +177 -42
  116. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  117. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  118. package/gsd-core/bin/lib/frontmatter.cjs +413 -234
  119. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  120. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  121. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  122. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  123. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  124. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  125. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  126. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  127. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  128. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  129. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  130. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  131. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  132. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  133. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  134. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  135. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  136. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  137. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  138. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  139. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  140. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  141. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  142. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  143. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  144. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  145. package/gsd-core/bin/lib/init.cjs +162 -96
  146. package/gsd-core/bin/lib/install-engine.cjs +19 -35
  147. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  148. package/gsd-core/bin/lib/io.cjs +122 -3
  149. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  150. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  151. package/gsd-core/bin/lib/milestone.cjs +19 -1
  152. package/gsd-core/bin/lib/model-resolver.cjs +18 -18
  153. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  154. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  155. package/gsd-core/bin/lib/pattern.cjs +10 -0
  156. package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
  157. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  158. package/gsd-core/bin/lib/phase-id.cjs +1 -1
  159. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  160. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  161. package/gsd-core/bin/lib/phase.cjs +280 -80
  162. package/gsd-core/bin/lib/plan-document.cjs +93 -19
  163. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  164. package/gsd-core/bin/lib/planning-document.cjs +273 -40
  165. package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
  166. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  167. package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
  168. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  169. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  170. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  171. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  172. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  173. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  174. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  175. package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
  176. package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
  177. package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
  178. package/gsd-core/bin/lib/roadmap.cjs +169 -72
  179. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +91 -157
  180. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
  181. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  182. package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
  183. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +307 -591
  184. package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
  185. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  186. package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
  187. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  188. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  189. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  190. package/gsd-core/bin/lib/state-document.cjs +100 -22
  191. package/gsd-core/bin/lib/state.cjs +214 -105
  192. package/gsd-core/bin/lib/surface.cjs +2 -1
  193. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
  194. package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
  195. package/gsd-core/bin/lib/uat.cjs +424 -7
  196. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
  197. package/gsd-core/bin/lib/vendor/README.md +31 -9
  198. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  199. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  200. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  201. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  202. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  203. package/gsd-core/bin/lib/verification.cjs +1165 -314
  204. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  205. package/gsd-core/bin/lib/verify.cjs +286 -567
  206. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  207. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  208. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  209. package/gsd-core/bin/lib/workstream.cjs +11 -2
  210. package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
  211. package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
  212. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  213. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  214. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  215. package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
  216. package/gsd-core/references/execute-phase-response-language.md +1 -1
  217. package/gsd-core/references/gsd-run-resolver.md +1 -1
  218. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  219. package/gsd-core/references/offer-next.md +1 -1
  220. package/gsd-core/references/planning-config.md +1 -1
  221. package/gsd-core/references/spidr-splitting.md +1 -1
  222. package/gsd-core/references/tdd.md +10 -4
  223. package/gsd-core/references/verifier-phase-gates.md +5 -2
  224. package/gsd-core/references/verify-mvp-mode.md +2 -2
  225. package/gsd-core/references/workstream-flag.md +33 -3
  226. package/gsd-core/templates/README.md +1 -1
  227. package/gsd-core/templates/UAT.md +17 -1
  228. package/gsd-core/templates/config.json +2 -11
  229. package/gsd-core/templates/verification-report.md +1 -1
  230. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  231. package/gsd-core/workflows/add-backlog.md +1 -1
  232. package/gsd-core/workflows/add-phase.md +8 -7
  233. package/gsd-core/workflows/add-tests.md +3 -2
  234. package/gsd-core/workflows/add-todo.md +4 -3
  235. package/gsd-core/workflows/ai-integration-phase.md +3 -2
  236. package/gsd-core/workflows/audit-fix.md +1 -1
  237. package/gsd-core/workflows/audit-milestone.md +4 -3
  238. package/gsd-core/workflows/audit-uat.md +1 -1
  239. package/gsd-core/workflows/autonomous.md +28 -18
  240. package/gsd-core/workflows/check-todos.md +6 -5
  241. package/gsd-core/workflows/cleanup.md +1 -1
  242. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  243. package/gsd-core/workflows/code-review-fix.md +5 -4
  244. package/gsd-core/workflows/code-review.md +84 -90
  245. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  246. package/gsd-core/workflows/complete-milestone.md +12 -7
  247. package/gsd-core/workflows/debug.md +30 -5
  248. package/gsd-core/workflows/diagnose-issues.md +3 -2
  249. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  250. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  252. package/gsd-core/workflows/discuss-phase.md +4 -3
  253. package/gsd-core/workflows/do.md +1 -1
  254. package/gsd-core/workflows/docs-update.md +4 -3
  255. package/gsd-core/workflows/edit-phase.md +4 -3
  256. package/gsd-core/workflows/eval-review.md +5 -3
  257. package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
  258. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
  259. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  260. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  261. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  263. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  264. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  265. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  266. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  267. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  268. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  269. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  270. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  271. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  272. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  273. package/gsd-core/workflows/execute-phase.md +78 -170
  274. package/gsd-core/workflows/execute-plan.md +13 -19
  275. package/gsd-core/workflows/explore.md +2 -2
  276. package/gsd-core/workflows/extract-learnings.md +3 -2
  277. package/gsd-core/workflows/fast.md +1 -1
  278. package/gsd-core/workflows/forensics.md +1 -1
  279. package/gsd-core/workflows/graduation.md +1 -1
  280. package/gsd-core/workflows/health.md +2 -1
  281. package/gsd-core/workflows/import.md +3 -2
  282. package/gsd-core/workflows/inbox.md +1 -1
  283. package/gsd-core/workflows/ingest-docs.md +1 -1
  284. package/gsd-core/workflows/insert-phase.md +4 -3
  285. package/gsd-core/workflows/list-seeds.md +1 -1
  286. package/gsd-core/workflows/list-workspaces.md +1 -1
  287. package/gsd-core/workflows/manager.md +5 -3
  288. package/gsd-core/workflows/map-codebase.md +4 -3
  289. package/gsd-core/workflows/milestone-summary.md +3 -2
  290. package/gsd-core/workflows/mvp-phase.md +14 -14
  291. package/gsd-core/workflows/new-milestone.md +10 -10
  292. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  293. package/gsd-core/workflows/new-project.md +1 -1
  294. package/gsd-core/workflows/new-workspace.md +1 -1
  295. package/gsd-core/workflows/next.md +1 -1
  296. package/gsd-core/workflows/pause-work.md +2 -2
  297. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  298. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  299. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  300. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  301. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
  302. package/gsd-core/workflows/plan-phase.md +27 -18
  303. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  304. package/gsd-core/workflows/plant-seed.md +1 -1
  305. package/gsd-core/workflows/pr-branch.md +1 -1
  306. package/gsd-core/workflows/profile-user.md +1 -1
  307. package/gsd-core/workflows/progress.md +19 -49
  308. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
  309. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  310. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  311. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  312. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  313. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  314. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  315. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  316. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  317. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  318. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  319. package/gsd-core/workflows/quick-batch.md +15 -10
  320. package/gsd-core/workflows/quick.md +42 -31
  321. package/gsd-core/workflows/remove-phase.md +3 -2
  322. package/gsd-core/workflows/remove-workspace.md +1 -1
  323. package/gsd-core/workflows/resume-project.md +3 -2
  324. package/gsd-core/workflows/review.md +3 -2
  325. package/gsd-core/workflows/scan.md +3 -2
  326. package/gsd-core/workflows/secure-phase.md +11 -12
  327. package/gsd-core/workflows/settings-advanced.md +1 -1
  328. package/gsd-core/workflows/settings-integrations.md +1 -1
  329. package/gsd-core/workflows/settings.md +1 -1
  330. package/gsd-core/workflows/ship.md +12 -12
  331. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  332. package/gsd-core/workflows/sketch.md +1 -1
  333. package/gsd-core/workflows/smart-entry.md +1 -1
  334. package/gsd-core/workflows/spec-phase.md +1 -1
  335. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  336. package/gsd-core/workflows/spike.md +1 -1
  337. package/gsd-core/workflows/stats.md +1 -1
  338. package/gsd-core/workflows/sync-skills.md +1 -1
  339. package/gsd-core/workflows/thread.md +2 -2
  340. package/gsd-core/workflows/transition.md +13 -23
  341. package/gsd-core/workflows/ui-phase.md +5 -4
  342. package/gsd-core/workflows/ui-review.md +4 -3
  343. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  344. package/gsd-core/workflows/undo.md +1 -1
  345. package/gsd-core/workflows/validate-phase.md +10 -12
  346. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  347. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  348. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  349. package/gsd-core/workflows/verify-work.md +68 -59
  350. package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
  351. package/hooks/dist/gsd-check-update-worker.js +8 -0
  352. package/hooks/dist/gsd-check-update.js +8 -0
  353. package/hooks/dist/gsd-context-monitor.js +31 -8
  354. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  355. package/hooks/dist/gsd-secret-read-guard.js +147 -16
  356. package/hooks/dist/gsd-statusline.js +15 -7
  357. package/hooks/dist/gsd-update-banner.js +8 -0
  358. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  359. package/hooks/dist/gsd-workflow-guard.js +5 -4
  360. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  361. package/hooks/dist/lib/cli-exit.js +12 -0
  362. package/hooks/dist/lib/git-probe.js +17 -1
  363. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  364. package/hooks/gsd-agent-isolation-guard.js +8 -0
  365. package/hooks/gsd-check-update-worker.js +8 -0
  366. package/hooks/gsd-check-update.js +8 -0
  367. package/hooks/gsd-context-monitor.js +31 -8
  368. package/hooks/gsd-cursor-subagent-start.js +8 -0
  369. package/hooks/gsd-secret-read-guard.js +147 -16
  370. package/hooks/gsd-statusline.js +15 -7
  371. package/hooks/gsd-update-banner.js +8 -0
  372. package/hooks/gsd-windsurf-pre-write.js +11 -2
  373. package/hooks/gsd-workflow-guard.js +5 -4
  374. package/hooks/gsd-worktree-path-guard.js +6 -2
  375. package/hooks/hooks.json +5 -5
  376. package/hooks/lib/cli-exit.js +12 -0
  377. package/hooks/lib/git-probe.js +17 -1
  378. package/hooks/lib/isolation-sentinel.js +2 -2
  379. package/package.json +21 -4
  380. package/scripts/changeset/parse.cjs +52 -4
  381. package/scripts/ci-timeout-report.cjs +770 -4
  382. package/scripts/command-contract-helpers.cjs +12 -8
  383. package/scripts/docs-guard-registry.cjs +6 -0
  384. package/scripts/gen-features.cjs +13 -8
  385. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  386. package/scripts/gen-loop-host-contract.cjs +10 -1
  387. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  388. package/scripts/gen-plugin-skills.cjs +87 -1
  389. package/scripts/gen-research-agents.cjs +24 -31
  390. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  391. package/scripts/gen-test-timings.cjs +32 -7
  392. package/scripts/lib/cli-exit.cjs +12 -0
  393. package/scripts/lib/macos-conformance-tier.generated.cjs +20 -2
  394. package/scripts/lib/ndjson-reporter.cjs +28 -3
  395. package/scripts/lib/platform-conformance-tier.generated.cjs +34 -5
  396. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  397. package/scripts/lib/vendor-bundle.cjs +59 -0
  398. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  399. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  400. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  401. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  402. package/scripts/lint-descriptions.cjs +7 -3
  403. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
  404. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  405. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  406. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  407. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  408. package/scripts/lint-phase-id-drift.cjs +29 -0
  409. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  410. package/scripts/lint-response-language-coverage.cjs +3 -0
  411. package/scripts/lint-skill-deps.cjs +7 -3
  412. package/scripts/lint-test-file-count.allowlist.json +11 -1
  413. package/scripts/lint-test-file-count.cjs +34 -1
  414. package/scripts/lint-vendored-deps.cjs +41 -5
  415. package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
  416. package/scripts/mutation-matrix.cjs +50 -5
  417. package/scripts/require-issue-link-policy.cjs +6 -2
  418. package/scripts/sync-runtime-launcher.cjs +184 -2
  419. package/scripts/verify-npm-publish.cjs +76 -20
  420. package/skills/gsd-add-tests/SKILL.md +6 -1
  421. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  422. package/skills/gsd-audit-fix/SKILL.md +5 -0
  423. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  424. package/skills/gsd-autonomous/SKILL.md +5 -0
  425. package/skills/gsd-capture/SKILL.md +8 -4
  426. package/skills/gsd-code-review/SKILL.md +7 -2
  427. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  428. package/skills/gsd-config/SKILL.md +8 -4
  429. package/skills/gsd-debug/SKILL.md +11 -7
  430. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  431. package/skills/gsd-docs-update/SKILL.md +12 -7
  432. package/skills/gsd-eval-review/SKILL.md +6 -1
  433. package/skills/gsd-execute-phase/SKILL.md +12 -7
  434. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  435. package/skills/gsd-fast/SKILL.md +4 -0
  436. package/skills/gsd-forensics/SKILL.md +5 -1
  437. package/skills/gsd-graphify/SKILL.md +10 -6
  438. package/skills/gsd-health/SKILL.md +5 -0
  439. package/skills/gsd-help/SKILL.md +7 -2
  440. package/skills/gsd-import/SKILL.md +7 -3
  441. package/skills/gsd-inbox/SKILL.md +5 -0
  442. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  443. package/skills/gsd-manager/SKILL.md +6 -1
  444. package/skills/gsd-map-codebase/SKILL.md +7 -3
  445. package/skills/gsd-mempalace-capture/SKILL.md +5 -1
  446. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  447. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  448. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  449. package/skills/gsd-new-milestone/SKILL.md +6 -1
  450. package/skills/gsd-new-project/SKILL.md +5 -0
  451. package/skills/gsd-next/SKILL.md +6 -1
  452. package/skills/gsd-ns-context/SKILL.md +4 -0
  453. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  454. package/skills/gsd-ns-manage/SKILL.md +4 -0
  455. package/skills/gsd-ns-project/SKILL.md +4 -0
  456. package/skills/gsd-ns-review/SKILL.md +4 -0
  457. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  458. package/skills/gsd-onboard/SKILL.md +6 -1
  459. package/skills/gsd-pause-work/SKILL.md +5 -1
  460. package/skills/gsd-phase/SKILL.md +8 -4
  461. package/skills/gsd-plan-phase/SKILL.md +6 -1
  462. package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
  463. package/skills/gsd-pr-branch/SKILL.md +4 -0
  464. package/skills/gsd-profile-user/SKILL.md +5 -1
  465. package/skills/gsd-progress/SKILL.md +6 -1
  466. package/skills/gsd-quick/SKILL.md +16 -10
  467. package/skills/gsd-quick-batch/SKILL.md +20 -8
  468. package/skills/gsd-review/SKILL.md +5 -1
  469. package/skills/gsd-review-backlog/SKILL.md +3 -2
  470. package/skills/gsd-secure-phase/SKILL.md +6 -1
  471. package/skills/gsd-ship/SKILL.md +5 -0
  472. package/skills/gsd-sketch/SKILL.md +7 -2
  473. package/skills/gsd-spec-phase/SKILL.md +5 -1
  474. package/skills/gsd-spike/SKILL.md +8 -3
  475. package/skills/gsd-surface/SKILL.md +5 -1
  476. package/skills/gsd-thread/SKILL.md +4 -0
  477. package/skills/gsd-ui-phase/SKILL.md +6 -1
  478. package/skills/gsd-ui-review/SKILL.md +6 -1
  479. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  480. package/skills/gsd-undo/SKILL.md +5 -1
  481. package/skills/gsd-update/SKILL.md +6 -2
  482. package/skills/gsd-validate-phase/SKILL.md +6 -1
  483. package/skills/gsd-verify-work/SKILL.md +6 -1
  484. package/skills/gsd-workspace/SKILL.md +7 -3
  485. package/skills/gsd-workstreams/SKILL.md +6 -6
  486. package/vscode/package.json +1 -1
  487. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
@@ -61,7 +61,13 @@
61
61
  // segment's operands — `[ -f .env ]` and `ls .env*` are existence checks
62
62
  // GSD's own agents run — while `cp`/`mv`/`ln`/`git` are deliberately NOT
63
63
  // exempt (`cp .env x && cat x` launders the name; `git show HEAD:.env`
64
- // reads). A shell interpreter (bash/sh/zsh/dash/ksh/su) has its script scanned
64
+ // reads). Narrower carve-outs exempt single operand POSITIONS that are
65
+ // names, never contents, and keep checking the rest of the segment: the
66
+ // `--env-file` value under a container runtime (#4639), the pathspecs of
67
+ // `git check-ignore` / `git ls-files` / `git rm --cached`, and the
68
+ // destination of a one-source `cp`/`mv` (#4856). The #4856 carve-outs
69
+ // fail closed: an option they do not list withdraws them from the segment.
70
+ // A shell interpreter (bash/sh/zsh/dash/ksh/su) has its script scanned
65
71
  // whether it arrives via `-c '…'`, a `<( )` file operand, a heredoc /
66
72
  // here-string, or a pipe from a knowable `echo`/`printf` source
67
73
  // (`echo cat .env | bash`); `eval` scans its joined operands; `source`/`.`
@@ -91,7 +97,11 @@
91
97
  // container's own command can print the interpolated environment
92
98
  // (`alpine printenv`, `docker compose config`) — the same exposure class
93
99
  // as the pre-existing volume-mount gap (`-v .env:/s`); the flag's value
94
- // itself is a name, never contents.)
100
+ // itself is a name, never contents. #4856 adds another: the `cp`/`mv`
101
+ // destination exemption can move a link prepared under a non-secret name
102
+ // onto a secret name (`ln -s .env.example l && mv l .env`), so a later
103
+ // edit of `.env` lands in the link target — a write redirection, the
104
+ // out-of-scope write class, not a read.)
95
105
  //
96
106
  // Triggers on: Read, Grep, Bash tool calls (Kimi: ReadFile, Grep, Shell)
97
107
  // Action: BLOCK (decision: 'block', exit 2) — codes secret-read |
@@ -141,6 +151,57 @@ const NON_READING_COMMANDS = new Set([
141
151
  const CONTAINER_RUNTIMES = new Set(['docker', 'docker-compose', 'podman', 'nerdctl']);
142
152
  const ENV_FILE_FLAG_RE = /^--env-file(=|$)/;
143
153
 
154
+ // #4856: git subcommands that report or drop a path's ignore / tracking
155
+ // status and never print the file, so their pathspec operands are names —
156
+ // the category NON_READING_COMMANDS encodes. Each carries the closed set of
157
+ // its NO-VALUE options (from `git <subcommand> -h`, git 2.49). Any other
158
+ // option withdraws the exemption from the whole segment, so an option value
159
+ // is never mistaken for a pathspec: `-X <file>` reads the file, and
160
+ // `--pathspec-from-file=<file>` echoes its lines in the "did not match"
161
+ // error. `requires` names the option without which the subcommand stays
162
+ // checked (#4856 scopes `git rm` to `--cached`).
163
+ const GIT_PATHSPEC_SUBCOMMANDS = new Map([
164
+ ['check-ignore', {
165
+ flags: new Set(['-q', '--quiet', '-v', '--verbose', '--stdin', '-z', '-n', '--non-matching', '--no-index', '--index']),
166
+ }],
167
+ ['ls-files', {
168
+ flags: new Set([
169
+ '-z', '-t', '-v', '-f', '-c', '--cached', '-d', '--deleted', '-m', '--modified', '-o', '--others',
170
+ '-i', '--ignored', '-s', '--stage', '-k', '--killed', '-u', '--unmerged', '--directory', '--eol',
171
+ '--no-empty-directory', '--resolve-undo', '--exclude-standard', '--full-name',
172
+ '--recurse-submodules', '--error-unmatch', '--abbrev', '--debug', '--deduplicate', '--sparse',
173
+ ]),
174
+ }],
175
+ ['rm', {
176
+ flags: new Set(['--cached', '-f', '--force', '-n', '--dry-run', '-q', '--quiet', '-r', '--ignore-unmatch', '--sparse']),
177
+ requires: '--cached',
178
+ }],
179
+ ]);
180
+
181
+ // git's global options (its own usage line, git 2.49), needed to locate the
182
+ // subcommand. A value option consumes the next word unless written
183
+ // `--opt=value`; an option in neither set fails closed, so a value is never
184
+ // read as the subcommand (`git -C ls-files show HEAD:.env` runs `show`).
185
+ const GIT_GLOBAL_VALUE_OPTIONS = new Set(['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--config-env']);
186
+ const GIT_GLOBAL_FLAGS = new Set([
187
+ '-p', '--paginate', '-P', '--no-pager', '--no-replace-objects', '--no-lazy-fetch',
188
+ '--no-optional-locks', '--no-advice', '--bare',
189
+ ]);
190
+
191
+ // #4856: `cp`/`mv` write their destination and never print it, so a secret
192
+ // name that is ONLY the destination of a one-source copy or move is a name
193
+ // (`cp .env.example .env`). The exemption holds for exactly two operands
194
+ // behind options from this closed set; anything else keeps the whole segment
195
+ // checked — `-t`/`--target-directory` (every operand becomes a source), a
196
+ // backup (`-b`, `--backup`, `-S`: the old secret survives as `.env~`, a name
197
+ // the predicate does not classify), a link (`-l`, `-s`) or `--exchange`
198
+ // (the destination shares or swaps the secret). Options must precede the
199
+ // operands: a trailing one is an option under GNU getopt's permutation but
200
+ // an operand under POSIXLY_CORRECT (`cp a .env -f` then copies `.env` INTO
201
+ // `-f/`), so which word is the destination would hang on an environment
202
+ // the hook cannot see.
203
+ const COPY_NAME_ONLY_FLAGS = new Set(['-f', '--force', '-i', '--interactive', '-n', '--no-clobber', '-v', '--verbose']);
204
+
144
205
  // Shell interpreters that run a script from `-c`, a file operand, or stdin
145
206
  // (heredoc / here-string / piped `echo`|`printf`). `su` is here for its `-c`
146
207
  // form (`su [user] -c 'cmd'`); a bare `su user` resolves to file mode, which
@@ -714,6 +775,86 @@ function resolveCommand(words) {
714
775
  return { base: lastSegment(words[idx].text).toLowerCase(), operands: words.slice(idx + 1) };
715
776
  }
716
777
 
778
+ // Operand indices the segment's command consumes as a NAME, never as
779
+ // CONTENTS (#4639, #4856). Only these positions skip the operand check;
780
+ // every other operand is still checked, so a carve-out cannot launder a read
781
+ // elsewhere in the segment.
782
+ function nameOnlyOperandIndices(base, operands) {
783
+ if (CONTAINER_RUNTIMES.has(base)) return envFileValueIndices(operands);
784
+ if (base === 'git') return gitPathspecIndices(operands);
785
+ if (base === 'cp' || base === 'mv') return copyDestinationIndices(operands);
786
+ return new Set();
787
+ }
788
+
789
+ // #4639: the `--env-file` word, plus the next operand for the bare flag
790
+ // (`--env-file=<value>` is a single word).
791
+ function envFileValueIndices(operands) {
792
+ const exempt = new Set();
793
+ for (let k = 0; k < operands.length; k++) {
794
+ if (!ENV_FILE_FLAG_RE.test(operands[k].text)) continue;
795
+ exempt.add(k);
796
+ if (!operands[k].text.includes('=')) exempt.add(++k);
797
+ }
798
+ return exempt;
799
+ }
800
+
801
+ // True when `text` is a listed long option, or a short cluster (`-co`) whose
802
+ // every letter is listed. `--opt=value`, an abbreviation, or a cluster
803
+ // holding a value option (`-ciX`) is not.
804
+ function isListedFlag(text, flags) {
805
+ if (text.startsWith('--')) return flags.has(text);
806
+ return /^-[A-Za-z]+$/.test(text) && [...text.slice(1)].every((ch) => flags.has(`-${ch}`));
807
+ }
808
+
809
+ // Index of git's subcommand past its global options, or -1 when an option
810
+ // outside GIT_GLOBAL_VALUE_OPTIONS / GIT_GLOBAL_FLAGS precedes it.
811
+ function gitSubcommandIndex(operands) {
812
+ for (let k = 0; k < operands.length; k++) {
813
+ const t = operands[k].text;
814
+ if (!t.startsWith('-')) return k;
815
+ if (GIT_GLOBAL_VALUE_OPTIONS.has(t)) { k++; continue; }
816
+ if (GIT_GLOBAL_FLAGS.has(t)) continue;
817
+ const eq = t.indexOf('=');
818
+ const isLongValueForm = t.startsWith('--') && eq !== -1 && GIT_GLOBAL_VALUE_OPTIONS.has(t.slice(0, eq));
819
+ if (!isLongValueForm) return -1;
820
+ }
821
+ return -1;
822
+ }
823
+
824
+ // #4856: the pathspec operands of a GIT_PATHSPEC_SUBCOMMANDS invocation. git
825
+ // permutes options, so one may follow a pathspec; after `--` every word is a
826
+ // pathspec. The subcommand is matched case-sensitively, as git looks it up.
827
+ function gitPathspecIndices(operands) {
828
+ const sub = gitSubcommandIndex(operands);
829
+ const spec = sub === -1 ? undefined : GIT_PATHSPEC_SUBCOMMANDS.get(operands[sub].text);
830
+ if (!spec) return new Set();
831
+ const pathspecs = new Set();
832
+ let required = spec.requires === undefined;
833
+ let endOfOptions = false;
834
+ for (let k = sub + 1; k < operands.length; k++) {
835
+ const t = operands[k].text;
836
+ if (endOfOptions || !t.startsWith('-') || t === '-') pathspecs.add(k);
837
+ else if (t === '--') endOfOptions = true;
838
+ else if (!isListedFlag(t, spec.flags)) return new Set();
839
+ else if (t === spec.requires) required = true;
840
+ }
841
+ return required ? pathspecs : new Set();
842
+ }
843
+
844
+ // #4856: the destination of a one-source `cp`/`mv` (see COPY_NAME_ONLY_FLAGS).
845
+ function copyDestinationIndices(operands) {
846
+ const positional = [];
847
+ let endOfOptions = false;
848
+ for (let k = 0; k < operands.length; k++) {
849
+ const t = operands[k].text;
850
+ if (endOfOptions || !t.startsWith('-') || t === '-') positional.push(k);
851
+ else if (positional.length) return new Set(); // an option or `--` after an operand
852
+ else if (t === '--') endOfOptions = true;
853
+ else if (!isListedFlag(t, COPY_NAME_ONLY_FLAGS)) return new Set();
854
+ }
855
+ return positional.length === 2 ? new Set([positional[1]]) : new Set();
856
+ }
857
+
717
858
  // The statically-knowable stdin a segment writes: `echo`/`printf` operands
718
859
  // joined by a space (for `echo`, leading `-neE` flags dropped). Any other
719
860
  // source (`cat gen.sh | bash`, `curl … | sh`) is not knowable → null.
@@ -854,20 +995,10 @@ function findSecretRead(command, depth) {
854
995
 
855
996
  if (NON_READING_COMMANDS.has(base)) continue;
856
997
 
857
- // #4639: exempt ONLY the value of `--env-file`, and only when the
858
- // segment's command word is a container runtime. A bare `--env-file`
859
- // consumes the next operand; `--env-file=<value>` is a single word.
860
- const envFileExempt = CONTAINER_RUNTIMES.has(base);
861
- let skipNext = false;
862
- for (const w of operands) {
863
- if (envFileExempt) {
864
- if (skipNext) { skipNext = false; continue; }
865
- if (ENV_FILE_FLAG_RE.test(w.text)) {
866
- if (!w.text.includes('=')) skipNext = true; // bare flag consumes the next operand
867
- continue;
868
- }
869
- }
870
- if (namesSecret(normalizeOperand(w.text))) return w.text;
998
+ const nameOnly = nameOnlyOperandIndices(base, operands);
999
+ for (let k = 0; k < operands.length; k++) {
1000
+ if (nameOnly.has(k)) continue;
1001
+ if (namesSecret(normalizeOperand(operands[k].text))) return operands[k].text;
871
1002
  }
872
1003
  }
873
1004
  return null;
@@ -3,6 +3,14 @@
3
3
  // Claude Code Statusline - GSD Edition
4
4
  // Shows: model | current task (or GSD state) | directory | context usage
5
5
 
6
+ // #5183: Node's on-disk compile cache, so later runs reuse V8's compiled code
7
+ // for unchanged modules. Only as the entry (a test require() is unaffected),
8
+ // skipped under V8 coverage, optional (Bun stubs it), and never fatal.
9
+ // NODE_DISABLE_COMPILE_CACHE (any value) turns it off.
10
+ if (require.main === module && !process.env.NODE_V8_COVERAGE) {
11
+ try { require('node:module').enableCompileCache?.(); } catch { /* optimization only */ }
12
+ }
13
+
6
14
  const fs = require('fs');
7
15
  const path = require('path');
8
16
  const os = require('os');
@@ -42,6 +50,7 @@ if (require.main === module) {
42
50
  const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
43
51
  const { PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs');
44
52
  const { normalizeStateStatus } = require('../gsd-core/bin/lib/state-document.cjs');
53
+ const { locateFrontmatterFence } = require('../gsd-core/bin/lib/frontmatter-fence.cjs');
45
54
  const {
46
55
  renderBracketPhaseDisplay,
47
56
  renderBracketMilestoneDisplay,
@@ -250,13 +259,12 @@ function readGsdState(dir, opts = {}) {
250
259
  function parseStateMd(content) {
251
260
  const state = {};
252
261
 
253
- // YAML frontmatter between --- markers (anchored at file start).
254
- // #2754: \r?\n (not literal \n) so a CRLF STATE.md (Windows-authored) parses
255
- // identically to LF — pre-fix the literal-\n fence dropped the ENTIRE block.
256
- // Mirrors the CRLF-safe extractFrontmatter in src/frontmatter.cts.
257
- const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
258
- if (fmMatch) {
259
- const fm = fmMatch[1];
262
+ // YAML frontmatter: the block the one fence owner finds (`locateFrontmatterFence`), so a
263
+ // CRLF STATE.md (#2754), a BOM, and a `---` inside a value read exactly as every other
264
+ // STATE.md reader reads them (found while implementing #5105).
265
+ const fence = locateFrontmatterFence(content);
266
+ if (fence && fence.closed) {
267
+ const fm = content.slice(fence.openEnd, fence.bodyEnd);
260
268
  // Top-level scalar key: value
261
269
  for (const line of fm.split(/\r?\n/)) {
262
270
  const m = line.match(/^(\w+):\s*(.+)/);
@@ -12,6 +12,14 @@
12
12
 
13
13
  'use strict';
14
14
 
15
+ // #5183: Node's on-disk compile cache, so later runs reuse V8's compiled code
16
+ // for unchanged modules. Only as the entry (a test require() is unaffected),
17
+ // skipped under V8 coverage, optional (Bun stubs it), and never fatal.
18
+ // NODE_DISABLE_COMPILE_CACHE (any value) turns it off.
19
+ if (require.main === module && !process.env.NODE_V8_COVERAGE) {
20
+ try { require('node:module').enableCompileCache?.(); } catch { /* optimization only */ }
21
+ }
22
+
15
23
  const fs = require('fs');
16
24
  const path = require('path');
17
25
  const os = require('os');
@@ -31,14 +31,23 @@ const fs = require('fs');
31
31
  const path = require('path');
32
32
  const { spawnSync } = require('child_process');
33
33
  const { allow, deny } = require('./lib/hook-exit.js');
34
- const { reportIfUndetermined } = require('./lib/git-probe.js');
34
+ const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
35
35
 
36
36
  // #3911 (ADR-3889 Phase 7): the exit(2) call site (block(), below) is
37
37
  // migrated to hook-exit.js's deny(undefined, reason) — see
38
38
  // gsd-windsurf-pre-command.js's identical note for the fixed defect
39
39
  // (terminateNow's fd 1/fd 2 writes now run in independent try/catch blocks).
40
40
 
41
- const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000, windowsHide: true };
41
+ // Shared blocking-guard probe budget (hooks/lib/git-probe.js, #5180): a timed-out
42
+ // probe fails open, so it is sized for a starved host. Windsurf documents NO
43
+ // hook timeout (docs.devin.ai/desktop/cascade/hooks lists only an aspirational
44
+ // "sub-100ms" target) and this surface's registration entry
45
+ // (buildWindsurfHookEntry in src/runtime-hooks-surface.cts) carries none, so the
46
+ // host budget is UNKNOWN. This guard runs at most 3 sequential probes (15 s
47
+ // worst case); that worst case is NOT claimed to fit any host budget — if
48
+ // Windsurf kills the hook sooner, the host kill fails open exactly as a probe
49
+ // timeout does. The guard keeps its fail-open posture either way.
50
+ const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: BLOCKING_GUARD_PROBE_TIMEOUT_MS, windowsHide: true };
42
51
 
43
52
  function git(args, cwd) {
44
53
  return spawnSync('git', args, { ...SPAWNOPT, cwd });
@@ -22,7 +22,7 @@ const path = require('path');
22
22
  const { spawnSync } = require('child_process');
23
23
  const { tokenize, skipToSubcommand } = require('./lib/git-cmd.js');
24
24
  const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
25
- const { reportIfUndetermined } = require('./lib/git-probe.js');
25
+ const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
26
26
 
27
27
  // This guard is almost entirely advisory (fail open — a broken advisory must
28
28
  // never wedge every tool call), with ONE hard block (#3504 force-add-on-
@@ -87,11 +87,12 @@ function currentBranch(cwd) {
87
87
  stdio: ['ignore', 'pipe', 'ignore'],
88
88
  windowsHide: true,
89
89
  // #3504: bounded — this ran unbounded before, an indefinite hang under a
90
- // wedged git would hang every PreToolUse call. Host wiring allows a 5s
91
- // budget for the whole hook, so the probe gets 2s of it; a timeout
90
+ // wedged git would hang every PreToolUse call. The probe gets the shared
91
+ // blocking-guard budget (hooks/lib/git-probe.js, #5180), well inside the
92
+ // 120s host budget this guard is registered with (#3981); a timeout
92
93
  // returns '' (branch unknown), which both the block decision and the
93
94
  // fail-closed re-check treat as "cannot establish agent branch".
94
- timeout: 2000,
95
+ timeout: BLOCKING_GUARD_PROBE_TIMEOUT_MS,
95
96
  killSignal: 'SIGTERM',
96
97
  });
97
98
  // #3911: a timeout/spawn-failure here previously degraded to '' exactly
@@ -18,7 +18,7 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { spawnSync } = require('child_process');
20
20
  const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
21
- const { reportIfUndetermined } = require('./lib/git-probe.js');
21
+ const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
22
22
 
23
23
  // This guard's outer catch has always exited 0 (fail open): a path guard that
24
24
  // cannot resolve the worktree must not block the user's edit — its whole job
@@ -28,7 +28,11 @@ const { reportIfUndetermined } = require('./lib/git-probe.js');
28
28
  // rather than inheriting a default (#3911).
29
29
  const ON_CRASH = HOOK_ON_CRASH.ALLOW;
30
30
 
31
- const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000, windowsHide: true };
31
+ // A timed-out git probe fails OPEN (see ON_CRASH above), so the per-probe
32
+ // budget is the shared blocking-guard value, sized for a starved host rather
33
+ // than git's ~0.1s typical run (rationale and arithmetic in
34
+ // hooks/lib/git-probe.js, #5180). This guard runs at most 3 sequential probes.
35
+ const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: BLOCKING_GUARD_PROBE_TIMEOUT_MS, windowsHide: true };
32
36
 
33
37
  function git(args, cwd) {
34
38
  return spawnSync('git', args, { ...SPAWNOPT, cwd });
package/hooks/hooks.json CHANGED
@@ -12,32 +12,32 @@
12
12
  {
13
13
  "matcher": "Write|Edit",
14
14
  "hooks": [
15
- { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-prompt-guard.js\"", "timeout": 5 },
15
+ { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-prompt-guard.js\"", "timeout": 120 },
16
16
  { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-read-guard.js\"", "timeout": 5 }
17
17
  ]
18
18
  },
19
19
  {
20
20
  "matcher": "Write|Edit|MultiEdit",
21
21
  "hooks": [
22
- { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-worktree-path-guard.js\"", "timeout": 5 }
22
+ { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-worktree-path-guard.js\"", "timeout": 120 }
23
23
  ]
24
24
  },
25
25
  {
26
26
  "matcher": "Write",
27
27
  "hooks": [
28
- { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-write-guard.js\"", "timeout": 5 }
28
+ { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-write-guard.js\"", "timeout": 120 }
29
29
  ]
30
30
  },
31
31
  {
32
32
  "matcher": "Read|Grep|Bash",
33
33
  "hooks": [
34
- { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-secret-read-guard.js\"", "timeout": 5 }
34
+ { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-secret-read-guard.js\"", "timeout": 120 }
35
35
  ]
36
36
  },
37
37
  {
38
38
  "matcher": "Agent|Task",
39
39
  "hooks": [
40
- { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-agent-isolation-guard.js\"", "timeout": 5 }
40
+ { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-agent-isolation-guard.js\"", "timeout": 120 }
41
41
  ]
42
42
  }
43
43
  ],
@@ -185,6 +185,17 @@ function setPendingOutcome(v) {
185
185
  function getPendingOutcome() {
186
186
  return globalThis[PENDING_OUTCOME_KEY];
187
187
  }
188
+ /**
189
+ * Declare the outcome a void-returning `main()` ends with (#5170, ADR-5057 §4): writes the
190
+ * pending-outcome cell that `runMain` projects. Validates `name` is an outcome
191
+ * `projectOutcome` accepts (PASS, FAIL or a registered name) and THROWS otherwise, so a typo'd
192
+ * name fails at the declaration rather than at process exit. Call it AFTER `output()`, which
193
+ * rewrites the cell on every call. `output()` and this are the only writers of the cell.
194
+ */
195
+ function declareOutcome(name) {
196
+ projectOutcome(name, 'v1');
197
+ setPendingOutcome(name);
198
+ }
188
199
  const EXIT_CONTRACT_FLAG_PREFIX = '--exit-contract=';
189
200
  /** Scan argv for the FIRST `--exit-contract=<value>` token; undefined if absent. */
190
201
  function findExitContractFlag(argv) {
@@ -557,4 +568,5 @@ module.exports = {
557
568
  terminateNow,
558
569
  setPendingOutcome,
559
570
  getPendingOutcome,
571
+ declareOutcome,
560
572
  };
@@ -81,4 +81,20 @@ function reportIfUndetermined(hookName, probeLabel, result) {
81
81
  }
82
82
  }
83
83
 
84
- module.exports = { classifyGitProbe, reportIfUndetermined };
84
+ // Per-probe budget for the git probes of BLOCKING guards. A probe that times
85
+ // out fails OPEN (see reportIfUndetermined above), which is the same outcome
86
+ // as the host killing the hook — so a budget sized to git's ~0.1s typical run
87
+ // silently disables the gate on a starved host (a 2000ms budget was exceeded
88
+ // on a loaded Windows runner, #5180). Worst case for a guard that runs up to
89
+ // BLOCKING_GUARD_MAX_SEQUENTIAL_PROBES sequential probes:
90
+ // 3 x 5000ms + ~500ms node start/kill/reap = ~15.5s,
91
+ // inside the 120s host budget blocking guards are registered with (#3981).
92
+ const BLOCKING_GUARD_PROBE_TIMEOUT_MS = 5000;
93
+ const BLOCKING_GUARD_MAX_SEQUENTIAL_PROBES = 3;
94
+
95
+ module.exports = {
96
+ classifyGitProbe,
97
+ reportIfUndetermined,
98
+ BLOCKING_GUARD_PROBE_TIMEOUT_MS,
99
+ BLOCKING_GUARD_MAX_SEQUENTIAL_PROBES,
100
+ };
@@ -23,8 +23,8 @@
23
23
  // explicit fallback for the per-plan submodule-degrade override and shares
24
24
  // the exact same atomic-write implementation.
25
25
  //
26
- // Sentinel path: `<cwd>/.gsd/dispatch-isolation-sentinel.json`. `.gsd` is
27
- // gitignored (root `.gitignore`'s bare `.gsd` entry matches at any depth),
26
+ // Sentinel path: `<cwd>/.gsd/dispatch-isolation-sentinel.json`. The writer
27
+ // seeds `.gsd/.gitignore` so this stays ignored in user projects too,
28
28
  // and lives inside the checkout that ran the workflow — concurrent worktrees
29
29
  // of the same repo are separate directories on disk, so each gets its own
30
30
  // sentinel with no cross-worktree collision, no lock file needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengsd/gsd-core",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
5
5
  "main": ".opencode/plugins/gsd-core.js",
6
6
  "bin": {
@@ -29,6 +29,12 @@
29
29
  "!scripts/run-affected-tests.cjs",
30
30
  "!scripts/lint-no-adhoc-regex-escape.cjs",
31
31
  "!scripts/lint-allow-test-rule-refs.cjs",
32
+ "!scripts/lint-verify-lifecycle-writes.cjs",
33
+ "!scripts/lint-verify-lifecycle-writes.allowlist.json",
34
+ "!scripts/lint-evaluation-scope-drift.cjs",
35
+ "!scripts/lint-evaluation-scope-drift.allowlist.json",
36
+ "!scripts/lint-gate-evidence-drift.cjs",
37
+ "!scripts/lint-gate-positive-control.cjs",
32
38
  "pi",
33
39
  "vscode"
34
40
  ],
@@ -69,6 +75,7 @@
69
75
  "@stryker-mutator/tap-runner": "^9.6.1",
70
76
  "@types/node": "^22.19.19",
71
77
  "c8": "^11.0.0",
78
+ "esbuild": "0.28.2",
72
79
  "eslint": "^9.39.4",
73
80
  "eslint-plugin-n": "^17.24.0",
74
81
  "eslint-plugin-no-only-tests": "^3.4.0",
@@ -79,6 +86,8 @@
79
86
  "js-yaml": "^4.3.2",
80
87
  "mutation-testing-metrics": "^3.7.3",
81
88
  "re2js": "^2.8.6",
89
+ "saxes": "6.0.0",
90
+ "tap-parser": "18.3.4",
82
91
  "typescript": "^6.0.3",
83
92
  "typescript-eslint": "^8.60.0"
84
93
  },
@@ -121,10 +130,16 @@
121
130
  "lint": "eslint . --cache --cache-location node_modules/.cache/eslint/ --max-warnings 0",
122
131
  "lint:fix": "eslint . --fix",
123
132
  "lint:table-schema-drift": "node scripts/lint-table-schema-drift.cjs",
133
+ "lint:planning-document-positive-control": "node scripts/lint-planning-document-positive-control.cjs",
124
134
  "lint:frontmatter-scalar-broad-grep": "node scripts/lint-frontmatter-scalar-broad-grep.cjs",
125
135
  "lint:removed-but-needed": "node scripts/lint-removed-but-needed.cjs",
136
+ "lint:verify-lifecycle-writes": "node scripts/lint-verify-lifecycle-writes.cjs",
137
+ "lint:evaluation-scope-drift": "node scripts/lint-evaluation-scope-drift.cjs",
138
+ "lint:gate-evidence-drift": "node scripts/lint-gate-evidence-drift.cjs",
139
+ "lint:gate-positive-control": "node scripts/lint-gate-positive-control.cjs",
140
+ "lint:frontmatter-fence-drift": "node scripts/lint-frontmatter-fence-drift.cjs",
126
141
  "lint:response-language": "node scripts/lint-response-language-coverage.cjs",
127
- "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && npm run lint:retired-runtime-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-portable-timeout.cjs && node scripts/lint-portable-grep.cjs && node scripts/lint-allowed-tools-parity.cjs && node scripts/lint-phase-arg-assignment.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-pr-branch-pattern-drift.cjs && node scripts/lint-fix-has-regression-tests.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-unreachable-guard-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-slug-derivation-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs && node scripts/lint-vendored-deps.cjs && node scripts/lint-docs-guard-registration.cjs && node scripts/lint-source-test-name-collision.cjs && npm run lint:hooks-runtime-build-seam && node scripts/check-contract-drift.cjs && node scripts/lint-mutation-test-derivation-drift.cjs && node scripts/lint-seam-enforcement.cjs && node scripts/lint-workflow-shellcheck.cjs && npm run lint:response-language",
142
+ "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && npm run lint:retired-runtime-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-portable-timeout.cjs && node scripts/lint-portable-grep.cjs && node scripts/lint-allowed-tools-parity.cjs && node scripts/lint-phase-arg-assignment.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-planning-document-positive-control.cjs && node scripts/lint-pr-branch-pattern-drift.cjs && node scripts/lint-fix-has-regression-tests.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-unreachable-guard-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-slug-derivation-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs && node scripts/lint-vendored-deps.cjs && node scripts/lint-docs-guard-registration.cjs && node scripts/lint-source-test-name-collision.cjs && npm run lint:hooks-runtime-build-seam && node scripts/check-contract-drift.cjs && node scripts/lint-mutation-test-derivation-drift.cjs && node scripts/lint-seam-enforcement.cjs && node scripts/lint-workflow-shellcheck.cjs && npm run lint:response-language && node scripts/lint-verify-lifecycle-writes.cjs && node scripts/lint-frontmatter-fence-drift.cjs && node scripts/lint-evaluation-scope-drift.cjs && node scripts/lint-gate-evidence-drift.cjs && node scripts/lint-gate-positive-control.cjs",
128
143
  "lint:allow-test-rule-refs": "node scripts/lint-allow-test-rule-refs.cjs",
129
144
  "lint:regression-names": "node scripts/lint-regression-test-names.cjs",
130
145
  "lint:descriptions": "node scripts/lint-descriptions.cjs",
@@ -161,9 +176,11 @@
161
176
  "test:coverage:all": "npm run test:coverage",
162
177
  "test:mutation": "stryker run",
163
178
  "gen:features": "node scripts/gen-features.cjs",
164
- "gen:state-md-docs": "node scripts/gen-state-md-docs.cjs"
179
+ "gen:state-md-docs": "node scripts/gen-state-md-docs.cjs",
180
+ "lint:vendored-deps": "node scripts/lint-vendored-deps.cjs"
165
181
  },
166
182
  "allowScripts": {
167
- "fallow@2.70.0": true
183
+ "fallow@2.70.0": true,
184
+ "esbuild@0.28.2": true
168
185
  }
169
186
  }
@@ -86,10 +86,58 @@ function extractDocsExempt(body) {
86
86
  return { docsExempt: reason, body: cleaned };
87
87
  }
88
88
 
89
+ /**
90
+ * A self-contained copy of `locateFrontmatterFence` (src/frontmatter-fence.cts, the one
91
+ * frontmatter fence owner). Kept here, not required from the built
92
+ * `gsd-core/bin/lib/frontmatter-fence.cjs`, because this module runs before any build: the
93
+ * `changeset-lint` job in .github/workflows/changeset-required.yml checks out and runs
94
+ * `node scripts/changeset/lint.cjs` with no `npm ci` and no `build:lib`. Found while
95
+ * implementing #5105: tests/frontmatter-fence.test.cjs ("kept frontmatter fence copies agree
96
+ * with the owner") pins this copy to the owner over a fixture corpus and a property test, and
97
+ * scripts/lint-frontmatter-fence-drift.cjs allowlists exactly this function.
98
+ */
99
+ function locateFrontmatterFence(text) {
100
+ if (typeof text !== 'string') {
101
+ throw new TypeError(`locateFrontmatterFence: expected a string, got ${typeof text}`);
102
+ }
103
+ const closingFenceLine = /^---[ \t]*$/;
104
+ const lenientClosingFenceLine = /^-{4,}[ \t]*$/;
105
+ const bom = text.charCodeAt(0) === 0xfeff ? text.slice(0, 1) : '';
106
+ const start = bom.length;
107
+ let eol;
108
+ if (text.startsWith('---\r\n', start)) eol = '\r\n';
109
+ else if (text.startsWith('---\n', start)) eol = '\n';
110
+ else return null;
111
+ const openEnd = start + 3 + eol.length;
112
+ const closedAt = (lineStart, lineEnd) => {
113
+ let bodyEnd = openEnd;
114
+ if (lineStart > openEnd) {
115
+ bodyEnd = lineStart - 1;
116
+ if (bodyEnd > openEnd && text[bodyEnd - 1] === '\r') bodyEnd -= 1;
117
+ }
118
+ return { bom, eol, openEnd, closed: true, closingStart: lineStart, closingFenceEnd: lineEnd, bodyEnd };
119
+ };
120
+ let lenient = null;
121
+ let lineStart = openEnd;
122
+ while (lineStart <= text.length) {
123
+ const newline = text.indexOf('\n', lineStart);
124
+ const lineEnd = newline === -1 ? text.length : newline > lineStart && text[newline - 1] === '\r' ? newline - 1 : newline;
125
+ const line = text.slice(lineStart, lineEnd);
126
+ if (closingFenceLine.test(line)) return closedAt(lineStart, lineEnd);
127
+ if (lenient === null && lenientClosingFenceLine.test(line)) lenient = [lineStart, lineEnd];
128
+ if (newline === -1) break;
129
+ lineStart = newline + 1;
130
+ }
131
+ if (lenient !== null) return closedAt(lenient[0], lenient[1]);
132
+ return { bom, eol, openEnd, closed: false, closingStart: -1, closingFenceEnd: -1, bodyEnd: text.length };
133
+ }
134
+
89
135
  function parseFragment(src) {
90
- const fmMatch = src.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
91
- if (!fmMatch) return { ok: false, reason: FRAGMENT_ERROR.MISSING_FRONTMATTER };
92
- const [, fmBlock, body] = fmMatch;
136
+ const fence = locateFrontmatterFence(src);
137
+ if (!fence || !fence.closed) return { ok: false, reason: FRAGMENT_ERROR.MISSING_FRONTMATTER };
138
+ const fmBlock = src.slice(fence.openEnd, fence.bodyEnd);
139
+ // Everything past the closing fence line and its line ending.
140
+ const body = src.slice(fence.closingFenceEnd).replace(/^\r?\n/, '');
93
141
 
94
142
  const fields = {};
95
143
  for (const line of fmBlock.split(/\r?\n/)) {
@@ -137,4 +185,4 @@ function parseFragment(src) {
137
185
  return { ok: true, fragment: { type: fields.type, pr, body: visibleBody, docsExempt } };
138
186
  }
139
187
 
140
- module.exports = { parseFragment, extractDocsExempt, FRAGMENT_ERROR, ALLOWED_TYPES, DOCS_EXEMPT_RE };
188
+ module.exports = { parseFragment, extractDocsExempt, locateFrontmatterFence, FRAGMENT_ERROR, ALLOWED_TYPES, DOCS_EXEMPT_RE };