@opengsd/gsd-core 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +85 -5
  4. package/agents/gsd-code-fixer.compact.md +1 -1
  5. package/agents/gsd-code-fixer.md +1 -1
  6. package/agents/gsd-code-reviewer.compact.md +5 -3
  7. package/agents/gsd-code-reviewer.md +8 -6
  8. package/agents/gsd-debug-session-manager.compact.md +1 -1
  9. package/agents/gsd-debug-session-manager.md +1 -1
  10. package/agents/gsd-debugger.md +2 -2
  11. package/agents/gsd-eval-auditor.compact.md +1 -1
  12. package/agents/gsd-eval-auditor.md +1 -1
  13. package/agents/gsd-executor.md +4 -4
  14. package/agents/gsd-intel-updater.compact.md +1 -1
  15. package/agents/gsd-intel-updater.md +1 -1
  16. package/agents/gsd-mempalace-curator.md +2 -2
  17. package/agents/gsd-phase-researcher.md +1 -1
  18. package/agents/gsd-plan-checker.md +7 -2
  19. package/agents/gsd-planner.md +3 -3
  20. package/agents/gsd-project-researcher.compact.md +1 -1
  21. package/agents/gsd-project-researcher.md +1 -1
  22. package/agents/gsd-research-synthesizer.compact.md +1 -1
  23. package/agents/gsd-research-synthesizer.md +1 -1
  24. package/agents/gsd-ui-auditor.compact.md +21 -30
  25. package/agents/gsd-ui-auditor.md +42 -51
  26. package/agents/gsd-ui-researcher.compact.md +1 -1
  27. package/agents/gsd-ui-researcher.md +1 -1
  28. package/agents/gsd-verifier.md +31 -9
  29. package/bin/install.js +126 -196
  30. package/commands/gsd/add-tests.md +6 -1
  31. package/commands/gsd/ai-integration-phase.md +6 -1
  32. package/commands/gsd/audit-fix.md +5 -0
  33. package/commands/gsd/audit-milestone.md +6 -1
  34. package/commands/gsd/autonomous.md +5 -0
  35. package/commands/gsd/capture.md +8 -4
  36. package/commands/gsd/code-review.md +7 -2
  37. package/commands/gsd/complete-milestone.md +4 -0
  38. package/commands/gsd/config.md +7 -3
  39. package/commands/gsd/debug.md +11 -7
  40. package/commands/gsd/discuss-phase.md +7 -3
  41. package/commands/gsd/docs-update.md +12 -7
  42. package/commands/gsd/eval-review.md +6 -1
  43. package/commands/gsd/execute-phase.md +12 -7
  44. package/commands/gsd/extract-learnings.md +5 -0
  45. package/commands/gsd/fast.md +4 -0
  46. package/commands/gsd/forensics.md +5 -1
  47. package/commands/gsd/graphify.md +10 -6
  48. package/commands/gsd/health.md +5 -0
  49. package/commands/gsd/help.md +7 -2
  50. package/commands/gsd/import.md +7 -3
  51. package/commands/gsd/inbox.md +5 -0
  52. package/commands/gsd/ingest-docs.md +5 -1
  53. package/commands/gsd/manager.md +6 -1
  54. package/commands/gsd/map-codebase.md +7 -3
  55. package/commands/gsd/mempalace-capture.md +5 -1
  56. package/commands/gsd/mempalace-recall.md +5 -1
  57. package/commands/gsd/milestone-summary.md +5 -1
  58. package/commands/gsd/mvp-phase.md +8 -3
  59. package/commands/gsd/new-milestone.md +6 -1
  60. package/commands/gsd/new-project.md +5 -0
  61. package/commands/gsd/next.md +6 -1
  62. package/commands/gsd/ns-context.md +4 -0
  63. package/commands/gsd/ns-ideate.md +4 -0
  64. package/commands/gsd/ns-manage.md +4 -0
  65. package/commands/gsd/ns-project.md +4 -0
  66. package/commands/gsd/ns-review.md +4 -0
  67. package/commands/gsd/ns-workflow.md +4 -0
  68. package/commands/gsd/onboard.md +6 -1
  69. package/commands/gsd/pause-work.md +5 -1
  70. package/commands/gsd/phase.md +8 -4
  71. package/commands/gsd/plan-phase.md +6 -1
  72. package/commands/gsd/plan-review-convergence.md +5 -1
  73. package/commands/gsd/pr-branch.md +4 -0
  74. package/commands/gsd/profile-user.md +5 -1
  75. package/commands/gsd/progress.md +6 -1
  76. package/commands/gsd/quick-batch.md +20 -8
  77. package/commands/gsd/quick.md +12 -7
  78. package/commands/gsd/review.md +5 -1
  79. package/commands/gsd/secure-phase.md +6 -1
  80. package/commands/gsd/ship.md +5 -0
  81. package/commands/gsd/sketch.md +7 -2
  82. package/commands/gsd/spec-phase.md +5 -1
  83. package/commands/gsd/spike.md +8 -3
  84. package/commands/gsd/surface.md +5 -1
  85. package/commands/gsd/thread.md +4 -0
  86. package/commands/gsd/ui-phase.md +6 -1
  87. package/commands/gsd/ui-review.md +6 -1
  88. package/commands/gsd/ultraplan-phase.md +5 -1
  89. package/commands/gsd/undo.md +5 -1
  90. package/commands/gsd/update.md +6 -2
  91. package/commands/gsd/validate-phase.md +6 -1
  92. package/commands/gsd/verify-work.md +6 -1
  93. package/commands/gsd/workspace.md +7 -3
  94. package/gsd-core/bin/gsd-tools.cjs +142 -56
  95. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  96. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  97. package/gsd-core/bin/lib/audit.cjs +78 -44
  98. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  99. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  100. package/gsd-core/bin/lib/capability-registry.cjs +183 -103
  101. package/gsd-core/bin/lib/capability-validator.cjs +16 -0
  102. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  103. package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
  104. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  105. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  106. package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
  107. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  108. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  109. package/gsd-core/bin/lib/commands.cjs +173 -170
  110. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  111. package/gsd-core/bin/lib/config.cjs +43 -12
  112. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  113. package/gsd-core/bin/lib/coverage.cjs +4 -8
  114. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  115. package/gsd-core/bin/lib/drift.cjs +177 -42
  116. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  117. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  118. package/gsd-core/bin/lib/frontmatter.cjs +413 -234
  119. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  120. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  121. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  122. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  123. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  124. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  125. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  126. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  127. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  128. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  129. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  130. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  131. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  132. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  133. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  134. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  135. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  136. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  137. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  138. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  139. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  140. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  141. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  142. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  143. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  144. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  145. package/gsd-core/bin/lib/init.cjs +162 -96
  146. package/gsd-core/bin/lib/install-engine.cjs +19 -35
  147. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  148. package/gsd-core/bin/lib/io.cjs +122 -3
  149. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  150. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  151. package/gsd-core/bin/lib/milestone.cjs +19 -1
  152. package/gsd-core/bin/lib/model-resolver.cjs +18 -18
  153. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  154. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  155. package/gsd-core/bin/lib/pattern.cjs +10 -0
  156. package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
  157. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  158. package/gsd-core/bin/lib/phase-id.cjs +1 -1
  159. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  160. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  161. package/gsd-core/bin/lib/phase.cjs +280 -80
  162. package/gsd-core/bin/lib/plan-document.cjs +93 -19
  163. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  164. package/gsd-core/bin/lib/planning-document.cjs +273 -40
  165. package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
  166. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  167. package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
  168. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  169. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  170. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  171. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  172. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  173. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  174. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  175. package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
  176. package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
  177. package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
  178. package/gsd-core/bin/lib/roadmap.cjs +169 -72
  179. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +91 -157
  180. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
  181. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  182. package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
  183. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +307 -591
  184. package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
  185. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  186. package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
  187. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  188. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  189. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  190. package/gsd-core/bin/lib/state-document.cjs +100 -22
  191. package/gsd-core/bin/lib/state.cjs +214 -105
  192. package/gsd-core/bin/lib/surface.cjs +2 -1
  193. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
  194. package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
  195. package/gsd-core/bin/lib/uat.cjs +424 -7
  196. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
  197. package/gsd-core/bin/lib/vendor/README.md +31 -9
  198. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  199. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  200. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  201. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  202. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  203. package/gsd-core/bin/lib/verification.cjs +1165 -314
  204. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  205. package/gsd-core/bin/lib/verify.cjs +286 -567
  206. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  207. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  208. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  209. package/gsd-core/bin/lib/workstream.cjs +11 -2
  210. package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
  211. package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
  212. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  213. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  214. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  215. package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
  216. package/gsd-core/references/execute-phase-response-language.md +1 -1
  217. package/gsd-core/references/gsd-run-resolver.md +1 -1
  218. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  219. package/gsd-core/references/offer-next.md +1 -1
  220. package/gsd-core/references/planning-config.md +1 -1
  221. package/gsd-core/references/spidr-splitting.md +1 -1
  222. package/gsd-core/references/tdd.md +10 -4
  223. package/gsd-core/references/verifier-phase-gates.md +5 -2
  224. package/gsd-core/references/verify-mvp-mode.md +2 -2
  225. package/gsd-core/references/workstream-flag.md +33 -3
  226. package/gsd-core/templates/README.md +1 -1
  227. package/gsd-core/templates/UAT.md +17 -1
  228. package/gsd-core/templates/config.json +2 -11
  229. package/gsd-core/templates/verification-report.md +1 -1
  230. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  231. package/gsd-core/workflows/add-backlog.md +1 -1
  232. package/gsd-core/workflows/add-phase.md +8 -7
  233. package/gsd-core/workflows/add-tests.md +3 -2
  234. package/gsd-core/workflows/add-todo.md +4 -3
  235. package/gsd-core/workflows/ai-integration-phase.md +3 -2
  236. package/gsd-core/workflows/audit-fix.md +1 -1
  237. package/gsd-core/workflows/audit-milestone.md +4 -3
  238. package/gsd-core/workflows/audit-uat.md +1 -1
  239. package/gsd-core/workflows/autonomous.md +28 -18
  240. package/gsd-core/workflows/check-todos.md +6 -5
  241. package/gsd-core/workflows/cleanup.md +1 -1
  242. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  243. package/gsd-core/workflows/code-review-fix.md +5 -4
  244. package/gsd-core/workflows/code-review.md +84 -90
  245. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  246. package/gsd-core/workflows/complete-milestone.md +12 -7
  247. package/gsd-core/workflows/debug.md +30 -5
  248. package/gsd-core/workflows/diagnose-issues.md +3 -2
  249. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  250. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  252. package/gsd-core/workflows/discuss-phase.md +4 -3
  253. package/gsd-core/workflows/do.md +1 -1
  254. package/gsd-core/workflows/docs-update.md +4 -3
  255. package/gsd-core/workflows/edit-phase.md +4 -3
  256. package/gsd-core/workflows/eval-review.md +5 -3
  257. package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
  258. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
  259. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  260. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  261. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  263. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  264. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  265. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  266. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  267. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  268. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  269. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  270. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  271. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  272. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  273. package/gsd-core/workflows/execute-phase.md +78 -170
  274. package/gsd-core/workflows/execute-plan.md +13 -19
  275. package/gsd-core/workflows/explore.md +2 -2
  276. package/gsd-core/workflows/extract-learnings.md +3 -2
  277. package/gsd-core/workflows/fast.md +1 -1
  278. package/gsd-core/workflows/forensics.md +1 -1
  279. package/gsd-core/workflows/graduation.md +1 -1
  280. package/gsd-core/workflows/health.md +2 -1
  281. package/gsd-core/workflows/import.md +3 -2
  282. package/gsd-core/workflows/inbox.md +1 -1
  283. package/gsd-core/workflows/ingest-docs.md +1 -1
  284. package/gsd-core/workflows/insert-phase.md +4 -3
  285. package/gsd-core/workflows/list-seeds.md +1 -1
  286. package/gsd-core/workflows/list-workspaces.md +1 -1
  287. package/gsd-core/workflows/manager.md +5 -3
  288. package/gsd-core/workflows/map-codebase.md +4 -3
  289. package/gsd-core/workflows/milestone-summary.md +3 -2
  290. package/gsd-core/workflows/mvp-phase.md +14 -14
  291. package/gsd-core/workflows/new-milestone.md +10 -10
  292. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  293. package/gsd-core/workflows/new-project.md +1 -1
  294. package/gsd-core/workflows/new-workspace.md +1 -1
  295. package/gsd-core/workflows/next.md +1 -1
  296. package/gsd-core/workflows/pause-work.md +2 -2
  297. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  298. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  299. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  300. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  301. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
  302. package/gsd-core/workflows/plan-phase.md +27 -18
  303. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  304. package/gsd-core/workflows/plant-seed.md +1 -1
  305. package/gsd-core/workflows/pr-branch.md +1 -1
  306. package/gsd-core/workflows/profile-user.md +1 -1
  307. package/gsd-core/workflows/progress.md +19 -49
  308. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
  309. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  310. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  311. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  312. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  313. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  314. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  315. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  316. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  317. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  318. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  319. package/gsd-core/workflows/quick-batch.md +15 -10
  320. package/gsd-core/workflows/quick.md +42 -31
  321. package/gsd-core/workflows/remove-phase.md +3 -2
  322. package/gsd-core/workflows/remove-workspace.md +1 -1
  323. package/gsd-core/workflows/resume-project.md +3 -2
  324. package/gsd-core/workflows/review.md +3 -2
  325. package/gsd-core/workflows/scan.md +3 -2
  326. package/gsd-core/workflows/secure-phase.md +11 -12
  327. package/gsd-core/workflows/settings-advanced.md +1 -1
  328. package/gsd-core/workflows/settings-integrations.md +1 -1
  329. package/gsd-core/workflows/settings.md +1 -1
  330. package/gsd-core/workflows/ship.md +12 -12
  331. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  332. package/gsd-core/workflows/sketch.md +1 -1
  333. package/gsd-core/workflows/smart-entry.md +1 -1
  334. package/gsd-core/workflows/spec-phase.md +1 -1
  335. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  336. package/gsd-core/workflows/spike.md +1 -1
  337. package/gsd-core/workflows/stats.md +1 -1
  338. package/gsd-core/workflows/sync-skills.md +1 -1
  339. package/gsd-core/workflows/thread.md +2 -2
  340. package/gsd-core/workflows/transition.md +13 -23
  341. package/gsd-core/workflows/ui-phase.md +5 -4
  342. package/gsd-core/workflows/ui-review.md +4 -3
  343. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  344. package/gsd-core/workflows/undo.md +1 -1
  345. package/gsd-core/workflows/validate-phase.md +10 -12
  346. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  347. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  348. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  349. package/gsd-core/workflows/verify-work.md +68 -59
  350. package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
  351. package/hooks/dist/gsd-check-update-worker.js +8 -0
  352. package/hooks/dist/gsd-check-update.js +8 -0
  353. package/hooks/dist/gsd-context-monitor.js +31 -8
  354. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  355. package/hooks/dist/gsd-secret-read-guard.js +147 -16
  356. package/hooks/dist/gsd-statusline.js +15 -7
  357. package/hooks/dist/gsd-update-banner.js +8 -0
  358. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  359. package/hooks/dist/gsd-workflow-guard.js +5 -4
  360. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  361. package/hooks/dist/lib/cli-exit.js +12 -0
  362. package/hooks/dist/lib/git-probe.js +17 -1
  363. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  364. package/hooks/gsd-agent-isolation-guard.js +8 -0
  365. package/hooks/gsd-check-update-worker.js +8 -0
  366. package/hooks/gsd-check-update.js +8 -0
  367. package/hooks/gsd-context-monitor.js +31 -8
  368. package/hooks/gsd-cursor-subagent-start.js +8 -0
  369. package/hooks/gsd-secret-read-guard.js +147 -16
  370. package/hooks/gsd-statusline.js +15 -7
  371. package/hooks/gsd-update-banner.js +8 -0
  372. package/hooks/gsd-windsurf-pre-write.js +11 -2
  373. package/hooks/gsd-workflow-guard.js +5 -4
  374. package/hooks/gsd-worktree-path-guard.js +6 -2
  375. package/hooks/hooks.json +5 -5
  376. package/hooks/lib/cli-exit.js +12 -0
  377. package/hooks/lib/git-probe.js +17 -1
  378. package/hooks/lib/isolation-sentinel.js +2 -2
  379. package/package.json +21 -4
  380. package/scripts/changeset/parse.cjs +52 -4
  381. package/scripts/ci-timeout-report.cjs +770 -4
  382. package/scripts/command-contract-helpers.cjs +12 -8
  383. package/scripts/docs-guard-registry.cjs +6 -0
  384. package/scripts/gen-features.cjs +13 -8
  385. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  386. package/scripts/gen-loop-host-contract.cjs +10 -1
  387. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  388. package/scripts/gen-plugin-skills.cjs +87 -1
  389. package/scripts/gen-research-agents.cjs +24 -31
  390. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  391. package/scripts/gen-test-timings.cjs +32 -7
  392. package/scripts/lib/cli-exit.cjs +12 -0
  393. package/scripts/lib/macos-conformance-tier.generated.cjs +20 -2
  394. package/scripts/lib/ndjson-reporter.cjs +28 -3
  395. package/scripts/lib/platform-conformance-tier.generated.cjs +34 -5
  396. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  397. package/scripts/lib/vendor-bundle.cjs +59 -0
  398. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  399. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  400. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  401. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  402. package/scripts/lint-descriptions.cjs +7 -3
  403. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
  404. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  405. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  406. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  407. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  408. package/scripts/lint-phase-id-drift.cjs +29 -0
  409. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  410. package/scripts/lint-response-language-coverage.cjs +3 -0
  411. package/scripts/lint-skill-deps.cjs +7 -3
  412. package/scripts/lint-test-file-count.allowlist.json +11 -1
  413. package/scripts/lint-test-file-count.cjs +34 -1
  414. package/scripts/lint-vendored-deps.cjs +41 -5
  415. package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
  416. package/scripts/mutation-matrix.cjs +50 -5
  417. package/scripts/require-issue-link-policy.cjs +6 -2
  418. package/scripts/sync-runtime-launcher.cjs +184 -2
  419. package/scripts/verify-npm-publish.cjs +76 -20
  420. package/skills/gsd-add-tests/SKILL.md +6 -1
  421. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  422. package/skills/gsd-audit-fix/SKILL.md +5 -0
  423. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  424. package/skills/gsd-autonomous/SKILL.md +5 -0
  425. package/skills/gsd-capture/SKILL.md +8 -4
  426. package/skills/gsd-code-review/SKILL.md +7 -2
  427. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  428. package/skills/gsd-config/SKILL.md +8 -4
  429. package/skills/gsd-debug/SKILL.md +11 -7
  430. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  431. package/skills/gsd-docs-update/SKILL.md +12 -7
  432. package/skills/gsd-eval-review/SKILL.md +6 -1
  433. package/skills/gsd-execute-phase/SKILL.md +12 -7
  434. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  435. package/skills/gsd-fast/SKILL.md +4 -0
  436. package/skills/gsd-forensics/SKILL.md +5 -1
  437. package/skills/gsd-graphify/SKILL.md +10 -6
  438. package/skills/gsd-health/SKILL.md +5 -0
  439. package/skills/gsd-help/SKILL.md +7 -2
  440. package/skills/gsd-import/SKILL.md +7 -3
  441. package/skills/gsd-inbox/SKILL.md +5 -0
  442. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  443. package/skills/gsd-manager/SKILL.md +6 -1
  444. package/skills/gsd-map-codebase/SKILL.md +7 -3
  445. package/skills/gsd-mempalace-capture/SKILL.md +5 -1
  446. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  447. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  448. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  449. package/skills/gsd-new-milestone/SKILL.md +6 -1
  450. package/skills/gsd-new-project/SKILL.md +5 -0
  451. package/skills/gsd-next/SKILL.md +6 -1
  452. package/skills/gsd-ns-context/SKILL.md +4 -0
  453. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  454. package/skills/gsd-ns-manage/SKILL.md +4 -0
  455. package/skills/gsd-ns-project/SKILL.md +4 -0
  456. package/skills/gsd-ns-review/SKILL.md +4 -0
  457. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  458. package/skills/gsd-onboard/SKILL.md +6 -1
  459. package/skills/gsd-pause-work/SKILL.md +5 -1
  460. package/skills/gsd-phase/SKILL.md +8 -4
  461. package/skills/gsd-plan-phase/SKILL.md +6 -1
  462. package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
  463. package/skills/gsd-pr-branch/SKILL.md +4 -0
  464. package/skills/gsd-profile-user/SKILL.md +5 -1
  465. package/skills/gsd-progress/SKILL.md +6 -1
  466. package/skills/gsd-quick/SKILL.md +16 -10
  467. package/skills/gsd-quick-batch/SKILL.md +20 -8
  468. package/skills/gsd-review/SKILL.md +5 -1
  469. package/skills/gsd-review-backlog/SKILL.md +3 -2
  470. package/skills/gsd-secure-phase/SKILL.md +6 -1
  471. package/skills/gsd-ship/SKILL.md +5 -0
  472. package/skills/gsd-sketch/SKILL.md +7 -2
  473. package/skills/gsd-spec-phase/SKILL.md +5 -1
  474. package/skills/gsd-spike/SKILL.md +8 -3
  475. package/skills/gsd-surface/SKILL.md +5 -1
  476. package/skills/gsd-thread/SKILL.md +4 -0
  477. package/skills/gsd-ui-phase/SKILL.md +6 -1
  478. package/skills/gsd-ui-review/SKILL.md +6 -1
  479. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  480. package/skills/gsd-undo/SKILL.md +5 -1
  481. package/skills/gsd-update/SKILL.md +6 -2
  482. package/skills/gsd-validate-phase/SKILL.md +6 -1
  483. package/skills/gsd-verify-work/SKILL.md +6 -1
  484. package/skills/gsd-workspace/SKILL.md +7 -3
  485. package/skills/gsd-workstreams/SKILL.md +6 -6
  486. package/vscode/package.json +1 -1
  487. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
