@opengsd/gsd-core 1.12.0 → 1.14.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 (455) 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 +12 -0
  4. package/agents/gsd-advisor-researcher.compact.md +85 -0
  5. package/agents/gsd-ai-researcher.compact.md +96 -0
  6. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  7. package/agents/gsd-code-fixer.compact.md +458 -0
  8. package/agents/gsd-code-fixer.md +5 -5
  9. package/agents/gsd-code-reviewer.compact.md +269 -0
  10. package/agents/gsd-code-reviewer.md +15 -3
  11. package/agents/gsd-codebase-mapper.compact.md +760 -0
  12. package/agents/gsd-debug-session-manager.compact.md +345 -0
  13. package/agents/gsd-doc-classifier.compact.md +192 -0
  14. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  15. package/agents/gsd-doc-verifier.compact.md +143 -0
  16. package/agents/gsd-doc-writer.compact.md +440 -0
  17. package/agents/gsd-dom-verifier.compact.md +138 -0
  18. package/agents/gsd-domain-researcher.compact.md +141 -0
  19. package/agents/gsd-eval-auditor.compact.md +160 -0
  20. package/agents/gsd-eval-planner.compact.md +137 -0
  21. package/agents/gsd-executor.md +63 -35
  22. package/agents/gsd-framework-selector.compact.md +82 -0
  23. package/agents/gsd-integration-checker.compact.md +245 -0
  24. package/agents/gsd-intel-updater.compact.md +226 -0
  25. package/agents/gsd-mempalace-curator.compact.md +45 -0
  26. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  27. package/agents/gsd-pattern-mapper.compact.md +275 -0
  28. package/agents/gsd-plan-checker.md +76 -57
  29. package/agents/gsd-planner.md +14 -0
  30. package/agents/gsd-project-researcher.compact.md +587 -0
  31. package/agents/gsd-research-synthesizer.compact.md +212 -0
  32. package/agents/gsd-roadmapper.compact.md +454 -0
  33. package/agents/gsd-roadmapper.md +13 -0
  34. package/agents/gsd-security-auditor.compact.md +162 -0
  35. package/agents/gsd-ui-auditor.compact.md +404 -0
  36. package/agents/gsd-ui-checker.compact.md +277 -0
  37. package/agents/gsd-ui-checker.md +19 -3
  38. package/agents/gsd-ui-researcher.compact.md +282 -0
  39. package/agents/gsd-ui-researcher.md +29 -0
  40. package/agents/gsd-user-profiler.compact.md +108 -0
  41. package/agents/gsd-verifier.md +23 -1
  42. package/bin/install.js +444 -134
  43. package/commands/gsd/cleanup.md +1 -0
  44. package/commands/gsd/code-review.md +2 -1
  45. package/commands/gsd/complete-milestone.md +1 -0
  46. package/commands/gsd/config.md +1 -0
  47. package/commands/gsd/debug.md +1 -0
  48. package/commands/gsd/execute-phase.md +1 -1
  49. package/commands/gsd/graphify.md +1 -0
  50. package/commands/gsd/health.md +1 -0
  51. package/commands/gsd/mempalace-capture.md +1 -0
  52. package/commands/gsd/mempalace-recall.md +1 -0
  53. package/commands/gsd/new-milestone.md +1 -0
  54. package/commands/gsd/new-project.md +1 -0
  55. package/commands/gsd/next.md +1 -0
  56. package/commands/gsd/ns-workflow.md +2 -1
  57. package/commands/gsd/pause-work.md +1 -0
  58. package/commands/gsd/phase.md +2 -1
  59. package/commands/gsd/pr-branch.md +1 -0
  60. package/commands/gsd/quick-batch.md +105 -0
  61. package/commands/gsd/resume-work.md +1 -0
  62. package/commands/gsd/review-backlog.md +1 -0
  63. package/commands/gsd/settings.md +2 -1
  64. package/commands/gsd/stats.md +1 -0
  65. package/commands/gsd/surface.md +18 -8
  66. package/commands/gsd/thread.md +1 -0
  67. package/commands/gsd/workspace.md +1 -0
  68. package/commands/gsd/workstreams.md +1 -0
  69. package/gsd-core/bin/check-latest-version.cjs +8 -3
  70. package/gsd-core/bin/gsd-tools.cjs +532 -174
  71. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  72. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  73. package/gsd-core/bin/lib/audit.cjs +39 -22
  74. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  75. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  76. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  77. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  78. package/gsd-core/bin/lib/capability-registry.cjs +528 -116
  79. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  80. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  81. package/gsd-core/bin/lib/capability-validator.cjs +134 -5
  82. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  83. package/gsd-core/bin/lib/check-command-router.cjs +198 -38
  84. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  85. package/gsd-core/bin/lib/clusters.cjs +1 -0
  86. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  87. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  88. package/gsd-core/bin/lib/commands.cjs +981 -79
  89. package/gsd-core/bin/lib/config-loader.cjs +4 -0
  90. package/gsd-core/bin/lib/config.cjs +153 -38
  91. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  92. package/gsd-core/bin/lib/coverage.cjs +1 -1
  93. package/gsd-core/bin/lib/decisions.cjs +343 -28
  94. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  95. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  96. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  97. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  98. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  99. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  100. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  101. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  102. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  103. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
  104. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  105. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  106. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  107. package/gsd-core/bin/lib/init.cjs +539 -60
  108. package/gsd-core/bin/lib/install-engine.cjs +199 -14
  109. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  110. package/gsd-core/bin/lib/install-profiles.cjs +36 -14
  111. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  112. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  113. package/gsd-core/bin/lib/io.cjs +35 -0
  114. package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
  115. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  116. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  117. package/gsd-core/bin/lib/milestone.cjs +41 -10
  118. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  119. package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
  120. package/gsd-core/bin/lib/phase-id.cjs +412 -31
  121. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  122. package/gsd-core/bin/lib/phase.cjs +941 -98
  123. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
  126. package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
  127. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  128. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  129. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  130. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  131. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  132. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  133. package/gsd-core/bin/lib/research-store.cjs +11 -12
  134. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  135. package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
  136. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  137. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  138. package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
  139. package/gsd-core/bin/lib/roadmap.cjs +292 -69
  140. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
  141. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
  142. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
  143. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
  144. package/gsd-core/bin/lib/security.cjs +126 -7
  145. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  146. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  147. package/gsd-core/bin/lib/state-document.cjs +159 -32
  148. package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
  149. package/gsd-core/bin/lib/state-transition.cjs +465 -62
  150. package/gsd-core/bin/lib/state.cjs +906 -151
  151. package/gsd-core/bin/lib/surface.cjs +83 -10
  152. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  153. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  154. package/gsd-core/bin/lib/uat.cjs +1420 -516
  155. package/gsd-core/bin/lib/update-context.cjs +36 -26
  156. package/gsd-core/bin/lib/validate.cjs +230 -12
  157. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  158. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  159. package/gsd-core/bin/lib/verification.cjs +316 -23
  160. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  161. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  162. package/gsd-core/bin/lib/verify.cjs +531 -36
  163. package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
  164. package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
  165. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  166. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  167. package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
  168. package/gsd-core/references/agent-contracts.md +3 -3
  169. package/gsd-core/references/compact-content-gate.md +66 -0
  170. package/gsd-core/references/edge-probe.md +17 -13
  171. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  172. package/gsd-core/references/execute-phase-response-language.md +6 -0
  173. package/gsd-core/references/executor-examples.md +42 -0
  174. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  175. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  176. package/gsd-core/references/model-profiles.md +12 -3
  177. package/gsd-core/references/mvp-concepts.md +2 -2
  178. package/gsd-core/references/plan-checker-examples.md +41 -0
  179. package/gsd-core/references/planner-antipatterns.md +25 -0
  180. package/gsd-core/references/planner-chunked.md +5 -1
  181. package/gsd-core/references/planner-coupling.md +42 -0
  182. package/gsd-core/references/planner-quick-batch.md +71 -0
  183. package/gsd-core/references/planner-reviews.md +47 -0
  184. package/gsd-core/references/planner-revision.md +75 -2
  185. package/gsd-core/references/planning-config.md +5 -1
  186. package/gsd-core/references/response-language-directive.md +9 -0
  187. package/gsd-core/references/revision-loop.md +118 -11
  188. package/gsd-core/references/tdd.md +17 -9
  189. package/gsd-core/references/thinking-models-planning.md +18 -2
  190. package/gsd-core/references/verification-patterns.md +17 -4
  191. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  192. package/gsd-core/references/worktree-path-safety.md +112 -2
  193. package/gsd-core/templates/README.md +7 -1
  194. package/gsd-core/templates/phase-prompt.md +4 -0
  195. package/gsd-core/templates/state.md +6 -3
  196. package/gsd-core/templates/summary.compact.md +212 -0
  197. package/gsd-core/templates/user-setup.compact.md +199 -0
  198. package/gsd-core/templates/user-setup.md +0 -9
  199. package/gsd-core/templates/verification-report.md +5 -0
  200. package/gsd-core/workflows/add-backlog.md +2 -0
  201. package/gsd-core/workflows/add-phase.md +2 -0
  202. package/gsd-core/workflows/add-tests.md +1 -1
  203. package/gsd-core/workflows/add-todo.md +4 -3
  204. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  205. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  206. package/gsd-core/workflows/audit-fix.md +2 -0
  207. package/gsd-core/workflows/audit-milestone.md +2 -0
  208. package/gsd-core/workflows/audit-uat.md +2 -0
  209. package/gsd-core/workflows/autonomous.md +15 -10
  210. package/gsd-core/workflows/check-todos.md +5 -3
  211. package/gsd-core/workflows/cleanup.md +4 -2
  212. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
  213. package/gsd-core/workflows/code-review-fix.md +5 -3
  214. package/gsd-core/workflows/code-review.md +211 -43
  215. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  216. package/gsd-core/workflows/complete-milestone.md +40 -254
  217. package/gsd-core/workflows/debug.md +1 -1
  218. package/gsd-core/workflows/diagnose-issues.md +5 -1
  219. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  220. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  221. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  222. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  223. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  224. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  225. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  226. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  227. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  228. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  229. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  230. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  231. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  232. package/gsd-core/workflows/discuss-phase.md +1 -1
  233. package/gsd-core/workflows/do.md +43 -13
  234. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  235. package/gsd-core/workflows/docs-update.md +15 -156
  236. package/gsd-core/workflows/edit-phase.md +2 -0
  237. package/gsd-core/workflows/eval-review.md +1 -1
  238. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  239. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
  240. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  241. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
  242. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  243. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  245. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  246. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  247. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  248. package/gsd-core/workflows/execute-phase.md +78 -159
  249. package/gsd-core/workflows/execute-plan.md +28 -15
  250. package/gsd-core/workflows/explore.md +2 -0
  251. package/gsd-core/workflows/extract-learnings.md +2 -0
  252. package/gsd-core/workflows/fast.md +6 -0
  253. package/gsd-core/workflows/forensics.md +2 -0
  254. package/gsd-core/workflows/graduation.md +1 -1
  255. package/gsd-core/workflows/health.md +1 -1
  256. package/gsd-core/workflows/help/modes/brief.md +2 -0
  257. package/gsd-core/workflows/help/modes/default.md +2 -0
  258. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  259. package/gsd-core/workflows/help/modes/full.md +12 -0
  260. package/gsd-core/workflows/help/modes/topic.md +2 -0
  261. package/gsd-core/workflows/help.md +3 -1
  262. package/gsd-core/workflows/import.md +3 -3
  263. package/gsd-core/workflows/inbox.md +1 -1
  264. package/gsd-core/workflows/ingest-docs.md +1 -1
  265. package/gsd-core/workflows/insert-phase.md +2 -0
  266. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  267. package/gsd-core/workflows/list-seeds.md +2 -0
  268. package/gsd-core/workflows/list-workspaces.md +2 -0
  269. package/gsd-core/workflows/manager.md +3 -3
  270. package/gsd-core/workflows/map-codebase.md +52 -3
  271. package/gsd-core/workflows/milestone-summary.md +2 -0
  272. package/gsd-core/workflows/mvp-phase.md +1 -1
  273. package/gsd-core/workflows/new-milestone.md +55 -13
  274. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  275. package/gsd-core/workflows/new-project.md +37 -205
  276. package/gsd-core/workflows/new-workspace.md +1 -1
  277. package/gsd-core/workflows/next.md +2 -0
  278. package/gsd-core/workflows/node-repair.md +2 -0
  279. package/gsd-core/workflows/note.md +2 -0
  280. package/gsd-core/workflows/onboard.md +1 -1
  281. package/gsd-core/workflows/pause-work.md +19 -4
  282. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  283. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  284. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  285. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  286. package/gsd-core/workflows/plan-phase.md +144 -185
  287. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  288. package/gsd-core/workflows/plant-seed.md +1 -1
  289. package/gsd-core/workflows/pr-branch.md +30 -10
  290. package/gsd-core/workflows/profile-user.md +1 -1
  291. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  292. package/gsd-core/workflows/progress.md +25 -3
  293. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  294. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  295. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  296. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  297. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  298. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  299. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  300. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  301. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  302. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  303. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  304. package/gsd-core/workflows/quick-batch.md +203 -0
  305. package/gsd-core/workflows/quick.md +21 -4
  306. package/gsd-core/workflows/reapply-patches.md +79 -3
  307. package/gsd-core/workflows/remove-phase.md +2 -0
  308. package/gsd-core/workflows/remove-workspace.md +1 -1
  309. package/gsd-core/workflows/resume-project.md +6 -2
  310. package/gsd-core/workflows/review.md +215 -10
  311. package/gsd-core/workflows/scan.md +2 -0
  312. package/gsd-core/workflows/section-manifest.json +12 -0
  313. package/gsd-core/workflows/secure-phase.md +1 -1
  314. package/gsd-core/workflows/session-report.md +2 -0
  315. package/gsd-core/workflows/settings-advanced.md +2 -0
  316. package/gsd-core/workflows/settings-integrations.md +9 -8
  317. package/gsd-core/workflows/settings.md +19 -6
  318. package/gsd-core/workflows/ship.md +10 -10
  319. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  320. package/gsd-core/workflows/sketch.md +1 -1
  321. package/gsd-core/workflows/smart-entry.md +1 -1
  322. package/gsd-core/workflows/spec-phase.md +24 -19
  323. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  324. package/gsd-core/workflows/spike.md +1 -1
  325. package/gsd-core/workflows/stats.md +2 -0
  326. package/gsd-core/workflows/sync-skills.md +12 -4
  327. package/gsd-core/workflows/thread.md +2 -0
  328. package/gsd-core/workflows/transition.md +2 -0
  329. package/gsd-core/workflows/ui-phase.md +26 -5
  330. package/gsd-core/workflows/ui-review.md +1 -1
  331. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  332. package/gsd-core/workflows/undo.md +1 -1
  333. package/gsd-core/workflows/update.md +48 -43
  334. package/gsd-core/workflows/validate-phase.md +1 -1
  335. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  336. package/gsd-core/workflows/verify-work.md +68 -182
  337. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  338. package/hooks/dist/gsd-check-update-worker.js +19 -2
  339. package/hooks/dist/gsd-context-monitor.js +371 -27
  340. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  341. package/hooks/dist/gsd-node-runner.sh +1 -0
  342. package/hooks/dist/gsd-prompt-guard.js +30 -5
  343. package/hooks/dist/gsd-read-guard.js +2 -0
  344. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  345. package/hooks/dist/gsd-secret-read-guard.js +1105 -0
  346. package/hooks/dist/gsd-statusline.js +18 -10
  347. package/hooks/dist/gsd-validate-commit.sh +474 -7
  348. package/hooks/dist/gsd-workflow-guard.js +2 -1
  349. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  350. package/hooks/dist/gsd-write-guard.js +46 -1
  351. package/hooks/dist/lib/dispatch-identity.js +187 -0
  352. package/hooks/dist/lib/filename-classification.js +64 -0
  353. package/hooks/dist/lib/git-cmd.js +210 -1
  354. package/hooks/dist/lib/injection-patterns.js +36 -6
  355. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  356. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  357. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  358. package/hooks/gsd-agent-isolation-guard.js +42 -16
  359. package/hooks/gsd-check-update-worker.js +19 -2
  360. package/hooks/gsd-context-monitor.js +371 -27
  361. package/hooks/gsd-cursor-subagent-start.js +34 -14
  362. package/hooks/gsd-node-runner.sh +1 -0
  363. package/hooks/gsd-prompt-guard.js +30 -5
  364. package/hooks/gsd-read-guard.js +2 -0
  365. package/hooks/gsd-read-injection-scanner.js +5 -5
  366. package/hooks/gsd-secret-read-guard.js +1105 -0
  367. package/hooks/gsd-statusline.js +18 -10
  368. package/hooks/gsd-validate-commit.sh +474 -7
  369. package/hooks/gsd-workflow-guard.js +2 -1
  370. package/hooks/gsd-worktree-path-guard.js +25 -14
  371. package/hooks/gsd-write-guard.js +46 -1
  372. package/hooks/hooks.json +6 -0
  373. package/hooks/lib/dispatch-identity.js +187 -0
  374. package/hooks/lib/filename-classification.js +64 -0
  375. package/hooks/lib/git-cmd.js +210 -1
  376. package/hooks/lib/injection-patterns.js +36 -6
  377. package/hooks/lib/isolation-deny-reason.js +53 -1
  378. package/hooks/lib/isolation-sentinel.js +58 -19
  379. package/hooks/managed-hooks-registry.cjs +1 -0
  380. package/package.json +13 -9
  381. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  382. package/scripts/benchmark-compact-content.cjs +368 -0
  383. package/scripts/build-hooks.js +11 -4
  384. package/scripts/check-contract-drift.cjs +4 -1
  385. package/scripts/check-env.cjs +36 -8
  386. package/scripts/check-glossary-refs.cjs +25 -21
  387. package/scripts/ci-next-health.cjs +271 -0
  388. package/scripts/ci-prepare-test-scope.cjs +7 -7
  389. package/scripts/ci-test-scope.cjs +133 -20
  390. package/scripts/ci-timeout-report.cjs +1 -1
  391. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  392. package/scripts/docs-guard-registry.cjs +17 -2
  393. package/scripts/gen-adr-index.cjs +8 -2
  394. package/scripts/gen-inventory-manifest.cjs +12 -0
  395. package/scripts/gen-loop-host-contract.cjs +67 -15
  396. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  397. package/scripts/lib/drift-scan.cjs +1 -1
  398. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  399. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  400. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  401. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  402. package/scripts/lib/suite-detection.cjs +32 -0
  403. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  404. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  405. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  406. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  407. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
  408. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  409. package/scripts/lint-phase-id-drift.cjs +465 -15
  410. package/scripts/lint-portable-grep.cjs +176 -0
  411. package/scripts/lint-response-language-coverage.cjs +530 -0
  412. package/scripts/lint-source-test-name-collision.cjs +1 -1
  413. package/scripts/lint-test-file-count.allowlist.json +4 -1
  414. package/scripts/lint-vendored-deps.cjs +128 -17
  415. package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
  416. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  417. package/scripts/npm-audit-baseline.cjs +376 -0
  418. package/scripts/prompt-injection-scan.sh +22 -0
  419. package/scripts/require-issue-link-policy.cjs +16 -1
  420. package/scripts/workflow-size.cjs +139 -0
  421. package/skills/gsd-cleanup/SKILL.md +1 -0
  422. package/skills/gsd-code-review/SKILL.md +2 -1
  423. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  424. package/skills/gsd-config/SKILL.md +1 -0
  425. package/skills/gsd-debug/SKILL.md +1 -0
  426. package/skills/gsd-execute-phase/SKILL.md +1 -1
  427. package/skills/gsd-graphify/SKILL.md +1 -0
  428. package/skills/gsd-health/SKILL.md +1 -0
  429. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  430. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  431. package/skills/gsd-new-milestone/SKILL.md +1 -0
  432. package/skills/gsd-new-project/SKILL.md +1 -0
  433. package/skills/gsd-next/SKILL.md +1 -0
  434. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  435. package/skills/gsd-pause-work/SKILL.md +1 -0
  436. package/skills/gsd-phase/SKILL.md +2 -1
  437. package/skills/gsd-pr-branch/SKILL.md +1 -0
  438. package/skills/gsd-quick-batch/SKILL.md +105 -0
  439. package/skills/gsd-resume-work/SKILL.md +1 -0
  440. package/skills/gsd-review-backlog/SKILL.md +1 -0
  441. package/skills/gsd-settings/SKILL.md +2 -1
  442. package/skills/gsd-stats/SKILL.md +1 -0
  443. package/skills/gsd-surface/SKILL.md +18 -8
  444. package/skills/gsd-thread/SKILL.md +1 -0
  445. package/skills/gsd-workspace/SKILL.md +1 -0
  446. package/skills/gsd-workstreams/SKILL.md +1 -0
  447. package/vscode/package.json +1 -1
  448. package/gsd-core/templates/claude-md.md +0 -145
  449. package/gsd-core/templates/codebase/concerns.md +0 -310
  450. package/gsd-core/templates/codebase/conventions.md +0 -307
  451. package/gsd-core/templates/codebase/integrations.md +0 -280
  452. package/gsd-core/templates/codebase/structure.md +0 -285
  453. package/gsd-core/templates/codebase/testing.md +0 -480
  454. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  455. package/gsd-core/templates/discovery.md +0 -146
