@opengsd/gsd-core 1.10.0 → 1.12.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 (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -17,7 +17,8 @@
17
17
  * - ./config-loader.cjs (loadConfig)
18
18
  * - ./configuration.cjs (CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
19
19
  * - ./model-profiles.cjs (MODEL_PROFILES, AGENT_TO_PHASE_TYPE, AGENT_DEFAULT_TIERS, VALID_AGENT_TIERS, nextTier)
20
- * - ./model-catalog.cjs (MODEL_ALIAS_MAP, RUNTIME_PROFILE_MAP, PROVIDER_PRESETS, VALID_TIERS)
20
+ * - ./model-catalog.cjs (MODEL_ALIAS_MAP, RUNTIME_PROFILE_MAP, PROVIDER_PRESETS, VALID_TIERS,
21
+ * CLAUDE_AGENT_ALIASES — re-exported below for back-compat, #3241)
21
22
  */
22
23
  var __importDefault = (this && this.__importDefault) || function (mod) {
23
24
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -34,6 +35,7 @@ const model_catalog_cjs_1 = require("./model-catalog.cjs");
34
35
  const node_fs_1 = __importDefault(require("node:fs"));
35
36
  const node_path_1 = __importDefault(require("node:path"));
36
37
  const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
38
+ const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
37
39
  // eslint-disable-next-line @typescript-eslint/no-require-imports
38
40
  const planningWorkspaceMod = require("./planning-workspace.cjs");
39
41
  const { planningDir } = planningWorkspaceMod;
@@ -57,38 +59,19 @@ const { planningDir } = planningWorkspaceMod;
57
59
  // registry-parity test guards this set so a future alias-capable runtime fails
58
60
  // loudly here instead of silently omitting.
59
61
  const RUNTIMES_WITH_NATIVE_ALIASES = new Set(['claude']);
60
- let _installMarkerCache;
61
- function readInstallRuntimeMarker() {
62
- if (_installMarkerCache !== undefined)
63
- return _installMarkerCache;
64
- try {
65
- const markerPath = node_path_1.default.join(__dirname, '..', '..', '.gsd-runtime');
66
- const raw = node_fs_1.default.readFileSync(markerPath, 'utf8').trim();
67
- _installMarkerCache = raw || null;
68
- }
69
- catch {
70
- // No marker: dev/source tree, or an install predating #2297. Fall through to
71
- // the 'claude' default (keeps tier aliases — never worse than the bug).
72
- _installMarkerCache = null;
73
- }
74
- return _installMarkerCache;
75
- }
76
- // Test seams for the install-marker rung (the dev/source tree has no marker, so
77
- // the file read always bottoms out at 'claude' — these let tests exercise the
78
- // third precedence rung and reset the module-level cache between cases).
79
- function _setInstallRuntimeMarkerForTests(value) {
80
- _installMarkerCache = value;
81
- }
82
- function _resetInstallRuntimeMarkerCacheForTests() {
83
- _installMarkerCache = undefined;
84
- }
62
+ // #3897 rung 2: the marker reader + its cache and test seams were promoted to
63
+ // the canonical owner, `runtime-slash.cts` (imported above) — this module now
64
+ // consumes that single implementation instead of holding its own copy. N5:
65
+ // behaviour and the seam contract are unchanged by the move; the re-exports
66
+ // below (`export =` at the bottom of this file) preserve every existing
67
+ // caller's `require('./model-resolver.cjs')` surface byte-for-behaviour.
85
68
  // The runtime whose install is actually resolving, canonicalized so an alias or
86
69
  // case variant (e.g. "claude-code"/"Claude") cannot defeat the native-alias
87
70
  // check below (#2297 review). Precedence mirrors resolveRuntime()
88
71
  // (runtime-slash.cts): GSD_RUNTIME env → project config.runtime → per-install
89
72
  // .gsd-runtime marker → 'claude'.
90
73
  function resolveActiveRuntime(config) {
91
- return (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(process.env['GSD_RUNTIME'], config['runtime'], readInstallRuntimeMarker()) || 'claude';
74
+ return (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(process.env['GSD_RUNTIME'], config['runtime'], (0, runtime_slash_cjs_1.readInstallRuntimeMarker)()) || 'claude';
92
75
  }
93
76
  // Did the PROJECT's own config (root `.planning/config.json` or the active
94
77
  // workstream/project override) explicitly set resolve_model_ids to "omit"?
@@ -153,7 +136,9 @@ const CLAUDE_POLICY_ID_TO_ALIAS = {
153
136
  .map(([aliasName, id]) => [id, aliasName])),
154
137
  'claude-fable-5': 'fable',
155
138
  };
156
- const CLAUDE_AGENT_ALIASES = new Set(['opus', 'sonnet', 'haiku', 'fable']);
139
+ // CLAUDE_AGENT_ALIASES moved to ./model-catalog.cts (#3241) — imported above
140
+ // and re-exported below for back-compat (bin/install.js:474,
141
+ // tests/codex-config.test.cjs:24 depend on the name being on this module).
157
142
  // Dedupe stderr warnings so repeated agent resolutions don't spam (#1133).
158
143
  const _modelPolicyUnmappableWarned = new Set();
159
144
  function warnModelPolicyUnmappable(agentType, policyModel, tier) {
@@ -216,7 +201,7 @@ function mapClaudeOverrideForRuntime(override, configRuntime, agentType) {
216
201
  if (Object.hasOwn(CLAUDE_POLICY_ID_TO_ALIAS, override)) {
217
202
  return CLAUDE_POLICY_ID_TO_ALIAS[override];
218
203
  }
219
- if (CLAUDE_AGENT_ALIASES.has(override))
204
+ if (model_catalog_cjs_1.CLAUDE_AGENT_ALIASES.has(override))
220
205
  return override;
221
206
  if (override.startsWith('claude-')) {
222
207
  warnModelOverrideUnmappable(agentType, override);
@@ -278,33 +263,161 @@ function resolveModelPolicy(policy, tier) {
278
263
  return null;
279
264
  return budgetEntry.model;
280
265
  }
266
+ /**
267
+ * #2229 — the profile/phase-type tier for (config, agentType).
268
+ *
269
+ * Extracted verbatim from resolveModelInternal's step 2 so the same expression can
270
+ * answer "which tier did GSD resolve?" without also resolving a model id. The
271
+ * extraction is behaviour-preserving by construction: resolveModelInternal calls
272
+ * straight back into it.
273
+ *
274
+ * Returns null when the agent has no catalog entry and the profile is not `inherit`.
275
+ */
276
+ function computeProfileTier(config, agentType) {
277
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
278
+ const profile = String(config['model_profile'] || 'balanced').toLowerCase();
279
+ // Own-property guard: agentType is an unvalidated CLI positional (the
280
+ // `resolve-model <agent-type>` argument is never checked against a known
281
+ // agent list), so a prototype-chain agentType ("toString", "constructor")
282
+ // would otherwise return an inherited member from this plain object
283
+ // instead of undefined — verified reachable purely via the CLI.
284
+ const modelProfilesMap = MODEL_PROFILES;
285
+ const agentModels = Object.hasOwn(modelProfilesMap, agentType) ? modelProfilesMap[agentType] : undefined;
286
+ const phaseType = (AGENT_TO_PHASE_TYPE)[agentType];
287
+ const configModels = config['models'];
288
+ const phaseTypeTier = (phaseType && configModels && typeof configModels === 'object')
289
+ ? configModels[phaseType]
290
+ : undefined;
291
+ return (phaseTypeTier && model_catalog_cjs_1.VALID_TIERS.has(phaseTypeTier))
292
+ ? phaseTypeTier
293
+ : (profile === 'inherit'
294
+ ? 'inherit'
295
+ : (agentModels
296
+ // Own-property guard: `profile` is a config-supplied string
297
+ // (config['model_profile'], lower-cased); an already-lowercase
298
+ // prototype-chain key ("constructor", "__proto__") would otherwise
299
+ // return an inherited non-string member instead of falling back to
300
+ // 'balanced' (verified: profile:"constructor"/"__proto__" leaked a
301
+ // function/object through both the tier and model resolution paths).
302
+ ? ((Object.hasOwn(agentModels, profile) ? agentModels[profile] : undefined) || agentModels['balanced'])
303
+ : null));
304
+ }
305
+ /**
306
+ * #2229 — the effective model TIER for (config, agentType), as a signal a workflow can
307
+ * read: `gsd_run query resolve-model <agent> --pick tier`.
308
+ *
309
+ * Why this is not just "look at the resolved model": on every runtime the installer
310
+ * configures with `resolve_model_ids: "omit"` — which is every non-Claude runtime, see
311
+ * docs/CONFIGURATION.md — resolveModelInternal deliberately returns '' below. A guard
312
+ * keyed on the model id therefore cannot tell a budget-tier run from a top-tier one
313
+ * there, while the tier itself is computed ABOVE that early-return and stays knowable.
314
+ *
315
+ * Honesty contract — this never guesses, because a guard that reports a wrong tier is
316
+ * worse than one that reports none:
317
+ * - a per-agent `model_overrides` pin naming a known alias (or a full Claude id that
318
+ * maps to one) reports that alias;
319
+ * - a pin that maps to nothing reports 'unknown' — a raw model id carries no tier;
320
+ * - `model_profile: inherit` reports 'inherit' — the session model is not ours to name;
321
+ * - an agent with no catalog entry reports 'unknown'.
322
+ *
323
+ * Callers must treat 'unknown' and 'inherit' as "cannot tell", never as "adequate".
324
+ */
325
+ function resolveTierFromConfig(config, agentType) {
326
+ const rawOverrides = config['model_overrides'];
327
+ const modelOverrides = (rawOverrides && typeof rawOverrides === 'object' && !Array.isArray(rawOverrides))
328
+ ? rawOverrides
329
+ : null;
330
+ // Own-property guard: agentType is a caller-supplied string (the
331
+ // `resolve-model <agent-type>` CLI positional is not validated against a
332
+ // known agent list); a prototype-chain agentType ("toString",
333
+ // "constructor") against ANY model_overrides object — even `{}` — would
334
+ // otherwise return an inherited member instead of undefined.
335
+ const override = (modelOverrides && Object.hasOwn(modelOverrides, agentType))
336
+ ? modelOverrides[agentType]
337
+ : undefined;
338
+ if (override && typeof override === 'string') {
339
+ if (model_catalog_cjs_1.CLAUDE_AGENT_ALIASES.has(override))
340
+ return override;
341
+ // Own-property guard: this indexes a plain object with a config-supplied
342
+ // string, so a prototype-chain key ("toString", "constructor", "valueOf")
343
+ // would otherwise return an inherited member instead of undefined — and a
344
+ // function-valued tier is dropped entirely by JSON.stringify, silently
345
+ // removing the key a guard depends on.
346
+ const alias = Object.hasOwn(CLAUDE_POLICY_ID_TO_ALIAS, override)
347
+ ? CLAUDE_POLICY_ID_TO_ALIAS[override]
348
+ : undefined;
349
+ if (typeof alias === 'string' && alias)
350
+ return alias;
351
+ return 'unknown';
352
+ }
353
+ const profileTier = computeProfileTier(config, agentType);
354
+ // #3282 — mirror resolveModelInternal's step 2.5 (model_policy preset). The
355
+ // profile tier alone under-reports: model_policy can dispatch a DIFFERENT
356
+ // tier than the profile implies (e.g. a `balanced` profile's "sonnet" tier
357
+ // combined with `model_policy: {budget: 'low'}` actually spawns "haiku"),
358
+ // and reporting the profile tier there is exactly the under-report this
359
+ // fixes — a haiku-tier run must never be reported as "sonnet". Skipped
360
+ // under the same condition resolveModelInternal skips it (no tier, or
361
+ // "inherit" — the session model is not ours to name).
362
+ if (profileTier && profileTier !== 'inherit') {
363
+ const mergedPolicy = config['model_policy']
364
+ ? { ...config['model_policy'], runtime: config['runtime'] || 'claude' }
365
+ : null;
366
+ const policyModel = resolveModelPolicy(mergedPolicy, profileTier);
367
+ if (policyModel) {
368
+ // Map the policy-resolved id back to a tier alias with the same
369
+ // own-property-guarded lookups used above. If it maps, that alias IS
370
+ // the tier that actually runs — report it (the fix). If it does not
371
+ // map — including every non-Claude runtime, where resolveModelInternal
372
+ // returns the policy model verbatim with no tier meaning — the model
373
+ // carries no tier we can name; report 'unknown' rather than falling
374
+ // back to the profile tier, which would silently reintroduce the
375
+ // under-report this block exists to close.
376
+ const aliasForId = Object.hasOwn(CLAUDE_POLICY_ID_TO_ALIAS, policyModel)
377
+ ? CLAUDE_POLICY_ID_TO_ALIAS[policyModel]
378
+ : undefined;
379
+ if (typeof aliasForId === 'string' && aliasForId)
380
+ return aliasForId;
381
+ if (model_catalog_cjs_1.CLAUDE_AGENT_ALIASES.has(policyModel))
382
+ return policyModel;
383
+ return 'unknown';
384
+ }
385
+ }
386
+ return profileTier || 'unknown';
387
+ }
388
+ function resolveTierInternal(cwd, agentType) {
389
+ return resolveTierFromConfig(loadConfig(cwd), agentType);
390
+ }
281
391
  function resolveModelInternal(cwd, agentType) {
282
392
  const config = loadConfig(cwd);
283
393
  // 1. Per-agent override (#2041: map Claude full IDs → Agent-tool aliases on
284
394
  // the claude runtime, mirroring the model_policy path #1144; non-Claude
285
395
  // runtimes and non-Claude values pass through verbatim).
286
396
  const modelOverrides = config['model_overrides'];
287
- const override = modelOverrides?.[agentType];
397
+ // Own-property guard (see resolveTierFromConfig above): without it, an
398
+ // agentType of "toString" against `model_overrides: {}` returned the
399
+ // inherited Function.prototype.toString as the resolved "model" — verified
400
+ // reachable purely via the CLI, no override value needed.
401
+ const override = (modelOverrides && Object.hasOwn(modelOverrides, agentType))
402
+ ? modelOverrides[agentType]
403
+ : undefined;
288
404
  if (override) {
289
405
  const mapped = mapClaudeOverrideForRuntime(override, config['runtime'], agentType);
290
406
  if (mapped !== null)
291
407
  return mapped;
292
408
  // Unmappable Claude ID — fall through to tier resolution (matches model_policy).
293
409
  }
294
- // 2. Compute the tier
410
+ // 2. Compute the tier (#2229: shared with resolveTierFromConfig so the tier a
411
+ // workflow reads and the tier a model is resolved from can never diverge).
295
412
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
296
413
  const profile = String(config['model_profile'] || 'balanced').toLowerCase();
297
- const agentModels = MODEL_PROFILES[agentType];
298
- const phaseType = (AGENT_TO_PHASE_TYPE)[agentType];
299
- const configModels = config['models'];
300
- const phaseTypeTier = (phaseType && configModels && typeof configModels === 'object')
301
- ? configModels[phaseType]
302
- : undefined;
303
- const tier = (phaseTypeTier && model_catalog_cjs_1.VALID_TIERS.has(phaseTypeTier))
304
- ? phaseTypeTier
305
- : (profile === 'inherit'
306
- ? 'inherit'
307
- : (agentModels ? (agentModels[profile] || agentModels['balanced']) : null));
414
+ // Own-property guard (see computeProfileTier above): without it, agentType
415
+ // "toString" returned Function.prototype.toString as `agentModels`
416
+ // (truthy), which skipped the "unknown agent" fallback below and made
417
+ // resolveModelInternal return undefined instead of a tier-derived string.
418
+ const modelProfilesMapForModel = MODEL_PROFILES;
419
+ const agentModels = Object.hasOwn(modelProfilesMapForModel, agentType) ? modelProfilesMapForModel[agentType] : undefined;
420
+ const tier = computeProfileTier(config, agentType);
308
421
  // 2.5. model_policy preset (#49, #1133)
309
422
  const configRuntime = config['runtime'];
310
423
  if (tier && tier !== 'inherit') {
@@ -320,11 +433,13 @@ function resolveModelInternal(cwd, agentType) {
320
433
  return policyModel;
321
434
  // Claude Code's Agent tool takes tier aliases (opus/sonnet/haiku/fable),
322
435
  // not full model IDs — map the policy-resolved ID back to an alias (#1133).
323
- const aliasForId = CLAUDE_POLICY_ID_TO_ALIAS[policyModel];
324
- if (aliasForId)
436
+ const aliasForId = Object.hasOwn(CLAUDE_POLICY_ID_TO_ALIAS, policyModel)
437
+ ? CLAUDE_POLICY_ID_TO_ALIAS[policyModel]
438
+ : undefined;
439
+ if (typeof aliasForId === 'string' && aliasForId)
325
440
  return aliasForId;
326
441
  // The policy value may already be a bare Claude agent alias (e.g. "fable").
327
- if (CLAUDE_AGENT_ALIASES.has(policyModel))
442
+ if (model_catalog_cjs_1.CLAUDE_AGENT_ALIASES.has(policyModel))
328
443
  return policyModel;
329
444
  // No Claude alias for this ID (e.g. a pinned minor version like
330
445
  // claude-opus-4-5). Warn once and fall through to the tier alias rather
@@ -415,7 +530,13 @@ function resolveModelForTier(cwd, agentType, attempt) {
415
530
  const config = loadConfig(cwd);
416
531
  const attemptN = Number.isInteger(attempt) && attempt > 0 ? attempt : 0;
417
532
  const modelOverrides = config['model_overrides'];
418
- const override = modelOverrides?.[agentType];
533
+ // Own-property guard (see resolveTierFromConfig above): without it, an
534
+ // agentType of "toString" against `model_overrides: {}` returned the
535
+ // inherited Function.prototype.toString as the resolved "model" — verified
536
+ // reachable purely via the CLI, no override value needed.
537
+ const override = (modelOverrides && Object.hasOwn(modelOverrides, agentType))
538
+ ? modelOverrides[agentType]
539
+ : undefined;
419
540
  if (override) {
420
541
  const mapped = mapClaudeOverrideForRuntime(override, config['runtime'], agentType);
421
542
  if (mapped !== null)
@@ -533,7 +654,12 @@ function resolveProviderEscalation(cwd, agentType, attempt, applicable) {
533
654
  }
534
655
  // ─── #443 — Unified effort + fast_mode resolvers ─────────────────────────────
535
656
  const VALID_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
536
- const EFFORT_SET = new Set(VALID_EFFORTS);
657
+ // #3533 (10d): the VOCABULARY carries one more member than the LADDER —
658
+ // 'inherit' is a declarable effort choice ("follow the session", expressed by
659
+ // OMITTING the effort key at the writer) but not a level nextEffort may step
660
+ // into. Keeping it out of VALID_EFFORTS means escalation (resolveEffortForTier)
661
+ // never walks past an explicit inherit: nextEffort('inherit') is null.
662
+ const EFFORT_SET = new Set([...VALID_EFFORTS, 'inherit']);
537
663
  /**
538
664
  * Walk one step up the effort ladder from `e`.
539
665
  */
@@ -576,26 +702,23 @@ function resolveEffortInternal(cwd, agentType, opts) {
576
702
  }
577
703
  }
578
704
  // Step 3: routing_tier_defaults by agent's default tier.
705
+ // #3531 (10c): the config block merges OVER the manifest tier defaults
706
+ // rather than replacing them — an effort block without
707
+ // routing_tier_defaults (or missing this agent's tier) falls back to the
708
+ // manifest built-in for that tier instead of skipping to effort.default.
709
+ // Invalid config values are dropped by the merge, so the manifest value for
710
+ // the tier surfaces (the same "invalid falls through" rule every layer has).
579
711
  const agentTier = (AGENT_DEFAULT_TIERS)[agentType];
580
712
  if (agentTier) {
581
- if (effortCfg && effortCfg['routing_tier_defaults'] &&
582
- typeof effortCfg['routing_tier_defaults'] === 'object' &&
583
- !Array.isArray(effortCfg['routing_tier_defaults'])) {
584
- const v = effortCfg['routing_tier_defaults'][agentTier];
585
- if (typeof v === 'string' && EFFORT_SET.has(v))
586
- return v;
587
- }
588
- else if (!effortCfg) {
589
- const canonicalEffort = (configuration_cjs_1.CONFIG_DEFAULTS)['effort'];
590
- const manifestDefaults = canonicalEffort && typeof canonicalEffort === 'object'
591
- ? canonicalEffort['routing_tier_defaults']
592
- : undefined;
593
- if (manifestDefaults && typeof manifestDefaults === 'object') {
594
- const v = manifestDefaults[agentTier];
595
- if (typeof v === 'string' && EFFORT_SET.has(v))
596
- return v;
597
- }
598
- }
713
+ const canonicalEffort = (configuration_cjs_1.CONFIG_DEFAULTS)['effort'];
714
+ const manifestDefaults = canonicalEffort && typeof canonicalEffort === 'object'
715
+ ? canonicalEffort['routing_tier_defaults']
716
+ : undefined;
717
+ const isValidEffort = (v) => typeof v === 'string' && EFFORT_SET.has(v);
718
+ const merged = (0, model_catalog_cjs_1.mergeEffortTierDefaults)(manifestDefaults, effortCfg ? effortCfg['routing_tier_defaults'] : undefined, isValidEffort);
719
+ const v = merged[agentTier];
720
+ if (isValidEffort(v))
721
+ return v;
599
722
  }
600
723
  // Step 4: effort.default
601
724
  if (effortCfg) {
@@ -693,13 +816,15 @@ function resolveEffortForTier(cwd, agentType, attempt) {
693
816
  }
694
817
  module.exports = {
695
818
  resolveTierEntry,
696
- CLAUDE_AGENT_ALIASES,
819
+ CLAUDE_AGENT_ALIASES: model_catalog_cjs_1.CLAUDE_AGENT_ALIASES,
697
820
  resolveModelPolicy,
698
821
  resolveModelInternal,
822
+ resolveTierInternal,
823
+ resolveTierFromConfig,
699
824
  _resetModelPolicyWarningCacheForTests,
700
825
  _resetModelOverrideWarningCacheForTests,
701
- _setInstallRuntimeMarkerForTests,
702
- _resetInstallRuntimeMarkerCacheForTests,
826
+ _setInstallRuntimeMarkerForTests: runtime_slash_cjs_1._setInstallRuntimeMarkerForTests,
827
+ _resetInstallRuntimeMarkerCacheForTests: runtime_slash_cjs_1._resetInstallRuntimeMarkerCacheForTests,
703
828
  VALID_GRANULARITIES,
704
829
  resolveGranularityInternal,
705
830
  assertValidGranularityOverride,
@@ -23,7 +23,7 @@
23
23
  * hang or take super-linear time on an adversarial `workflow.test_command`.
24
24
  *
25
25
  * Single source of truth: all four test-command gates (regression, post-merge,
26
- * audit-fix, verify-phase) route their resolved command through this helper so
26
+ * audit-fix) route their resolved command through this helper so
27
27
  * the paths cannot drift.
28
28
  *
29
29
  * Leaf module — depends only on node:fs / node:path.
@@ -117,7 +117,11 @@ function listPlanningDocCandidates(cwd) {
117
117
  return [...candidates].sort();
118
118
  }
119
119
  function listCodebaseMapFiles(cwd) {
120
- const codebaseDir = node_path_1.default.join(planningRoot(cwd), 'codebase');
120
+ // #3964: project-scoped, agreeing with init's map-codebase surface and
121
+ // verify.cts's codebase drift check — a flat-root read made
122
+ // has_codebase_map/needs_codebase_map answer for the wrong project under
123
+ // GSD_PROJECT.
124
+ const codebaseDir = node_path_1.default.join(planningDir(cwd), 'codebase');
121
125
  if (!node_fs_1.default.existsSync(codebaseDir))
122
126
  return [];
123
127
  return REQUIRED_CODEBASE_MAP_FILES.filter((file) => node_fs_1.default.existsSync(node_path_1.default.join(codebaseDir, file)));
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ /**
3
+ * pattern.cts — the pattern-construction seam (ADR-3212 §1, epic #3212 Phase 1,
4
+ * #3412).
5
+ *
6
+ * Source in src/pattern.cts, compiled to gsd-core/bin/lib/pattern.cjs
7
+ * (gitignored), per the repo's ADR-457 build-at-publish convention.
8
+ *
9
+ * Sole owner of building a `RegExp` from a runtime value. `escapeRegex`
10
+ * delegates to the built-in `RegExp.escape` (ES2026 / Node 24+) when present,
11
+ * falling back to an in-file metacharacter escape below Node 24 (#3498) —
12
+ * still the one owner: no module outside this seam escapes a value for regex
13
+ * use (ADR §1).
14
+ *
15
+ * Counts, corrected during implementation (design doc "Ground truth" #1;
16
+ * .gsd/phase/chore-3412-pattern-seam/40-design.md): the ADR's census counted
17
+ * ~12 *named* helper functions. The shape-based lint guard
18
+ * (eslint-rules/no-adhoc-regex-escape.cjs), which matches the escape-class
19
+ * SHAPE wherever it appears rather than named-function bodies, found 27
20
+ * additional inline copies that census-by-name could not see — **~39 escape
21
+ * sites total**. Call sites follow the same pattern: 17 were surveyed
22
+ * directly, but `src/phase-id.cts`'s `escapeRegex` turned out to have 8
23
+ * external production importers the pre-implementation survey missed, plus
24
+ * the 27 guard-found inline sites also call into the seam — **~44 call
25
+ * sites total**. The lesson worth keeping: a named-function census
26
+ * structurally cannot see an inline `.replace(...)` copy or an
27
+ * externally-imported symbol; only a shape-based guard (or a direct
28
+ * importer graph query) does.
29
+ *
30
+ * `escapeRegex` is the PRIMARY export: the large majority of call sites
31
+ * build a *source string* (alternation via `.map(escapeRegex).join('|')`, or
32
+ * template/concat interpolation into a larger pattern) rather than a
33
+ * standalone literal match. `literalPattern` is the minority convenience
34
+ * wrapper for the remaining "match this value literally" shape — not the
35
+ * dominant one (design doc "Ground truth" #2).
36
+ *
37
+ * Behavior-preserving for MATCH RESULTS, not for pattern TEXT: `RegExp.escape`
38
+ * hex-escapes the leading character of nearly every non-empty input (and `-`,
39
+ * space, and control characters throughout), so the escaped source string
40
+ * differs from the twelve deleted copies' output for almost every value.
41
+ * Match behavior against that source is unaffected — verified by the
42
+ * migration-equivalence property sweep in tests/pattern.test.cjs (rows 15-17).
43
+ */
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.MAX_USER_PATTERN_LEN = void 0;
46
+ exports.escapeRegex = escapeRegex;
47
+ exports.literalPattern = literalPattern;
48
+ exports.compileUserPattern = compileUserPattern;
49
+ // re2js is vendored, not an npm dependency at runtime: gsd-core/bin/** is
50
+ // copied into installed trees that have no node_modules, so this module must
51
+ // carry zero external requires (eslint-rules/no-external-require-in-bin.cjs
52
+ // enforces it). See gsd-core/bin/lib/vendor/README.md.
53
+ const re2js_cjs_1 = require("./vendor/re2js.cjs");
54
+ // #3498: RegExp.escape is ES2026 (first shipped in Node 24). The gsd-test
55
+ // matrix still runs a linux-node22 lane, and the build itself consumes this
56
+ // module (scripts/gen-loop-host-contract.cjs), so a hard dependency breaks
57
+ // `npm run build` on Node 22. Prefer the built-in when present; otherwise use
58
+ // the local metachar escape — still inside this file, so the #3212 sole-owner
59
+ // invariant (and lint-no-adhoc-regex-escape's scope) is preserved. Captured at
60
+ // module load so a runtime mutation of RegExp.escape cannot flip the path
61
+ // mid-process.
62
+ const escapeBuiltin = typeof RegExp.escape === 'function'
63
+ ? RegExp.escape.bind(RegExp)
64
+ : undefined;
65
+ const escapeMetachars = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
66
+ function escapeRegex(value) {
67
+ return (escapeBuiltin ?? escapeMetachars)(value);
68
+ }
69
+ function literalPattern(value, flags) {
70
+ return new RegExp(escapeRegex(value), flags);
71
+ }
72
+ /** Max length for a user-supplied regex pattern before it is refused (ReDoS/compile-cost mitigation). */
73
+ exports.MAX_USER_PATTERN_LEN = 512;
74
+ /** Always-false matcher shared by every neutralization path — a refused pattern must never be able to report a match. */
75
+ const NEVER_MATCH = () => false;
76
+ /**
77
+ * Compile an UNTRUSTED, user-supplied pattern (e.g. plan frontmatter) via RE2
78
+ * (re2js), whose matching is linear-time in input length by construction —
79
+ * there is no backtracking engine here to exploit, so the vulnerability class
80
+ * (catastrophic/exponential backtracking) is closed by the engine rather than
81
+ * detected by a heuristic scan of the pattern text.
82
+ *
83
+ * Never throws. A pattern that is empty, too long, or not valid RE2 syntax
84
+ * (backreferences and look-around are unsupported by RE2 — those are exactly
85
+ * the constructs that require backtracking) is REFUSED: `test()` always
86
+ * returns `false`, and `neutralized` reports why so callers can surface the
87
+ * refusal (#3477 follow-up: a neutralized pattern must not look like a plain
88
+ * "not found"). Restores the guards lost with
89
+ * `sdk/src/query/validate.ts:regexForKeyLinkPattern` (#3477); this revision
90
+ * (post-#3477-follow-up) replaces the hand-rolled backtracking-shape scanner
91
+ * with RE2's linear-time guarantee — a refused pattern is never re-attempted
92
+ * as a literal-escaped match, since guessing at a pattern we could not
93
+ * compile is what produced the prior false-pass regression.
94
+ *
95
+ * `pattern` is `unknown`, not `string`, because callers pull this straight off
96
+ * parsed plan frontmatter (untrusted YAML) — a non-string value must reach the
97
+ * `'empty'`/never-match branch rather than being force-cast by the caller.
98
+ */
99
+ function compileUserPattern(pattern) {
100
+ if (typeof pattern !== 'string' || pattern.length === 0) {
101
+ return { test: NEVER_MATCH, neutralized: 'empty' };
102
+ }
103
+ if (pattern.length > exports.MAX_USER_PATTERN_LEN) {
104
+ // The cap now bounds compile cost/memory, not backtracking (RE2 has none) —
105
+ // an over-long pattern is refused outright rather than truncated-and-compiled.
106
+ return { test: NEVER_MATCH, neutralized: 'too-long' };
107
+ }
108
+ try {
109
+ // translateRegExp accepts JS-flavored syntax (named groups, `/`-escaping,
110
+ // etc.) that RE2's own grammar doesn't, reducing spurious refusals of
111
+ // otherwise-safe, JS-authored patterns before compiling under RE2's
112
+ // linear-time engine.
113
+ const compiled = re2js_cjs_1.RE2JS.compile(re2js_cjs_1.RE2JS.translateRegExp(pattern));
114
+ return { test: (input) => compiled.test(input), neutralized: null };
115
+ }
116
+ catch {
117
+ // Backreferences, look-around, or any other RE2-unsupported/malformed
118
+ // syntax. No literal-escape fallback: a pattern we could not compile is
119
+ // never guessed at — guessing produced the #3477 false-pass regression.
120
+ return { test: NEVER_MATCH, neutralized: 'unsupported' };
121
+ }
122
+ }
@@ -218,20 +218,29 @@ function applyCalibration(rawTokens, factor) {
218
218
  * Extract a two-space-indented scalar block (`estimate:` / `actuals:`) out of a
219
219
  * document's leading YAML frontmatter.
220
220
  *
221
- * Hand-rolled because gsd-core ships no external dependencies (CONTRIBUTING.md
222
- * "No external dependencies in core") — js-yaml is a devDependency and is not
223
- * available at runtime. Scope is deliberately narrow: the leading `---` block
224
- * only, so a `estimate:` line inside a fenced code block in the body cannot be
225
- * mistaken for frontmatter (the DEFECT.FRONTMATTER-SCALAR-BROAD-GREP class).
226
- *
227
- * Numeric-looking values are returned as numbers so parseEstimate/parseActuals
228
- * see the types they validate; everything else stays a string.
221
+ * Hand-rolled for its TYPE CONTRACT, not for dependency availability — js-yaml
222
+ * is vendored and available at runtime as of ADR-3473 §8.1 (#3881), which
223
+ * migrated `src/frontmatter.cts`'s `extractFrontmatter` onto it. That parser
224
+ * runs under js-yaml's FAILSAFE_SCHEMA, which resolves every scalar as a
225
+ * string, whereas this function deliberately returns numbers for
226
+ * numeric-looking values (`out[m[1]] = asNumber : rawValue`) so
227
+ * `parseEstimate`/`parseActuals` see the types they validate. A naive swap
228
+ * onto `extractFrontmatter` would turn `tokens: 5000` into `"5000"` and make
229
+ * `isPositiveInt` reject every estimate. Scope is deliberately narrow: the
230
+ * leading `---` block only, so an `estimate:` line inside a fenced code
231
+ * block in the body cannot be mistaken for frontmatter (the
232
+ * DEFECT.FRONTMATTER-SCALAR-BROAD-GREP class). Migrating this function onto
233
+ * the shared parser (with a typed coercion layer over its string-only
234
+ * output) is tracked as follow-on work under ADR-3473 §8.1, not done here —
235
+ * this module is calibration-critical and has a history of subtle numeric
236
+ * defects shipping past a large green suite (#2631 factor², #2632
237
+ * self-defeating loop).
229
238
  */
230
239
  function extractFrontmatterBlock(text, key) {
231
240
  if (typeof text !== 'string')
232
241
  return null;
233
242
  // Anchor at byte 0 — CRLF-tolerant.
234
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?(?:\n|$)/.exec(text);
243
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|\r?$)/.exec(text);
235
244
  if (fm === null)
236
245
  return null;
237
246
  const lines = fm[1].split(/\r?\n/);