@@ -18,19 +18,32 @@ const node_fs_1 = __importDefault(require("node:fs"));
18
18
  const node_path_1 = __importDefault(require("node:path"));
19
19
  // eslint-disable-next-line @typescript-eslint/no-require-imports
20
20
  const frontmatter = require("./frontmatter.cjs");
21
- const { extractFrontmatter } = frontmatter;
21
+ const { extractFrontmatter, frontmatterBlock } = frontmatter;
22
22
  // eslint-disable-next-line @typescript-eslint/no-require-imports
23
23
  const markdownSectionizer = require("./markdown-sectionizer.cjs");
24
24
  const { stripFencedCode } = markdownSectionizer;
25
25
  // eslint-disable-next-line @typescript-eslint/no-require-imports
26
26
  const verification = require("./verification.cjs");
27
- const { readVerificationStatus } = verification;
27
+ const { readVerificationStatus, reportStatusOf, isReportContained, VERIFICATION_STATUS } = verification;
28
28
  // eslint-disable-next-line @typescript-eslint/no-require-imports
29
29
  const phaseIdMod = require("./phase-id.cjs");
30
30
  const { scopeToPhase } = phaseIdMod;
31
31
  // eslint-disable-next-line @typescript-eslint/no-require-imports
32
32
  const coreUtils = require("./core-utils.cjs");