@@ -5,7 +5,9 @@
5
5
  * truth). Behaviour is preserved byte-for-behaviour from the prior hand-written
6
6
  * .cjs; only types are added.
7
7
  *
8
- * Accepts both numeric (D-42) and alphanumeric (D-INFRA-01) IDs.
8
+ * Accepts numeric (D-42), alphanumeric (D-INFRA-01), and phase-prefixed
9
+ * (D4-01 — an optional digit-run between the leading letter and the hyphen,
10
+ * #4130) IDs.
9
11
  * Returns {id, text, category, tags, trackable} per decision.
10
12
  * CJS callers that only use {id, text} safely ignore the extra fields.
11
13
  *
@@ -16,6 +18,11 @@
16
18
  * - Outer bullet loop → seam's `iterateBullets` (for the header-fallback path)
17
19
  *
18
20
  * Resolves #1364 (markdown-header + em-dash recall) and #1365 (fail-loud gate).
21
+ *
22
+ * #4130 follow-up (hardening): the three bullet grammars below consume the
23
+ * decision ID atomically and narrow the em-dash first separator, eliminating
24
+ * the quadratic-backtracking cliff on pathological single bullets. Output is
25
+ * byte-identical on all legal inputs — see the notes at DECISION_ID_SOURCE.
19
26
  */
20
27
  Object.defineProperty(exports, "__esModule", { value: true });
21
28
  exports.extractDecisions = extractDecisions;
@@ -30,21 +37,84 @@ const DISCRETION_HEADINGS = new Set([
30
37
  const NON_TRACKABLE_TAGS = new Set(['informational', 'folded', 'deferred']);
31
38
  // ─── Bullet parsers (decisions-specific grammar) ─────────────────────────────
32
39
  /**
33
- * Colon form: `- **D-NN[ [tags]]:** text`
40
+ * #4130: the ID grammar every extractor regex below shares, as ONE source.
41
+ * `D`, an OPTIONAL digit-run phase prefix, a hyphen, then the pre-existing
42
+ * alphanumeric tail — so `D-01` (bare), `D4-01`/`D12-01` (phase-prefixed,
43
+ * the reporter's multi-phase convention where bare D-01 collides across
44
+ * phases), and `D-INFRA-01` (alnum tail) are all the same grammar now.
45
+ * #2347 had already taught the shape DETECTOR to call `D4-01` decision-shaped
46
+ * while the EXTRACTOR still anchored on the literal `**D-` — the disagreement
47
+ * that made a whole phase-prefixed CONTEXT.md report could-not-parse. Deriving
48
+ * the three grammars (and the token evidence below) from this one constant is
49
+ * the parity pin: the extractor's ID universe cannot drift from the declared
50
+ * grammar again without editing this line, which the #4130 property tests
51
+ * watch from the other side.
52
+ */
53
+ const DECISION_ID_SOURCE = 'D[0-9]*-[A-Za-z0-9][A-Za-z0-9_-]*';
54
+ /**
55
+ * #4130 follow-up (hardening): how the three grammars below CONSUME the ID —
56
+ * atomically, via the `(?=(X))\1` lookahead emulation (lookarounds are atomic
57
+ * in ECMAScript; the backreference must replay exactly what the lookahead
58
+ * captured, so the engine can never give the ID tail back one character at a
59
+ * time). That give-back was quadratic driver #1: the tail class
60
+ * `[A-Za-z0-9_-]*` overlaps the pre-separator class `[^:*]*` (every id char
61
+ * is also `[^:*]`), so on a FAILING bullet the base regex re-split the tail
62
+ * O(n) times with an O(n) scan after each — measured ~1.1s @ 40k chars on
63
+ * `- **D-` + `a-`×20k (the #4357 review's deferred cliff).
64
+ *
65
+ * Byte-identical on all legal inputs: a successful match always consumes the
66
+ * MAXIMAL id run (the lookahead's own match is exactly that maximal run), and
67
+ * the continuation's success depends only on the position of the first
68
+ * `:`/`*` (or `*` for the em-dash form) after the id boundary — id chars
69
+ * contain neither, so moving the boundary inside the run cannot change
70
+ * success or any capture. Group 1 stays the full id (the lookahead's capture
71
+ * IS group 1), so handlers keep reading match[1]/[2]/[3] untouched. Pinned by
72
+ * the differential property test against a frozen copy of the pre-hardening
73
+ * grammars and by the regex-lattice test in tests/decisions.test.cjs.
74
+ */
75
+ /**
76
+ * #4130: the bold lead-in that ATTEMPTS the ID grammar above — used by the
77
+ * parse-miss guard and the #3939 join regexes, where recognising MORE shapes
78
+ * is the conservative direction (an over-broad match can only make a
79
+ * malformed bullet fail loud). The prefix run is either empty (bare `D-`) or
80
+ * DIGIT-INITIAL (`4`, `4x` — a phase prefix with a typo still counts as an
81
+ * attempted ID, so `D4x-01` reaches the guard and fails loud instead of
82
+ * vanishing), but never letter-initial: `D` + letters + `-` (`Deferred-until`)
83
+ * is a prose word, and prose must stay `none-present` (#2347's law).
84
+ */
85
+ const ID_ATTEMPT_SOURCE = 'D(?:[0-9][A-Za-z0-9]*)?-';
86
+ /**
87
+ * Colon form: `- **D[phase]-NN[ [tags]]:** text`
34
88
  * (#1343: `[^:*]*` subsumes any pre-colon prose, stops at `:**`)
89
+ * Group 1 captures the FULL id including any phase prefix (#4130).
90
+ * The ID is consumed atomically `(?=(…))\1` — see the hardening note above
91
+ * the constants (#4130 follow-up); with the tail unable to give back, the
92
+ * remaining `[^:*]*:` scan has a single viable split and the whole match is
93
+ * linear in line length.
35
94
  */
36
- const bulletColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^:*]*:\*\*\s*(.*)$/;
95
+ const bulletColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[^:*]*:\\*\\*\\s*(.*)$`);
37
96
  /**
38
- * Em-dash form: `- **D-NN[ [tags]] — title** body`
97
+ * Em-dash form: `- **D[phase]-NN[ [tags]] — title** body`
39
98
  * The em-dash (U+2014) or its lookalike separates the ID+tags group from a title
40
99
  * that lives inside the bold markers; the body (which may be empty) follows
41
100
  * outside the closing `**`. This form was not handled pre-T1 (bug #1364).
42
101
  *
43
102
  * Accepts both U+2014 em-dash (—) and U+2013 en-dash (–) for robustness.
103
+ *
104
+ * #4130 follow-up (hardening), quadratic driver #2: the first separator was
105
+ * `[^*]*[—–]`, whose leading class ALSO accepts the dash — on a failing
106
+ * dash-laden title the engine retried the separator at every dash position
107
+ * with an O(n) scan after each (~1.7s @ 40k). Narrowed to `[^*—–]*[—–]`:
108
+ * the leading class now excludes the dash, so the separator is the FIRST
109
+ * dash — one viable split, single pass. Behavior-preserving because every
110
+ * candidate dash lies before the first `*` (the leading class cannot cross
111
+ * a star), so the trailing `[^*]*` reaches that same first star from any
112
+ * candidate and `**` succeeds or fails identically; no capture involves the
113
+ * dash position. The ID is atomic like the other forms (driver #1).
44
114
  */
45
- const bulletEmDashRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^*]*[—–][^*]*\*\*\s*(.*)$/;
115
+ const bulletEmDashRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[^*—–]*[—–][^*]*\\*\\*\\s*(.*)$`);
46
116
  /**
47
- * Titled-colon form: `- **D-NN[ [tags]]: Title.** body`
117
+ * Titled-colon form: `- **D[phase]-NN[ [tags]]: Title.** body`
48
118
  * A title sits between the colon and the closing `**` (so the `:**` anchor of
49
119
  * bulletColonRe fails, and there is no em-dash for bulletEmDashRe). This is a strict
50
120
  * superset of the colon-immediate form, so it MUST be checked AFTER bulletColonRe and
@@ -53,16 +123,34 @@ const bulletEmDashRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+
53
123
  * (e.g. `D-07 ratio 3:1:**`) still fails the anchor and falls through to the parse-miss
54
124
  * guard — matching bulletColonRe's `[^:*]*` discipline that the separator colon is the
55
125
  * only colon permitted before `**`. (#1639)
126
+ *
127
+ * The ID is consumed atomically `(?=(…))\1` like the other forms — the
128
+ * hardening note above the constants explains why (#4130 follow-up).
129
+ */
130
+ const bulletTitledColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[^:*]*:[^:*]*\\*\\*\\s*(.*)$`);
131
+ /**
132
+ * #4130: the parse-miss guard's probe — a line whose bold lead-in ATTEMPTS the
133
+ * ID grammar (see `ID_ATTEMPT_SOURCE`) but failed all three bullet patterns
134
+ * above. Bare `D-` attempts behave exactly as before #4130; a digit-initial
135
+ * prefix run (`D4-`… including a typo'd `D4x-`) is new evidence of an attempt,
136
+ * so the malformed-prefixed bullet fails loud instead of silently vanishing.
137
+ */
138
+ const parseMissGuardRe = new RegExp(`^\\s*-\\s+\\*\\*${ID_ATTEMPT_SOURCE}`);
139
+ /**
140
+ * #4130: bare-token evidence of decision-shaped content — a `D…-<alnum>` token
141
+ * in running text. `D-01` matched before; the digit-run phase prefix (`D4-01`)
142
+ * is added so token evidence agrees with the extractor's ID grammar
143
+ * (DECISION_ID_SOURCE) instead of silently ignoring prefixed mentions.
56
144
  */
57
- const bulletTitledColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^:*]*:[^:*]*\*\*\s*(.*)$/;
145
+ const decisionTokenRe = new RegExp(`\\bD[0-9]*-[A-Za-z0-9]`, 'm');
58
146
  /**
59
147
  * #2347: format-agnostic evidence that a block/section holds real decision
60
148
  * ENTRIES the parser could not read — a bullet whose bold lead-in is an
61
149
  * ID-SHAPED token (uppercase prefix, optional digits, hyphen, alnum), whatever
62
- * the exact ID grammar. The three parser grammars above all require a `D-`
63
- * prefix; #1365's fail-loud guard reused that same `\bD-` test as its "is this
64
- * decision-shaped?" evidence, so any other prefix (e.g. `D5-01`) was invisible
65
- * to BOTH parser and guard, collapsing `could-not-parse` into a clean
150
+ * the exact ID grammar. #1365's fail-loud guard originally reused the parser's
151
+ * own `\bD-` test as its "is this decision-shaped?" evidence, so any prefix the
152
+ * parser could not read (e.g. `D5-01` then, `DEC-01` now) was invisible to
153
+ * BOTH parser and guard, collapsing `could-not-parse` into a clean
66
154
  * `none-present` pass.
67
155
  *
68
156
  * The ID-shape requirement (not "any bold bullet") is deliberate: a decisions
@@ -70,11 +158,231 @@ const bulletTitledColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([
70
158
  * bullets with bold labels (`- **Scope:** …`, `- **Why:** …`, `- **Note:** …`).
71
159
  * Those are NOT decision entries and must stay `none-present` — a false
72
160
  * `could-not-parse` hard-blocks the plan gate. `[A-Z]+[0-9]*-[A-Za-z0-9]` matches
73
- * `D-01` / `D5-01` / `DEC-01` but not `Scope:` / `Why:` / `Follow-up:` (mixed
161
+ * `D-01` / `D4-01` / `DEC-01` but not `Scope:` / `Why:` / `Follow-up:` (mixed
74
162
  * case) / `TODO:` (no `-<alnum>` id) — mirroring the parser's own `D-<alnum>`
75
163
  * shape without hardcoding the `D`.
164
+ *
165
+ * #4130 parity note: for the D-prefixed universe this detector's grammar
166
+ * (`D` + digit-run + `-` + alnum) is exactly `DECISION_ID_SOURCE` above, so a
167
+ * well-formed bullet the detector calls decision-shaped is now always one the
168
+ * extractor can read. The detector stays WIDER on purpose (`DEC-01` is still
169
+ * evidence): an ID grammar outside the parser's universe must keep failing
170
+ * loud, never silently passing. The #4130 property tests pin both directions.
76
171
  */
77
172
  const boldLeadInBulletRe = /^\s*-\s+\*\*[A-Z]+[0-9]*-[A-Za-z0-9]/m;
173
+ /**
174
+ * #3939: a decision bullet's DECLARATION line — the `- **D[phase]-NN … **` bold
175
+ * lead-in the three grammars above anchor on — may wrap across a line break.
176
+ * Physical line breaks inside a bullet are markdown-insignificant, and GSD's own
177
+ * discuss-phase writer emits the wrapped shape whenever a decision title runs
178
+ * past the wrap column. All three grammars require the closing `**` in the same
179
+ * string as the `- **D…-` anchor, so a wrapped declaration matched none of them
180
+ * and fell to the #1365 parse-miss guard, forcing `could-not-parse` (which
181
+ * hard-blocks `check.decision-coverage-plan`) on a well-formed CONTEXT.md.
182
+ *
183
+ * The repair is confined to how the LOGICAL bullet is assembled — the grammars
184
+ * themselves are untouched, so every single-line form parses exactly as before.
185
+ * #4130: the anchor uses `ID_ATTEMPT_SOURCE` (digit-run phase prefixes join
186
+ * like bare ones; recognising more start shapes only reassembles the logical
187
+ * bullet, which then parses or fails loud as itself).
188
+ */
189
+ const decisionBulletStartRe = new RegExp(`^\\s*-\\s+\\*\\*${ID_ATTEMPT_SOURCE}`);
190
+ /**
191
+ * A line that opens a new BLOCK-LEVEL construct, and therefore terminates the
192
+ * bullet above it: a list marker of any family (`-`, `*`, `+`, `1.`, `1)`), an
193
+ * ATX heading, a blockquote, or a table row. Joining never reaches across one of
194
+ * these (nor across a blank/whitespace-only line, checked separately), so a
195
+ * declaration whose bold run genuinely never closes cannot absorb the block
196
+ * below it and get "closed" by an unrelated inline `**` — it stays a parse-miss
197
+ * and still fails loud, which #1365's contract requires.
198
+ *
199
+ * The four MARKER families demand trailing whitespace so that a continuation
200
+ * line opening with emphasis (`*in* the header.** …`) is text, not a bullet.
201
+ * The table-row alternative deliberately does not: CommonMark tables may open
202
+ * flush (`|Col1|Col2|`), and a leading `|` is never ordinary decision prose.
203
+ * A `- ` line at ANY indent stops the join: a deeper one is #3169 nested
204
+ * elaboration, which the main loop folds into the open decision itself.
205
+ *
206
+ * DELIBERATE DIVERGENCE from the sectionizer seam (ADR-1372): `iterateBullets`
207
+ * recognises only the `N. ` ordered-list form (`numberedRe`,
208
+ * src/markdown-sectionizer.cts), while this set also stops at the `N) ` form.
209
+ * That is intentional and one-directional — this regex answers "may the join
210
+ * cross this line?", where recognising MORE block openers is the conservative
211
+ * answer (a missed terminator can manufacture a decision; a spare one can only
212
+ * make a malformed bullet fail loud, which #1365 already wants). `N)` is a
213
+ * CommonMark ordered-list marker, so a join must not reach across it whether or
214
+ * not the seam's own bullet iterator yields it. Both forms are pinned by tests,
215
+ * and a drift test asserts the seam still does NOT treat `N)` as a bullet, so
216
+ * this divergence stays visible if either side moves.
217
+ *
218
+ * Accepted over-termination: continuation prose that happens to open with
219
+ * digits-then-`.`/`)` ("10. really keeps going") or a literal `|` stops the join
220
+ * early, so such a bullet fails loud rather than parsing. That is the same
221
+ * markdown ambiguity every line-oriented reader carries, and this direction of
222
+ * the trade is the one #1365 asks for — fail loud, never guess.
223
+ */
224
+ const blockConstructRe = /^(?:[-*+]\s|\d+[.)]\s|#{1,6}\s|>\s|\|)/;
225
+ /**
226
+ * The id-adjacent `[tags]` REGION of a logical bullet, as far as it has been
227
+ * assembled. Matching means the region is still unsettled, in one of two ways:
228
+ * capture group 1 is present when the bracket is open (group 1 is the content
229
+ * seen so far), and absent when the id has been read but no `[` has followed
230
+ * yet — so a bracket may still open on the next absorbed line.
231
+ *
232
+ * A NON-match means the region is settled for good: the bracket closed, or
233
+ * something other than `[` followed the id. Either way the join no longer has
234
+ * to watch for a splice.
235
+ *
236
+ * The id character class is deliberately looser than the grammars' (it admits
237
+ * an empty id, so a bare `- **D-` still counts as unsettled, and — #4130 — a
238
+ * digit-run phase prefix between the `D` and the first hyphen). This regex only
239
+ * answers "may an id-adjacent bracket still open here?", where recognising MORE
240
+ * shapes is the conservative direction: an over-broad match can only make a
241
+ * malformed bullet fail loud, while a missed one silently re-classifies.
242
+ *
243
+ * Only the ID-ADJACENT bracket matters: that is the one the three grammars turn
244
+ * into `tags` (and therefore into `trackable`). A `[` further along the title is
245
+ * ordinary text and does not restrict the join.
246
+ */
247
+ const tagRegionRe = new RegExp(`^\\s*-\\s+\\*\\*D[0-9]*-[A-Za-z0-9_-]*\\s*(?:\\[([^\\]]*))?$`);
248
+ /**
249
+ * #3939 (review): would folding `next` onto a lead-in whose `[tags]` bracket is
250
+ * still open splice the inserted space INTO a tag token?
251
+ *
252
+ * Tags are comma-split and trimmed, so a space landing next to a delimiter
253
+ * (`[`, `,`, `]`) changes nothing — `[informational,` + `deferred]` is still
254
+ * exactly `[informational, deferred]`. A space landing anywhere else splits one
255
+ * token into two (`[defer` + `red]` → `defer red`), which would not fail; it
256
+ * would parse to a DIFFERENT tag, silently flipping `trackable` on a gate that
257
+ * decides whether a decision must be covered. Refusing to join there leaves the
258
+ * bullet unchanged, so it reaches the #1365 parse-miss guard and fails loud —
259
+ * a wrong answer about coverage is worse than a blocked gate.
260
+ *
261
+ * `tail` is the bracket content accumulated so far, `next` the trimmed
262
+ * continuation line.
263
+ */
264
+ function wouldSpliceTagToken(tail, next) {
265
+ const before = tail.trimEnd();
266
+ if (before === '' || before.endsWith(',') || before.endsWith('['))
267
+ return false;
268
+ return !(next.startsWith(',') || next.startsWith(']'));
269
+ }
270
+ /**
271
+ * True when the bullet's own bold lead-in — the FIRST bold run on the line —
272
+ * is still open at end-of-line. Deliberately asks only about that first run
273
+ * (not `**`-parity over the whole string), because that is the run the three
274
+ * grammars anchor on: a balanced inline `**bold**` later in the body must not
275
+ * make a terminated lead-in look open.
276
+ */
277
+ function boldLeadInIsUnterminated(text) {
278
+ const open = text.indexOf('**');
279
+ if (open === -1)
280
+ return false;
281
+ return text.indexOf('**', open + 2) === -1;
282
+ }
283
+ /**
284
+ * Fold a decision bullet whose bold lead-in wraps into ONE logical line, so the
285
+ * declaration grammars see the whole lead-in (#3939).
286
+ *
287
+ * Bounded and fail-loud-preserving: a wrapped declaration absorbs following
288
+ * lines only until its lead-in closes, and a blank/whitespace-only line, a new
289
+ * block-level construct (`blockConstructRe`), or the end of the block stops it.
290
+ * If the lead-in never closes, the original line is emitted UNCHANGED — a
291
+ * genuinely malformed bullet (e.g. an unterminated bold run) still reaches the
292
+ * parse-miss guard and still fails loud, exactly as #1365 requires. Non-decision
293
+ * lines pass through untouched, so continuation lines (#1372 FIX) and nested
294
+ * cross-reference bullets (#3169) are handled by the main loop as before.
295
+ *
296
+ * The joined line keeps the FIRST physical line's leading whitespace, so the
297
+ * `indentWidth` signal #3169 depends on is unchanged. Absorbed lines are
298
+ * trimmed and re-joined with a single space, which is what a soft line break
299
+ * means in markdown — so the join reproduces the rendered one-line text rather
300
+ * than concatenating the raw bytes.
301
+ *
302
+ * One place that equivalence does not hold is inside the id-adjacent `[tags]`
303
+ * bracket, where an inserted space can split a tag token and silently flip
304
+ * `trackable`. The join stops there instead (`wouldSpliceTagToken`), leaving the
305
+ * bullet to fail loud.
306
+ *
307
+ * Absorption stops at the first `**` on a continuation line, so an inline
308
+ * `**bold**` INSIDE a wrapped title closes the run early. That is deliberate:
309
+ * the result is byte-identical to what the same bullet written on one physical
310
+ * line parses to (the text past the early close re-attaches through the main
311
+ * loop's continuation folding), which is the whole contract here — wrapping is
312
+ * markdown-insignificant, never a second grammar.
313
+ */
314
+ function joinWrappedBoldLeadIns(lines) {
315
+ const joined = [];
316
+ for (let i = 0; i < lines.length; i += 1) {
317
+ const line = lines[i];
318
+ if (!decisionBulletStartRe.test(line) || !boldLeadInIsUnterminated(line)) {
319
+ joined.push(line);
320
+ continue;
321
+ }
322
+ // Absorbed lines accumulate as SEGMENTS joined by a single space, and each
323
+ // new segment is searched on its own: the lead-in is known to be open at the
324
+ // end of the declaration line, and the inserted space means a closing `**`
325
+ // can never straddle a segment boundary, so the first `**` in any later
326
+ // segment is the close. Scanning per segment (rather than re-searching the
327
+ // accumulated string, which forces a rope flatten every iteration) keeps a
328
+ // long unterminated run linear on the plan gate's hot path.
329
+ const segments = [line];
330
+ // Bracket content accumulated while the id-adjacent `[tags]` bracket is
331
+ // still open; null when it is not open. O(1) per segment.
332
+ let tagTail = null;
333
+ // The logical text assembled so far, kept ONLY while the id-adjacent
334
+ // bracket has yet to open, so a bracket that opens on ANY absorbed line
335
+ // arms the splice guard — not just one that opens on the declaration line.
336
+ // Null once the region settles (the bracket opened and `tagTail` took over,
337
+ // or the id was followed by something else), so this never re-walks a long
338
+ // absorption: a non-empty segment that is not a bracket-open settles the
339
+ // region immediately, which bounds the string to a single extra join.
340
+ let tagRegion = null;
341
+ const declRegion = tagRegionRe.exec(line);
342
+ if (declRegion !== null) {
343
+ if (declRegion[1] === undefined)
344
+ tagRegion = line;
345
+ else
346
+ tagTail = declRegion[1];
347
+ }
348
+ let scan = i + 1;
349
+ let closed = false;
350
+ while (scan < lines.length) {
351
+ const trimmed = lines[scan].trim();
352
+ if (trimmed === '' || blockConstructRe.test(trimmed))
353
+ break;
354
+ if (tagTail !== null && wouldSpliceTagToken(tagTail, trimmed))
355
+ break;
356
+ segments.push(trimmed);
357
+ scan += 1;
358
+ if (tagTail !== null) {
359
+ tagTail = trimmed.indexOf(']') === -1 ? trimmed : null;
360
+ }
361
+ else if (tagRegion !== null) {
362
+ tagRegion = `${tagRegion} ${trimmed}`;
363
+ const opened = tagRegionRe.exec(tagRegion);
364
+ if (opened === null)
365
+ tagRegion = null;
366
+ else if (opened[1] !== undefined) {
367
+ tagTail = opened[1];
368
+ tagRegion = null;
369
+ }
370
+ }
371
+ if (trimmed.indexOf('**') !== -1) {
372
+ closed = true;
373
+ break;
374
+ }
375
+ }
376
+ if (closed) {
377
+ joined.push(segments.join(' '));
378
+ i = scan - 1;
379
+ }
380
+ else {
381
+ joined.push(line);
382
+ }
383
+ }
384
+ return joined;
385
+ }
78
386
  /**
79
387
  * Parse decision lines from a block of text (the inner text of a <decisions>
80
388
  * or markdown-header section body). Returns the extracted decisions and a count
@@ -83,9 +391,12 @@ const boldLeadInBulletRe = /^\s*-\s+\*\*[A-Z]+[0-9]*-[A-Za-z0-9]/m;
83
391
  * FIX B (#1365): parseMisses > 0 means the caller must treat the result as
84
392
  * could-not-parse even when some decisions were extracted — a silent drop is
85
393
  * worse than a fail-loud signal.
394
+ *
395
+ * #3939: physical lines are folded into logical bullets first, so a declaration
396
+ * whose bold lead-in wraps is matched as the one bullet it is.
86
397
  */
87
398
  function parseDecisionLines(block) {
88
- const lines = block.split(/\r?\n/);
399
+ const lines = joinWrappedBoldLeadIns(block.split(/\r?\n/));
89
400
  const out = [];
90
401
  let category = '';
91
402
  let inDiscretion = false;
@@ -135,11 +446,11 @@ function parseDecisionLines(block) {
135
446
  current.text += ' ' + trimmed;
136
447
  continue;
137
448
  }
138
- // Colon form: `- **D-NN[ [tags]]:** text`
449
+ // Colon form: `- **D[phase]-NN[ [tags]]:** text`
139
450
  const colonMatch = line.match(bulletColonRe);
140
451
  if (colonMatch) {
141
452
  flush();
142
- const id = `D-${colonMatch[1]}`;
453
+ const id = colonMatch[1];
143
454
  const tags = colonMatch[2]
144
455
  ? colonMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
145
456
  : [];
@@ -148,11 +459,11 @@ function parseDecisionLines(block) {
148
459
  openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
149
460
  continue;
150
461
  }
151
- // Em-dash form: `- **D-NN[ [tags]] — title** body`
462
+ // Em-dash form: `- **D[phase]-NN[ [tags]] — title** body`
152
463
  const emDashMatch = line.match(bulletEmDashRe);
153
464
  if (emDashMatch) {
154
465
  flush();
155
- const id = `D-${emDashMatch[1]}`;
466
+ const id = emDashMatch[1];
156
467
  const tags = emDashMatch[2]
157
468
  ? emDashMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
158
469
  : [];
@@ -164,14 +475,14 @@ function parseDecisionLines(block) {
164
475
  openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
165
476
  continue;
166
477
  }
167
- // Titled-colon form: `- **D-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
478
+ // Titled-colon form: `- **D[phase]-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
168
479
  // a strict superset of bulletColonRe, so it only catches bullets the colon-immediate
169
480
  // and em-dash forms missed (minimal blast radius). id + [tags] trackability honored;
170
481
  // the body after the closing bold run is reported as text.
171
482
  const titledColonMatch = line.match(bulletTitledColonRe);
172
483
  if (titledColonMatch) {
173
484
  flush();
174
- const id = `D-${titledColonMatch[1]}`;
485
+ const id = titledColonMatch[1];
175
486
  const tags = titledColonMatch[2]
176
487
  ? titledColonMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
177
488
  : [];
@@ -180,10 +491,14 @@ function parseDecisionLines(block) {
180
491
  openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
181
492
  continue;
182
493
  }
183
- // Parse-miss guard (FIX B + #1343): a line that looks like a `D-NN` decision
184
- // bullet but failed both patterns — flush, warn, and record the miss.
494
+ // Parse-miss guard (FIX B + #1343, grammar widened #4130): a line whose bold
495
+ // lead-in ATTEMPTS the ID grammar but failed all three patterns — flush,
496
+ // warn, and record the miss. `ID_ATTEMPT_SOURCE` accepts the bare `D-` form
497
+ // (as before) plus a digit-initial prefix run, so a typo'd phase prefix
498
+ // (`D4x-01`) fails loud instead of silently vanishing, while a letter-initial
499
+ // run (`Deferred-until`) stays prose and stays invisible.
185
500
  // parseMisses > 0 forces could-not-parse even when other decisions parsed.
186
- if (/^\s*-\s+\*\*D-/.test(line)) {
501
+ if (parseMissGuardRe.test(line)) {
187
502
  flush();
188
503
  parseMisses += 1;
189
504
  console.warn(`parseDecisions: ignored unparseable decision bullet: ${trimmed}`);
@@ -237,10 +552,10 @@ function extractDecisions(content) {
237
552
  // FIX A: Block present but 0 extracted and no parse-misses.
238
553
  // Only report could-not-parse when there is genuine evidence of real decisions
239
554
  // that failed to parse: a bold-lead-in bullet (`- **…**`, any ID grammar — #2347),
240
- // a \bD- token in the block text, or an unterminated fence. An empty scaffold
555
+ // a bare `D[phase]-<alnum>` token (#4130) in the block text, or an unterminated fence. An empty scaffold
241
556
  // (<decisions></decisions>) or an all-prose block has no such evidence — treat
242
557
  // as none-present so the gate passes cleanly.
243
- const hasDecisionTokenInBlock = /\bD-[A-Za-z0-9]/m.test(combined);
558
+ const hasDecisionTokenInBlock = decisionTokenRe.test(combined);
244
559
  const hasBoldLeadInBullet = boldLeadInBulletRe.test(combined);
245
560
  if (hasDecisionTokenInBlock || hasBoldLeadInBullet || unterminatedFence) {
246
561
  return { decisions: [], outcome: 'could-not-parse' };
@@ -263,10 +578,10 @@ function extractDecisions(content) {
263
578
  }
264
579
  // FIX A: Heading found but 0 extracted and no parse-misses.
265
580
  // Report could-not-parse when the section body holds a decision-entry-shaped
266
- // bold-lead-in bullet (`- **…**`, any ID grammar — #2347) or a D- token. A
581
+ // bold-lead-in bullet (`- **…**`, any ID grammar — #2347) or a `D[phase]-<alnum>` token (#4130). A
267
582
  // heading with only prose, sub-headings, or all-discretion content (no such
268
583
  // evidence) is a legitimate empty/discretion section → none-present.
269
- const hasDecisionTokenInSection = /\bD-[A-Za-z0-9]/m.test(section.body);
584
+ const hasDecisionTokenInSection = decisionTokenRe.test(section.body);
270
585
  const hasBoldLeadInBulletInSection = boldLeadInBulletRe.test(section.body);
271
586
  if (hasDecisionTokenInSection || hasBoldLeadInBulletInSection) {
272
587
  return { decisions: [], outcome: 'could-not-parse' };
@@ -275,8 +590,8 @@ function extractDecisions(content) {
275
590
  }
276
591
  // ── Path 3: no blocks, no heading ────────────────────────────────────────────
277
592
  // Apply shape heuristics to distinguish none-present from could-not-parse.
278
- // We re-use the already-computed unterminatedFence and check for D- tokens.
279
- const hasDecisionToken = /\bD-[A-Za-z0-9]/m.test(stripped);
593
+ // We re-use the already-computed unterminatedFence and check for decision tokens.
594
+ const hasDecisionToken = decisionTokenRe.test(stripped);
280
595
  if (unterminatedFence || hasDecisionToken) {
281
596
  return { decisions: [], outcome: 'could-not-parse' };
282
597
  }
@@ -110,6 +110,15 @@ function validateRequirement(requirement) {
110
110
  if (r.shapes == null && !(typeof r.text === 'string' && r.text.trim())) {
111
111
  throw new Error(`requirement ${requirement.id} text must be a non-empty string when no shapes override is provided`);
112
112
  }
113
+ // text_en (#3717) is optional, but when present it must be a non-empty string. An empty
114
+ // string is NOT caught by `??` (only null/undefined are), so an unvalidated `text_en: ''`
115
+ // would silently win `text_en ?? text` and classify against '' — the same fail-open shape
116
+ // #1110/#2773 already exist to eliminate, just moved one field over. Validated
117
+ // unconditionally (not gated on whether `shapes` will make it unused) so bad data fails
118
+ // closed even when it happens to be dead for this particular call.
119
+ if (r.text_en != null && !(typeof r.text_en === 'string' && r.text_en.trim())) {
120
+ throw new Error(`requirement ${requirement.id} text_en must be a non-empty string when present`);
121
+ }
113
122
  }
114
123
  /** Validate an edge resolution against the edge verification vocabulary. */
115
124
  function validateResolution(resolution) {
@@ -135,7 +144,11 @@ function proposeEdges(requirement) {
135
144
  shapes = requirement.shapes;
136
145
  }
137
146
  else {
138
- shapes = classifyShape(requirement.text);
147
+ // #3717: prefer the English translation when present — SHAPE_CUES are English-only
148
+ // word-boundary patterns, so a non-English `text` (e.g. response_language projects)
149
+ // would otherwise classify to zero shapes. validateRequirement (called above) has
150
+ // already guaranteed text_en, if present, is a non-empty string.
151
+ shapes = classifyShape(requirement.text_en ?? requirement.text);
139
152
  if (shapes.length === 0) {
140
153
  // Prose present but no shape cue matched. Do NOT silently drop it (#1110): an
141
154
  // edge-relevant requirement whose phrasing missed every cue would otherwise vanish from
@@ -18,13 +18,10 @@
18
18
  * concern sharing the word "trust".
19
19
  */
20
20
  'use strict';
21
- var __importDefault = (this && this.__importDefault) || function (mod) {
22
- return (mod && mod.__esModule) ? mod : { "default": mod };
23
- };
24
21
  Object.defineProperty(exports, "__esModule", { value: true });
25
22
  exports.isPathConfined = isPathConfined;
26
23
  exports.assertDescriptorConfined = assertDescriptorConfined;
27
- const node_path_1 = __importDefault(require("node:path"));
24
+ const security_cjs_1 = require("./security.cjs");
28
25
  /**
29
26
  * Pure LEXICAL path-containment check (cross-platform). `target` is confined
30
27
  * to `root` iff resolving it relative to `root` (via `path.resolve` — string
@@ -36,20 +33,38 @@ const node_path_1 = __importDefault(require("node:path"));
36
33
  * `root`) that would redirect the LEXICALLY-confined path to a physically
37
34
  * different, unconfined location on disk. A caller relying on this for a
38
35
  * write-confinement guarantee against a symlink-planting attacker must pair
39
- * it with a symlink check (or refuse to follow symlinks at write time) — see
40
- * capability-source.cts's install adapters (:491,577,675), which is what
41
- * currently keeps every caller of this function's callers symlink-safe: they
42
- * reject symlinks upstream, before a target ever reaches a lexical-only check
43
- * like this one.
36
+ * it with a symlink check (or refuse to follow symlinks at write time). Only
37
+ * the capability-loader.cts route into assertDescriptorConfined gets this for
38
+ * free today: capability-source.cts's staging path rejects symlinks upstream,
39
+ * before a target ever reaches a lexical-only check like this one — see
40
+ * copyDirRecursive's `entry.isSymbolicLink()` throw (capability-source.cts:585-586)
41
+ * and the post-copy budget-walk re-check (capability-source.cts:671-674). This
42
+ * does NOT extend to isPathConfined's other callers: install-engine.cts:1608
43
+ * and install-profiles.cts:755,880 do not go through capability-source.cts's
44
+ * adapters at all and have no symlink guard of their own here. Of the
45
+ * remaining callers, only retired-artifact-cleanup.cts:69 carries its own
46
+ * defense, via a local `lstatSync(destDir).isSymbolicLink()` check at line 77.
47
+ *
48
+ * `opts.pathImpl` (default: the ambient `path` module) lets a caller inject
49
+ * `path.win32` or `path.posix`. This is security-relevant: the win32 branch
50
+ * (drive letters, UNC paths, `\` separator) is otherwise only reachable by
51
+ * actually running this process on a Windows host, so without injection a
52
+ * win32-specific confinement escape would be unverified on every other
53
+ * platform. This mirrors the platform-injection seam already used elsewhere
54
+ * in this repo, e.g. src/shell-command-projection.cts's `opts.platform`
55
+ * (#4641). All existing 2-arg callers are unaffected: the default resolves to
56
+ * the ambient `path`, preserving byte-identical behaviour.
57
+ *
58
+ * The containment DECISION here now comes from the canonical predicate in
59
+ * src/security.cts (`tryWithinRootLexical`, ADR-4650 decision 6) — this
60
+ * function keeps only the lexical RESOLUTION policy (no realpath, no
61
+ * filesystem access) as its own choice; the comparison itself is shared.
44
62
  */
45
- function isPathConfined(target, root) {
63
+ function isPathConfined(target, root, opts = {}) {
46
64
  if (typeof target !== 'string' || typeof root !== 'string' || target.length === 0 || root.length === 0) {
47
65
  return false;
48
66
  }
49
- const rootResolved = node_path_1.default.resolve(root);
50
- const targetResolved = node_path_1.default.resolve(root, target);
51
- const prefix = rootResolved + node_path_1.default.sep;
52
- return targetResolved === rootResolved || targetResolved.startsWith(prefix);
67
+ return (0, security_cjs_1.tryWithinRootLexical)(target, root, { pathImpl: opts.pathImpl }) !== null;
53
68
  }
54
69
  /**
55
70
  * Assert every destSubpath the descriptor declares (global + local artifact