33
- const { normalizeLineEndings } = coreUtils;
33
+ const { normalizeLineEndings, countMatchedSummaries } = coreUtils;
34
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
35
+ const planScanMod = require("./plan-scan.cjs");
36
+ const { isRootPlanFile } = planScanMod;
37
+ // Two CommonMark-legal line terminators (LINE SEPARATOR, PARAGRAPH SEPARATOR)
38
+ // that a naive `split('\n')`-only scan would not treat as line breaks.
39
+ // Built via String.fromCharCode rather than written as a regex-literal escape
40
+ // sequence, deliberately: this source file is round-tripped through tooling
41
+ // that decodes a literal backslash-u escape into the real character, which
42
+ // would leave an ACTUAL U+2028/U+2029 sitting inside a regex literal — and
43
+ // both are themselves JS/TS source line terminators, so the regex literal
44
+ // containing one would be truncated at that point and fail to parse at all.
45
+ const EXOTIC_LINE_SEPARATORS = String.fromCharCode(0x2028) + String.fromCharCode(0x2029);
46
+ const LINE_SPLIT_RE = new RegExp('[\\n' + EXOTIC_LINE_SEPARATORS + ']');
34
47
  // ─── Blocking state sets (documented for maintainability) ─────────────────────
35
48
  // UAT file frontmatter `status` values that indicate the file is not fully done
36
49
  const BLOCKING_UAT_FM_STATUSES = new Set([
@@ -38,13 +51,11 @@ const BLOCKING_UAT_FM_STATUSES = new Set([
38
51
  ]);
39
52
  // UAT file frontmatter `result` values that indicate failure
40
53
  const BLOCKING_UAT_FM_RESULTS = new Set(['pending', 'blocked', 'failed']);
41
- // Canonical VERIFICATION frontmatter `status` value that indicates passing.
42
- const PASSING_VERIFICATION_STATUSES = new Set(['passed']);
43
- // VERIFICATION file frontmatter `status` values that explicitly block
44
- const BLOCKING_VERIFICATION_FM_STATUSES = new Set([
45
- 'human_needed', 'gaps_found', 'pending', 'blocked', 'partial',
46
- 'failed', 'in_progress',
47
- ]);
54
+ // #5118: the VERIFICATION report's `status` vocabulary is the owner's closed
55
+ // enum (src/verification.cts) — this module no longer keeps its own passing /
56
+ // blocking sets. `pending|blocked|partial|failed|in_progress` were never
57
+ // written by the verifier; a report carrying one (or any other out-of-set
58
+ // value) is now a VerificationStatusError from the owner, not a blocker.
48
59
  // UAT test-item `result` values that count as passing
49
60
  const PASSING_RESULTS = new Set(['passed', 'pass']);
50
61
  // #4546 — a `skipped` test-item whose reason carries the verify-work writer's
@@ -63,6 +74,18 @@ const DEFERRED_REASON_RE = /^["']?deferred follow-up\b/i;
63
74
  // section also bypasses the complete_session promotion offer; the section is
64
75
  // the durable project-level record. Variant spellings that do not match
65
76
  // ("Deferred follow-ups:", "followup") block — fail-closed by design.
77
+ // #4983 — the sibling case #4546 left unfixed: a `result: issue` test whose
78
+ // `## Gaps` entry (templates/UAT.md) has been reconciled to `status: resolved`
79
+ // by an executed gap-closure plan (verify-work.md's reconcile_gaps step,
80
+ // #1921) is a genuinely fixed-and-verified issue: non-blocking. Unlike the
81
+ // deferred-skip case above, this signal is NOT trusted on the entry's
82
+ // `status:`/`resolved_by:` text alone — `isTestGapResolved` below also
83
+ // requires `resolved_by` to name a `*-PLAN.md` file that actually exists in
84
+ // this phase directory with a matching `*-SUMMARY.md` (mirroring
85
+ // reconcile_gaps' own criterion: a plan whose `gap_ids` names the gap AND has
86
+ // a SUMMARY was actually executed) — so a `resolved_by` naming no executed
87
+ // plan still blocks, closing the false-green risk a text-only trust model
88
+ // would leave open for a status this predicate gate is not free to bypass.
66
89
  // ─── stripFalsePositiveContexts ───────────────────────────────────────────────
67
90
  /**
68
91
  * Remove contexts that can contain `result: ...` lines that are NOT real test results:
@@ -75,8 +98,10 @@ const DEFERRED_REASON_RE = /^["']?deferred follow-up\b/i;
75
98
  * Returns surviving lines joined by '\n'. Robust to CRLF input.
76
99
  */
77
100
  function stripFalsePositiveContexts(content) {
78
- // Step (a): strip leading frontmatter block only at byte 0
79
- let stripped = content.replace(/^---\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/, '');
101
+ // Step (a): strip the leading frontmatter block — the one `frontmatterBlock` (the one fence
102
+ // owner) finds — with its closing fence line's line ending.
103
+ const block = frontmatterBlock(content);
104
+ let stripped = block ? block.rest.replace(/^\r?\n/, '') : content;
80
105
  // Step (b): remove HTML comments anywhere; unterminated comment swallows to EOF
81
106
  stripped = stripped.replace(/<!--[\s\S]*?(?:-->|$)/g, '');
82
107
  // Step (c): remove fenced code blocks via the canonical seam (ADR-1372 T5)
@@ -135,19 +160,19 @@ function parseUatResultItems(cleanContent) {
135
160
  // #3078-CR MEDIUM (security review follow-up): STRUCTURE and ATTRIBUTION
136
161
  // need different split frames. This is a STRUCTURE scan — finding where a
137
162
  // heading block begins — and there is no attribution distinction to
138
- // preserve, so split on any of `\n`, U+2028, U+2029: a heading delimited by
139
- // an exotic line separator (origin/next's `/m`-anchored scan found these;
140
- // a naive `split('\n')`-only port silently stopped finding them, making the
141
- // gate MORE permissive than origin/next) is found exactly like a
142
- // `\n`-delimited one. Contrast the `result:` scan below, which is an
143
- // ATTRIBUTION scan and must NOT do this.
163
+ // preserve, so split on any of the two exotic line separators alongside
164
+ // ordinary `\n` (LINE_SPLIT_RE, above): a heading delimited by one of them
165
+ // (origin/next's `/m`-anchored scan found these; a naive `split('\n')`-only
166
+ // port silently stopped finding them, making the gate MORE permissive than
167
+ // origin/next) is found exactly like a `\n`-delimited one. Contrast the
168
+ // `result:` scan below, which is an ATTRIBUTION scan and must NOT do this.
144
169
  const HEADING_LINE_RE = /^###\s*(\d+)\.\s*(.+)$/;
145
170
  const headings = [];
146
171
  {
147
172
  // All three separators are exactly one UTF-16 code unit, so the
148
173
  // `line.length + 1` offset arithmetic below stays valid regardless of
149
174
  // which separator terminated a given line.
150
- const lines = cleanContent.split(/[\n\u2028\u2029]/);
175
+ const lines = cleanContent.split(LINE_SPLIT_RE);
151
176
  let offset = 0;
152
177
  for (const line of lines) {
153
178
  const hMatch = line.match(HEADING_LINE_RE);
@@ -168,8 +193,8 @@ function parseUatResultItems(cleanContent) {
168
193
  // A block spans until the START of the next heading's line (tracked
169
194
  // directly from the same split-frame scan above), not a re-search for a
170
195
  // literal '\n###' over unsplit text -- the latter would silently miss a
171
- // next heading delimited by U+2028/U+2029 instead of '\n' and swallow
172
- // every subsequent block into this one.
196
+ // next heading delimited by an exotic separator instead of '\n' and
197
+ // swallow every subsequent block into this one.
173
198
  const blockContent = i + 1 < headings.length
174
199
  ? cleanContent.slice(blockStart, headings[i + 1].lineStart)
175
200
  : cleanContent.slice(blockStart);
@@ -177,9 +202,9 @@ function parseUatResultItems(cleanContent) {
177
202
  // the heading scan above): test each already-split line individually
178
203
  // against a single-line (no `/m` anchor) pattern instead of anchoring
179
204
  // over unsplit `blockContent`, so a `result:`-shaped line reachable only
180
- // via a U+2028/U+2029 separator inside an `expected: |` scalar body can
181
- // never register as a fake column-0 match. FIRST MATCH WINS — no
182
- // ambiguity counting, matching src/uat.cts's contract.
205
+ // via an exotic separator inside an `expected: |` scalar body can never
206
+ // register as a fake column-0 match. FIRST MATCH WINS — no ambiguity
207
+ // counting, matching src/uat.cts's contract.
183
208
  // Uses [ \t]* (not \s*) so the captured value must sit on the SAME line as result:.
184
209
  // A result: key with the value on a subsequent line yields no match → 'missing' (blocker).
185
210
  // #4546: the `reason:` line is captured with the same frame and FIRST-match
@@ -218,6 +243,234 @@ function parseUatResultItems(cleanContent) {
218
243
  }
219
244
  return items;
220
245
  }
246
+ /**
247
+ * #4983: parse the UAT file's `## Gaps` bullet list (templates/UAT.md) for
248
+ * the four scalar fields the predicate needs to decide whether a resolved
249
+ * `result: issue` test still blocks — `test`, `status`, `resolved_by`,
250
+ * `gap_id`.
251
+ *
252
+ * Deliberately a separate, minimal parser from src/uat.cts's `parseGapsItems`
253
+ * (the read-side/audit parser): this module hardens independently, on the
254
+ * ALREADY-CLEANED body (stripFalsePositiveContexts has already removed
255
+ * frontmatter/HTML comments/fenced code/blockquotes), and only needs these
256
+ * four fields — never the nested `artifacts:`/`missing:` sub-lists the
257
+ * read-side parser also handles.
258
+ *
259
+ * Entries are split on column-0 `- ` bullet openers (the template's shape);
260
+ * a nested sub-list item (e.g. a ` artifacts:` key followed by an indented
261
+ * ` - path: "..."` line) is indented and never matches the column-0
262
+ * opener, so it can never be mistaken for a new top-level entry.
263
+ *
264
+ * Fields are matched per line, first-match-wins, bounded to the entry's OWN
265
+ * top-level indentation — either the bullet-opener line itself (0 leading
266
+ * spaces once `BULLET_OPENER_RE` has stripped the `"- "` marker) or a
267
+ * continuation line indented by exactly the template's 2 spaces (each field
268
+ * regex below is anchored `^[ ]{0,2}key:`). A candidate line indented deeper
269
+ * than that (4+ spaces) is a nested sub-list's body or a multi-line scalar
270
+ * VALUE, never a sibling
271
+ * field declaration, and is deliberately NOT trimmed-and-matched the way an
272
+ * earlier revision of this function did (#4983 review round 1, MEDIUM): a
273
+ * block-scalar value line that happens to read "status: resolved" after
274
+ * trimming (e.g. embedded free text) could otherwise be mistaken for a real
275
+ * field at the wrong indentation, the same false-positive class
276
+ * `parseUatResultItems` above already guards against via column-0 anchoring
277
+ * for `result:`/`reason:`.
278
+ */
279
+ function parseGapsEntries(cleanContent) {
280
+ const GAPS_HEADING_RE = /^##\s*Gaps\s*$/i;
281
+ const OTHER_HEADING_RE = /^##\s+\S/;
282
+ const lines = cleanContent.split('\n');
283
+ let sectionStart = -1;
284
+ let sectionEnd = lines.length;
285
+ for (let i = 0; i < lines.length; i++) {
286
+ if (sectionStart === -1) {
287
+ if (GAPS_HEADING_RE.test(lines[i]))
288
+ sectionStart = i + 1;
289
+ }
290
+ else if (OTHER_HEADING_RE.test(lines[i])) {
291
+ sectionEnd = i;
292
+ break;
293
+ }
294
+ }
295
+ if (sectionStart === -1)
296
+ return [];
297
+ // Split the section body into entries at column-0 `- ` bullet openers.
298
+ // The opener line's own field (if any) is stored with a synthetic 0-space
299
+ // prefix removed — see FIELD_INDENT_RE below, which accepts both 0 and 2
300
+ // leading spaces as "this entry's own top level".
301
+ const BULLET_OPENER_RE = /^-\s?(.*)$/;
302
+ const entryBlocks = [];
303
+ for (const line of lines.slice(sectionStart, sectionEnd)) {
304
+ const m = line.match(BULLET_OPENER_RE);
305
+ if (m) {
306
+ entryBlocks.push([m[1]]);
307
+ }
308
+ else if (entryBlocks.length > 0) {
309
+ entryBlocks[entryBlocks.length - 1].push(line);
310
+ }
311
+ // Lines before the first bullet (blank lines, the template's own
312
+ // `<!-- YAML format ... -->` comment — already stripped by the caller)
313
+ // carry no entry and are simply ignored.
314
+ }
315
+ const stripQuotes = (v) => v.trim().replace(/^["']+|["']+$/g, '').trim();
316
+ // Bounded to 0-2 leading spaces: the bullet-opener line's stored text has
317
+ // 0 (its `- ` marker already stripped), and a real continuation line in
318
+ // the template's own two-space-indented shape has exactly 2. Anything
319
+ // indented deeper (a scalar body, a nested `artifacts:`/`missing:` list
320
+ // item) is excluded by construction, not merely by accident of key name.
321
+ const TEST_FIELD_RE = /^[ ]{0,2}test:[ \t]*(.*)$/i;
322
+ const STATUS_FIELD_RE = /^[ ]{0,2}status:[ \t]*(.*)$/i;
323
+ const RESOLVED_BY_FIELD_RE = /^[ ]{0,2}resolved_by:[ \t]*(.*)$/i;
324
+ const GAP_ID_FIELD_RE = /^[ ]{0,2}gap_id:[ \t]*(.*)$/i;
325
+ const entries = [];
326
+ for (const block of entryBlocks) {
327
+ let test = null;
328
+ let status = '';
329
+ let resolvedBy = '';
330
+ let gapId = '';
331
+ for (const rawLine of block) {
332
+ if (test === null) {
333
+ const tm = rawLine.match(TEST_FIELD_RE);
334
+ if (tm) {
335
+ const n = parseInt(stripQuotes(tm[1]).replace(/[[\]]/g, ''), 10);
336
+ if (!Number.isNaN(n))
337
+ test = n;
338
+ continue;
339
+ }
340
+ }
341
+ if (!status) {
342
+ const sm = rawLine.match(STATUS_FIELD_RE);
343
+ if (sm) {
344
+ status = stripQuotes(sm[1]).toLowerCase();
345
+ continue;
346
+ }
347
+ }
348
+ if (!resolvedBy) {
349
+ const rm = rawLine.match(RESOLVED_BY_FIELD_RE);
350
+ if (rm) {
351
+ resolvedBy = stripQuotes(rm[1]);
352
+ continue;
353
+ }
354
+ }
355
+ if (!gapId) {
356
+ const gm = rawLine.match(GAP_ID_FIELD_RE);
357
+ if (gm) {
358
+ gapId = stripQuotes(gm[1]);
359
+ continue;
360
+ }
361
+ }
362
+ }
363
+ entries.push({ test, status, resolvedBy, gapId });
364
+ }
365
+ return entries;
366
+ }
367
+ // ─── isTestGapResolved ────────────────────────────────────────────────────────
368
+ /**
369
+ * #4983: true only when EVERY `## Gaps` entry recorded against `testNum` is a
370
+ * verified resolution — never on the presence of just one resolved entry,
371
+ * since a later regression re-opens the SAME test number with a fresh
372
+ * `gap_id` (per verify-work.md's reconcile_gaps contract) while a prior entry
373
+ * for it stays `resolved`; an unresolved regression must still block.
374
+ *
375
+ * A single entry only counts as a verified resolution when ALL of:
376
+ * - `status` is `resolved` (an entry with no gap recorded, or `status:
377
+ * failed`/blank, is not a resolution — fails closed);
378
+ * - `resolved_by` is non-empty (a partial resolution missing the writer's
379
+ * attribution fails closed, per #4983's acceptance criteria);
380
+ * - `resolved_by`, taken as a bare basename (rejecting any path separator —
381
+ * no directory traversal), names a `*-PLAN.md` file that actually exists
382
+ * in this phase directory;
383
+ * - that plan has a matching `*-SUMMARY.md` sibling in the same directory —
384
+ * the same "plan executed" evidence verify-work.md's own reconcile_gaps
385
+ * step requires (elaboration.md §1) before it ever writes `resolved_by`;
386
+ * - the entry's own `gap_id` is present, AND the named plan's frontmatter
387
+ * `gap_ids` array includes it — proof the plan resolved THIS gap, not
388
+ * some other one.
389
+ *
390
+ * The `gap_id` cross-check (#4983 review round 1, CRITICAL) closes a false-
391
+ * green a plan-existence-only check leaves wide open: `resolved_by` naming a
392
+ * real, executed plan is not proof that plan addressed THIS gap. Concretely,
393
+ * a phase with a legitimately-resolved gap on test 3 (`resolved_by:
394
+ * 05-02-PLAN.md`, `05-02-PLAN.md`'s own `gap_ids: [G-05-3]`) could otherwise
395
+ * have an UNRELATED, still-broken test 7 marked `status: resolved,
396
+ * resolved_by: 05-02-PLAN.md` (copy-pasted or mis-attributed) and the prior
397
+ * checks alone would accept it — `05-02-PLAN.md` and its SUMMARY genuinely
398
+ * exist, they just never touched gap 7. Reading the plan's own `gap_ids` and
399
+ * requiring it to name this entry's `gap_id` closes that: a resolution claim
400
+ * this gate cannot independently verify against the phase directory's own
401
+ * files, cross-referenced back to the SPECIFIC gap, is not trusted. An entry
402
+ * with no `gap_id` at all (an older, hand-written entry predating the
403
+ * `gap_id` convention) fails closed — there is nothing to cross-reference,
404
+ * so it cannot be verified.
405
+ *
406
+ * This is deliberately MORE than a text-only trust check (contrast the
407
+ * `DEFERRED_REASON_RE` deferred-skip case above, an accepted authoring
408
+ * contract, not a security boundary): #4983's acceptance criteria explicitly
409
+ * requires that "a `resolved_by` that names no executed plan still blocks" —
410
+ * reading this as "no plan that executed AND resolved THIS gap" is the
411
+ * reading that actually closes the false-green risk, not merely "some plan
412
+ * ran somewhere in this phase."
413
+ */
414
+ function isTestGapResolved(entries, testNum, dirEntries, phaseFullDir) {
415
+ const forTest = entries.filter((e) => e.test === testNum);
416
+ if (forTest.length === 0)
417
+ return false;
418
+ return forTest.every((e) => {
419
+ if (e.status !== 'resolved')
420
+ return false;
421
+ if (!e.resolvedBy)
422
+ return false;
423
+ if (node_path_1.default.basename(e.resolvedBy) !== e.resolvedBy)
424
+ return false;
425
+ // Canonical plan-filename predicate (src/plan-scan.cts) rather than a
426
+ // hand-rolled `-PLAN.md$` regex — one owner for "what is a plan file"
427
+ // across the codebase (lint-plan-count-drift.cjs enforces this).
428
+ if (!isRootPlanFile(e.resolvedBy))
429
+ return false;
430
+ if (!dirEntries.includes(e.resolvedBy))
431
+ return false;
432
+ // Canonical plan→summary pairing (src/core-utils.cts), same reason: a
433
+ // single-plan/single-candidate-set query reuses the exact matching rules
434
+ // scanPhasePlans's own summaryCount is built from, rather than a
435
+ // hand-rolled `-PLAN.md` → `-SUMMARY.md` suffix swap.
436
+ if (countMatchedSummaries([e.resolvedBy], dirEntries) !== 1)
437
+ return false;
438
+ // Fail closed on an entry with no gap_id to cross-reference — there is
439
+ // nothing to verify the named plan actually resolved THIS gap against.
440
+ if (!e.gapId)
441
+ return false;
442
+ return planClaimsGapId(phaseFullDir, e.resolvedBy, e.gapId);
443
+ });
444
+ }
445
+ /**
446
+ * #4983 (review round 1, CRITICAL follow-up): read the named plan's own
447
+ * frontmatter `gap_ids` array and confirm it includes `gapId` — the
448
+ * cross-reference `isTestGapResolved` needs to confirm a plan that exists
449
+ * and has a SUMMARY actually claims to resolve THIS gap, not merely that it
450
+ * ran. Any failure to read or parse the plan (missing file, unreadable,
451
+ * malformed frontmatter, `gap_ids` absent or not an array) returns `false` —
452
+ * fail-closed, matching this module's convention throughout.
453
+ */
454
+ function planClaimsGapId(phaseFullDir, planBasename, gapId) {
455
+ let raw;
456
+ try {
457
+ raw = node_fs_1.default.readFileSync(node_path_1.default.join(phaseFullDir, planBasename), 'utf-8');
458
+ }
459
+ catch {
460
+ return false;
461
+ }
462
+ let fm;
463
+ try {
464
+ fm = extractFrontmatter(raw, planBasename);
465
+ }
466
+ catch {
467
+ return false;
468
+ }
469
+ const gapIds = fm['gap_ids'];
470
+ if (!Array.isArray(gapIds))
471
+ return false;
472
+ return gapIds.some((id) => typeof id === 'string' && id.trim() === gapId.trim());
473
+ }
221
474
  // ─── evaluateUatPassed ────────────────────────────────────────────────────────
222
475
  /**
223
476
  * Evaluate all UAT/VERIFICATION files in a phase directory.
@@ -269,8 +522,15 @@ function evaluateUatPassed(phaseFullDir, opts) {
269
522
  const verFileNames = scopeToPhase(dirEntries.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md')), phaseDirBaseName);
270
523
  // ── Process UAT files ──────────────────────────────────────────────────────
271
524
  for (const file of uatFileNames) {
272
- uatFiles.push(file);
273
525
  const uatFilePath = node_path_1.default.join(phaseFullDir, file);
526
+ // #5118 security review (SEC-1): containment BEFORE the read, exactly as
527
+ // for the VERIFICATION loop below — a `*-UAT.md` / `*-HUMAN-UAT.md` whose
528
+ // real path escapes the phase directory is treated as absent (not listed,
529
+ // no blocker naming it), so the `### N. <name>` text it points at never
530
+ // reaches a blocker or a check row.
531
+ if (!isReportContained(phaseFullDir, uatFilePath))
532
+ continue;
533
+ uatFiles.push(file);
274
534
  let raw = '';
275
535
  try {
276
536
  // #3078-CR MEDIUM: normalize line endings at the read boundary — the
@@ -303,6 +563,10 @@ function evaluateUatPassed(phaseFullDir, opts) {
303
563
  // Parse test items from the cleaned body (hardened against false positives)
304
564
  const cleanContent = stripFalsePositiveContexts(raw);
305
565
  const items = parseUatResultItems(cleanContent);
566
+ // #4983: parsed once per file — `evaluateUatPassed` already has `dirEntries`
567
+ // from the readdir above, so `isTestGapResolved` can verify `resolved_by`
568
+ // against this phase directory's own files.
569
+ const gapEntries = parseGapsEntries(cleanContent);
306
570
  for (const item of items) {
307
571
  // #4546: a `skipped` item whose reason matches the verify-work writer's
308
572
  // "Deferred follow-up:" template is a deliberately deferred follow-up
@@ -310,7 +574,10 @@ function evaluateUatPassed(phaseFullDir, opts) {
310
574
  // reason WITH its wrapping quotes. Everything else — pending, blocked,
311
575
  // issue, missing, and a plain or non-deferral skipped — still blocks.
312
576
  const deferred = item.result === 'skipped' && DEFERRED_REASON_RE.test(item.reason);
313
- const passing = PASSING_RESULTS.has(item.result) || deferred;
577
+ // #4983: an `issue` item whose `## Gaps` entry has been verified-resolved
578
+ // (see isTestGapResolved) is a genuinely fixed issue — non-blocking.
579
+ const resolved = item.result === 'issue' && isTestGapResolved(gapEntries, item.test, dirEntries, phaseFullDir);
580
+ const passing = PASSING_RESULTS.has(item.result) || deferred || resolved;
314
581
  checks.push({
315
582
  file,
316
583
  test: item.test,
@@ -318,6 +585,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
318
585
  result: item.result,
319
586
  passing,
320
587
  deferred,
588
+ resolved,
321
589
  });
322
590
  if (!passing) {
323
591
  blockers.push(`${file}: test ${item.test} (${item.result})`);
@@ -329,6 +597,11 @@ function evaluateUatPassed(phaseFullDir, opts) {
329
597
  for (const file of uatOnly ? [] : verFileNames) {
330
598
  verificationFiles.push(file);
331
599
  const verificationFilePath = node_path_1.default.join(phaseFullDir, file);
600
+ // #5118 security review (S1): containment BEFORE the read — a report whose
601
+ // real path escapes the planning root reads `missing` (no status counts,
602
+ // no blocker) and not a byte of it reaches any message.
603
+ if (!isReportContained(phaseFullDir, verificationFilePath))
604
+ continue;
332
605
  let raw = '';
333
606
  try {
334
607
  // #3078-CR MEDIUM: same read-boundary normalization as the UAT loop above.
@@ -338,16 +611,17 @@ function evaluateUatPassed(phaseFullDir, opts) {
338
611
  blockers.push(`${file}: could not read verification file`);
339
612
  continue;
340
613
  }
341
- const vfm = extractFrontmatter(raw, verificationFilePath);
342
- const vStatus = vfm['status'];
343
- if (vStatus && BLOCKING_VERIFICATION_FM_STATUSES.has(vStatus)) {
614
+ // #5118: judged by the owner's report reader — a status outside the
615
+ // closed writer set throws VerificationStatusError (no silent pass-through).
616
+ const vStatus = reportStatusOf(extractFrontmatter(raw, verificationFilePath), verificationFilePath);
617
+ if (vStatus === VERIFICATION_STATUS.HUMAN_NEEDED || vStatus === VERIFICATION_STATUS.GAPS_FOUND) {
344
618
  blockers.push(`${file}: verification status=${vStatus}`);
345
619
  }
346
- else if (vStatus && PASSING_VERIFICATION_STATUSES.has(vStatus)) {
347
- // Allowlist: only explicitly-passing statuses count
620
+ else if (vStatus === VERIFICATION_STATUS.PASSED) {
621
+ // Allowlist: only an explicitly-passing status counts
348
622
  hasPassingVerification = true;
349
623
  }
350
- // Missing or unknown status: does NOT count as passing, does NOT push a blocker
624
+ // No status: does NOT count as passing, does NOT push a blocker
351
625
  // (handled by the requireVerification policy check below if needed)
352
626
  }
353
627
  // ── Policy: requireVerification ───────────────────────────────────────────
@@ -360,10 +634,10 @@ function evaluateUatPassed(phaseFullDir, opts) {
360
634
  const verificationResult = readVerificationStatus(phaseFullDir);
361
635
  const verificationStatus = verificationResult.status;
362
636
  verificationStaleCheckIndeterminate = verificationResult.staleCheckIndeterminate === true;
363
- if (verificationStatus === 'stale') {
364
- blockers.push('policy: verification status=stale');
637
+ if (verificationStatus === VERIFICATION_STATUS.STALE) {
638
+ blockers.push(`policy: verification status=${VERIFICATION_STATUS.STALE}`);
365
639
  }
366
- else if (verificationStatus !== 'passed' || !hasPassingVerification) {
640
+ else if (verificationStatus !== VERIFICATION_STATUS.PASSED || !hasPassingVerification) {
367
641
  blockers.push('policy: verification required but no passing *-VERIFICATION.md found');
368
642
  }
369
643
  }
@@ -392,4 +666,8 @@ module.exports = {
392
666
  parseUatResultItems,
393
667
  analyzeMarkdown,
394
668
  evaluateUatPassed,
669
+ // #4983: exported for direct unit-testability of the Gaps-resolution
670
+ // parser/validator, independent of a full evaluateUatPassed round-trip.
671
+ parseGapsEntries,
672
+ isTestGapResolved,
395
673
  };