@opengsd/gsd-core 1.15.0 → 1.17.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 (505) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -2
  3. package/.opencode/plugins/gsd-core.js +336 -6
  4. package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
  5. package/GEMINI.md +1 -1
  6. package/agents/gsd-code-fixer.compact.md +1 -1
  7. package/agents/gsd-code-fixer.md +1 -1
  8. package/agents/gsd-code-reviewer.compact.md +5 -3
  9. package/agents/gsd-code-reviewer.md +8 -6
  10. package/agents/gsd-debug-session-manager.compact.md +7 -1
  11. package/agents/gsd-debug-session-manager.md +7 -1
  12. package/agents/gsd-debugger.md +2 -2
  13. package/agents/gsd-eval-auditor.compact.md +1 -1
  14. package/agents/gsd-eval-auditor.md +1 -1
  15. package/agents/gsd-executor.md +4 -4
  16. package/agents/gsd-intel-updater.compact.md +1 -1
  17. package/agents/gsd-intel-updater.md +1 -1
  18. package/agents/gsd-mempalace-curator.md +2 -2
  19. package/agents/gsd-phase-researcher.md +1 -1
  20. package/agents/gsd-plan-checker.md +7 -2
  21. package/agents/gsd-planner.md +3 -3
  22. package/agents/gsd-project-researcher.compact.md +1 -1
  23. package/agents/gsd-project-researcher.md +1 -1
  24. package/agents/gsd-research-synthesizer.compact.md +1 -1
  25. package/agents/gsd-research-synthesizer.md +1 -1
  26. package/agents/gsd-ui-auditor.compact.md +21 -30
  27. package/agents/gsd-ui-auditor.md +42 -51
  28. package/agents/gsd-ui-researcher.compact.md +1 -1
  29. package/agents/gsd-ui-researcher.md +1 -1
  30. package/agents/gsd-verifier.md +31 -9
  31. package/bin/gsd-mcp-server.js +3 -1
  32. package/bin/install.js +487 -290
  33. package/commands/gsd/add-tests.md +6 -1
  34. package/commands/gsd/ai-integration-phase.md +6 -1
  35. package/commands/gsd/audit-fix.md +5 -0
  36. package/commands/gsd/audit-milestone.md +6 -1
  37. package/commands/gsd/autonomous.md +5 -0
  38. package/commands/gsd/capture.md +8 -4
  39. package/commands/gsd/code-review.md +7 -2
  40. package/commands/gsd/complete-milestone.md +4 -0
  41. package/commands/gsd/config.md +7 -3
  42. package/commands/gsd/debug.md +11 -7
  43. package/commands/gsd/discuss-phase.md +7 -3
  44. package/commands/gsd/docs-update.md +12 -7
  45. package/commands/gsd/eval-review.md +6 -1
  46. package/commands/gsd/execute-phase.md +12 -7
  47. package/commands/gsd/extract-learnings.md +5 -0
  48. package/commands/gsd/fast.md +4 -0
  49. package/commands/gsd/forensics.md +5 -1
  50. package/commands/gsd/graphify.md +10 -6
  51. package/commands/gsd/health.md +5 -0
  52. package/commands/gsd/help.md +7 -2
  53. package/commands/gsd/import.md +7 -3
  54. package/commands/gsd/inbox.md +5 -0
  55. package/commands/gsd/ingest-docs.md +5 -1
  56. package/commands/gsd/manager.md +6 -1
  57. package/commands/gsd/map-codebase.md +7 -3
  58. package/commands/gsd/mempalace-capture.md +5 -1
  59. package/commands/gsd/mempalace-recall.md +5 -1
  60. package/commands/gsd/milestone-summary.md +5 -1
  61. package/commands/gsd/mvp-phase.md +8 -3
  62. package/commands/gsd/new-milestone.md +6 -1
  63. package/commands/gsd/new-project.md +5 -0
  64. package/commands/gsd/next.md +6 -1
  65. package/commands/gsd/ns-context.md +4 -0
  66. package/commands/gsd/ns-ideate.md +4 -0
  67. package/commands/gsd/ns-manage.md +4 -0
  68. package/commands/gsd/ns-project.md +4 -0
  69. package/commands/gsd/ns-review.md +4 -0
  70. package/commands/gsd/ns-workflow.md +4 -0
  71. package/commands/gsd/onboard.md +6 -1
  72. package/commands/gsd/pause-work.md +5 -1
  73. package/commands/gsd/phase.md +8 -4
  74. package/commands/gsd/plan-phase.md +6 -1
  75. package/commands/gsd/plan-review-convergence.md +5 -1
  76. package/commands/gsd/pr-branch.md +4 -0
  77. package/commands/gsd/profile-user.md +5 -1
  78. package/commands/gsd/progress.md +6 -1
  79. package/commands/gsd/quick-batch.md +20 -8
  80. package/commands/gsd/quick.md +12 -7
  81. package/commands/gsd/review.md +5 -1
  82. package/commands/gsd/secure-phase.md +6 -1
  83. package/commands/gsd/ship.md +5 -0
  84. package/commands/gsd/sketch.md +7 -2
  85. package/commands/gsd/spec-phase.md +5 -1
  86. package/commands/gsd/spike.md +8 -3
  87. package/commands/gsd/surface.md +5 -1
  88. package/commands/gsd/thread.md +4 -0
  89. package/commands/gsd/ui-phase.md +6 -1
  90. package/commands/gsd/ui-review.md +6 -1
  91. package/commands/gsd/ultraplan-phase.md +5 -1
  92. package/commands/gsd/undo.md +5 -1
  93. package/commands/gsd/update.md +6 -2
  94. package/commands/gsd/validate-phase.md +6 -1
  95. package/commands/gsd/verify-work.md +6 -1
  96. package/commands/gsd/workspace.md +7 -3
  97. package/gsd-core/bin/gsd-tools.cjs +159 -58
  98. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  99. package/gsd-core/bin/lib/adr-parser.cjs +37 -5
  100. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  101. package/gsd-core/bin/lib/audit.cjs +78 -44
  102. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  103. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  104. package/gsd-core/bin/lib/capability-registry.cjs +249 -117
  105. package/gsd-core/bin/lib/capability-validator.cjs +18 -1
  106. package/gsd-core/bin/lib/capability-writer.cjs +2 -1
  107. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  108. package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
  109. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  110. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  111. package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
  112. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  113. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  114. package/gsd-core/bin/lib/commands.cjs +201 -178
  115. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  116. package/gsd-core/bin/lib/config.cjs +43 -12
  117. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  118. package/gsd-core/bin/lib/coverage.cjs +4 -8
  119. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  120. package/gsd-core/bin/lib/drift.cjs +177 -42
  121. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  122. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  123. package/gsd-core/bin/lib/frontmatter.cjs +413 -234
  124. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  125. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  126. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  127. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  128. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  129. package/gsd-core/bin/lib/gate-context-drift.cjs +142 -0
  130. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  131. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  132. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  133. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  134. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  135. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  136. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  137. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  138. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  139. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  140. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  141. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  142. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  143. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  144. package/gsd-core/bin/lib/gate-ui-safety.cjs +81 -0
  145. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  146. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  147. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  148. package/gsd-core/bin/lib/graphify.cjs +3 -2
  149. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  150. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  151. package/gsd-core/bin/lib/init.cjs +194 -108
  152. package/gsd-core/bin/lib/install-engine.cjs +30 -35
  153. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  154. package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
  155. package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
  156. package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
  157. package/gsd-core/bin/lib/io.cjs +122 -3
  158. package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
  159. package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
  160. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  161. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  162. package/gsd-core/bin/lib/mcp-server.cjs +5 -1
  163. package/gsd-core/bin/lib/milestone.cjs +19 -1
  164. package/gsd-core/bin/lib/model-resolver.cjs +18 -18
  165. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  166. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  167. package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
  168. package/gsd-core/bin/lib/opencode-version.cjs +486 -0
  169. package/gsd-core/bin/lib/pattern.cjs +10 -0
  170. package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
  171. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  172. package/gsd-core/bin/lib/phase-id.cjs +1 -1
  173. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  174. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  175. package/gsd-core/bin/lib/phase.cjs +301 -80
  176. package/gsd-core/bin/lib/plan-document.cjs +93 -19
  177. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  178. package/gsd-core/bin/lib/planning-document.cjs +273 -40
  179. package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
  180. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  181. package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
  182. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  183. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  184. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  185. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  186. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +13 -9
  187. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  188. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  189. package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
  190. package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
  191. package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
  192. package/gsd-core/bin/lib/roadmap.cjs +169 -72
  193. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +186 -161
  194. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
  195. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  196. package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
  197. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +365 -594
  198. package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
  199. package/gsd-core/bin/lib/runtime-slash.cjs +60 -30
  200. package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
  201. package/gsd-core/bin/lib/smart-entry.cjs +111 -56
  202. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  203. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  204. package/gsd-core/bin/lib/state-document.cjs +100 -22
  205. package/gsd-core/bin/lib/state-transition.cjs +67 -9
  206. package/gsd-core/bin/lib/state.cjs +668 -171
  207. package/gsd-core/bin/lib/surface.cjs +2 -1
  208. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
  209. package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
  210. package/gsd-core/bin/lib/uat.cjs +424 -7
  211. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
  212. package/gsd-core/bin/lib/update-context.cjs +18 -1
  213. package/gsd-core/bin/lib/vendor/README.md +31 -9
  214. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  215. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  216. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  217. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  218. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  219. package/gsd-core/bin/lib/verification.cjs +1265 -324
  220. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  221. package/gsd-core/bin/lib/verify.cjs +286 -567
  222. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  223. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  224. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  225. package/gsd-core/bin/lib/workstream.cjs +11 -2
  226. package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
  227. package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
  228. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  229. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  230. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  231. package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
  232. package/gsd-core/references/execute-phase-response-language.md +1 -1
  233. package/gsd-core/references/gsd-run-resolver.md +1 -1
  234. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  235. package/gsd-core/references/offer-next.md +1 -1
  236. package/gsd-core/references/planning-config.md +1 -1
  237. package/gsd-core/references/spidr-splitting.md +1 -1
  238. package/gsd-core/references/tdd.md +10 -4
  239. package/gsd-core/references/verifier-phase-gates.md +5 -2
  240. package/gsd-core/references/verify-mvp-mode.md +2 -2
  241. package/gsd-core/references/workstream-flag.md +33 -3
  242. package/gsd-core/templates/README.md +1 -1
  243. package/gsd-core/templates/UAT.md +17 -1
  244. package/gsd-core/templates/config.json +2 -11
  245. package/gsd-core/templates/verification-report.md +1 -1
  246. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  247. package/gsd-core/workflows/add-backlog.md +1 -1
  248. package/gsd-core/workflows/add-phase.md +8 -7
  249. package/gsd-core/workflows/add-tests.md +3 -2
  250. package/gsd-core/workflows/add-todo.md +4 -3
  251. package/gsd-core/workflows/ai-integration-phase.md +3 -2
  252. package/gsd-core/workflows/audit-fix.md +1 -1
  253. package/gsd-core/workflows/audit-milestone.md +4 -3
  254. package/gsd-core/workflows/audit-uat.md +1 -1
  255. package/gsd-core/workflows/autonomous.md +28 -18
  256. package/gsd-core/workflows/check-todos.md +6 -5
  257. package/gsd-core/workflows/cleanup.md +1 -1
  258. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  259. package/gsd-core/workflows/code-review-fix.md +5 -4
  260. package/gsd-core/workflows/code-review.md +100 -91
  261. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  262. package/gsd-core/workflows/complete-milestone.md +12 -7
  263. package/gsd-core/workflows/debug.md +30 -5
  264. package/gsd-core/workflows/diagnose-issues.md +3 -2
  265. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  266. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  267. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  268. package/gsd-core/workflows/discuss-phase.md +4 -3
  269. package/gsd-core/workflows/do.md +1 -1
  270. package/gsd-core/workflows/docs-update.md +4 -3
  271. package/gsd-core/workflows/edit-phase.md +4 -3
  272. package/gsd-core/workflows/eval-review.md +5 -3
  273. package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
  274. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
  275. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  276. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  277. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +2 -2
  278. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  279. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  280. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  281. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  282. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  283. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  284. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  285. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  286. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  288. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  289. package/gsd-core/workflows/execute-phase.md +81 -171
  290. package/gsd-core/workflows/execute-plan.md +13 -19
  291. package/gsd-core/workflows/explore.md +2 -2
  292. package/gsd-core/workflows/extract-learnings.md +3 -2
  293. package/gsd-core/workflows/fast.md +1 -1
  294. package/gsd-core/workflows/forensics.md +1 -1
  295. package/gsd-core/workflows/graduation.md +1 -1
  296. package/gsd-core/workflows/health.md +2 -1
  297. package/gsd-core/workflows/import.md +3 -2
  298. package/gsd-core/workflows/inbox.md +1 -1
  299. package/gsd-core/workflows/ingest-docs.md +1 -1
  300. package/gsd-core/workflows/insert-phase.md +4 -3
  301. package/gsd-core/workflows/list-seeds.md +1 -1
  302. package/gsd-core/workflows/list-workspaces.md +1 -1
  303. package/gsd-core/workflows/manager.md +5 -3
  304. package/gsd-core/workflows/map-codebase.md +4 -3
  305. package/gsd-core/workflows/milestone-summary.md +3 -2
  306. package/gsd-core/workflows/mvp-phase.md +14 -14
  307. package/gsd-core/workflows/new-milestone.md +10 -10
  308. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  309. package/gsd-core/workflows/new-project.md +1 -1
  310. package/gsd-core/workflows/new-workspace.md +1 -1
  311. package/gsd-core/workflows/next.md +1 -1
  312. package/gsd-core/workflows/pause-work.md +2 -2
  313. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  314. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  315. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  316. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  317. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
  318. package/gsd-core/workflows/plan-phase.md +27 -18
  319. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  320. package/gsd-core/workflows/plant-seed.md +1 -1
  321. package/gsd-core/workflows/pr-branch.md +1 -1
  322. package/gsd-core/workflows/profile-user.md +1 -1
  323. package/gsd-core/workflows/progress.md +19 -49
  324. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
  325. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  326. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  327. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  328. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  329. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  330. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  331. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  332. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  333. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  334. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  335. package/gsd-core/workflows/quick-batch.md +15 -10
  336. package/gsd-core/workflows/quick.md +42 -31
  337. package/gsd-core/workflows/remove-phase.md +3 -2
  338. package/gsd-core/workflows/remove-workspace.md +1 -1
  339. package/gsd-core/workflows/resume-project.md +17 -12
  340. package/gsd-core/workflows/review.md +3 -2
  341. package/gsd-core/workflows/scan.md +3 -2
  342. package/gsd-core/workflows/secure-phase.md +11 -12
  343. package/gsd-core/workflows/settings-advanced.md +1 -1
  344. package/gsd-core/workflows/settings-integrations.md +1 -1
  345. package/gsd-core/workflows/settings.md +1 -1
  346. package/gsd-core/workflows/ship.md +12 -12
  347. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  348. package/gsd-core/workflows/sketch.md +1 -1
  349. package/gsd-core/workflows/smart-entry.md +1 -1
  350. package/gsd-core/workflows/spec-phase.md +1 -1
  351. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  352. package/gsd-core/workflows/spike.md +1 -1
  353. package/gsd-core/workflows/stats.md +1 -1
  354. package/gsd-core/workflows/sync-skills.md +1 -1
  355. package/gsd-core/workflows/thread.md +2 -2
  356. package/gsd-core/workflows/transition.md +13 -23
  357. package/gsd-core/workflows/ui-phase.md +5 -4
  358. package/gsd-core/workflows/ui-review.md +4 -3
  359. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  360. package/gsd-core/workflows/undo.md +1 -1
  361. package/gsd-core/workflows/validate-phase.md +10 -12
  362. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  365. package/gsd-core/workflows/verify-work.md +68 -59
  366. package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
  367. package/hooks/dist/gsd-check-update-worker.js +8 -0
  368. package/hooks/dist/gsd-check-update.js +34 -20
  369. package/hooks/dist/gsd-context-monitor.js +45 -13
  370. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  371. package/hooks/dist/gsd-secret-read-guard.js +263 -16
  372. package/hooks/dist/gsd-statusline.js +336 -29
  373. package/hooks/dist/gsd-update-banner.js +8 -0
  374. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  375. package/hooks/dist/gsd-workflow-guard.js +5 -4
  376. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  377. package/hooks/dist/lib/cli-exit.js +12 -0
  378. package/hooks/dist/lib/git-probe.js +17 -1
  379. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  380. package/hooks/gsd-agent-isolation-guard.js +8 -0
  381. package/hooks/gsd-check-update-worker.js +8 -0
  382. package/hooks/gsd-check-update.js +34 -20
  383. package/hooks/gsd-context-monitor.js +45 -13
  384. package/hooks/gsd-cursor-subagent-start.js +8 -0
  385. package/hooks/gsd-secret-read-guard.js +263 -16
  386. package/hooks/gsd-statusline.js +336 -29
  387. package/hooks/gsd-update-banner.js +8 -0
  388. package/hooks/gsd-windsurf-pre-write.js +11 -2
  389. package/hooks/gsd-workflow-guard.js +5 -4
  390. package/hooks/gsd-worktree-path-guard.js +6 -2
  391. package/hooks/hooks.json +5 -5
  392. package/hooks/lib/cli-exit.js +12 -0
  393. package/hooks/lib/git-probe.js +17 -1
  394. package/hooks/lib/isolation-sentinel.js +2 -2
  395. package/package.json +21 -4
  396. package/scripts/changeset/parse.cjs +52 -4
  397. package/scripts/ci-timeout-report.cjs +770 -4
  398. package/scripts/command-contract-helpers.cjs +12 -8
  399. package/scripts/docs-guard-registry.cjs +6 -0
  400. package/scripts/gen-capability-registry.cjs +7 -1
  401. package/scripts/gen-features.cjs +13 -8
  402. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  403. package/scripts/gen-loop-host-contract.cjs +14 -2
  404. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  405. package/scripts/gen-plugin-skills.cjs +87 -1
  406. package/scripts/gen-research-agents.cjs +24 -31
  407. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  408. package/scripts/gen-test-timings.cjs +32 -7
  409. package/scripts/lib/cli-exit.cjs +12 -0
  410. package/scripts/lib/macos-conformance-tier.generated.cjs +24 -2
  411. package/scripts/lib/ndjson-reporter.cjs +28 -3
  412. package/scripts/lib/platform-conformance-tier.generated.cjs +44 -5
  413. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  414. package/scripts/lib/vendor-bundle.cjs +59 -0
  415. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  416. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  417. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  418. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  419. package/scripts/lint-descriptions.cjs +7 -3
  420. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
  421. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  422. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  423. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  424. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  425. package/scripts/lint-phase-id-drift.cjs +29 -0
  426. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  427. package/scripts/lint-response-language-coverage.cjs +3 -0
  428. package/scripts/lint-skill-deps.cjs +7 -3
  429. package/scripts/lint-test-file-count.allowlist.json +11 -1
  430. package/scripts/lint-test-file-count.cjs +34 -1
  431. package/scripts/lint-vendored-deps.cjs +41 -5
  432. package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
  433. package/scripts/mutation-matrix.cjs +53 -6
  434. package/scripts/release-tarball-smoke.cjs +14 -7
  435. package/scripts/require-issue-link-policy.cjs +6 -2
  436. package/scripts/sync-runtime-launcher.cjs +184 -2
  437. package/scripts/verify-npm-publish.cjs +76 -20
  438. package/skills/gsd-add-tests/SKILL.md +6 -1
  439. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  440. package/skills/gsd-audit-fix/SKILL.md +5 -0
  441. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  442. package/skills/gsd-autonomous/SKILL.md +5 -0
  443. package/skills/gsd-capture/SKILL.md +8 -4
  444. package/skills/gsd-code-review/SKILL.md +7 -2
  445. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  446. package/skills/gsd-config/SKILL.md +8 -4
  447. package/skills/gsd-debug/SKILL.md +11 -7
  448. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  449. package/skills/gsd-docs-update/SKILL.md +12 -7
  450. package/skills/gsd-eval-review/SKILL.md +6 -1
  451. package/skills/gsd-execute-phase/SKILL.md +12 -7
  452. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  453. package/skills/gsd-fast/SKILL.md +4 -0
  454. package/skills/gsd-forensics/SKILL.md +5 -1
  455. package/skills/gsd-graphify/SKILL.md +10 -6
  456. package/skills/gsd-health/SKILL.md +5 -0
  457. package/skills/gsd-help/SKILL.md +7 -2
  458. package/skills/gsd-import/SKILL.md +7 -3
  459. package/skills/gsd-inbox/SKILL.md +5 -0
  460. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  461. package/skills/gsd-manager/SKILL.md +6 -1
  462. package/skills/gsd-map-codebase/SKILL.md +7 -3
  463. package/skills/gsd-mempalace-capture/SKILL.md +5 -1
  464. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  465. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  466. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  467. package/skills/gsd-new-milestone/SKILL.md +6 -1
  468. package/skills/gsd-new-project/SKILL.md +5 -0
  469. package/skills/gsd-next/SKILL.md +6 -1
  470. package/skills/gsd-ns-context/SKILL.md +4 -0
  471. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  472. package/skills/gsd-ns-manage/SKILL.md +4 -0
  473. package/skills/gsd-ns-project/SKILL.md +4 -0
  474. package/skills/gsd-ns-review/SKILL.md +4 -0
  475. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  476. package/skills/gsd-onboard/SKILL.md +6 -1
  477. package/skills/gsd-pause-work/SKILL.md +5 -1
  478. package/skills/gsd-phase/SKILL.md +8 -4
  479. package/skills/gsd-plan-phase/SKILL.md +6 -1
  480. package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
  481. package/skills/gsd-pr-branch/SKILL.md +4 -0
  482. package/skills/gsd-profile-user/SKILL.md +5 -1
  483. package/skills/gsd-progress/SKILL.md +6 -1
  484. package/skills/gsd-quick/SKILL.md +16 -10
  485. package/skills/gsd-quick-batch/SKILL.md +20 -8
  486. package/skills/gsd-review/SKILL.md +5 -1
  487. package/skills/gsd-review-backlog/SKILL.md +3 -2
  488. package/skills/gsd-secure-phase/SKILL.md +6 -1
  489. package/skills/gsd-ship/SKILL.md +5 -0
  490. package/skills/gsd-sketch/SKILL.md +7 -2
  491. package/skills/gsd-spec-phase/SKILL.md +5 -1
  492. package/skills/gsd-spike/SKILL.md +8 -3
  493. package/skills/gsd-surface/SKILL.md +5 -1
  494. package/skills/gsd-thread/SKILL.md +4 -0
  495. package/skills/gsd-ui-phase/SKILL.md +6 -1
  496. package/skills/gsd-ui-review/SKILL.md +6 -1
  497. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  498. package/skills/gsd-undo/SKILL.md +5 -1
  499. package/skills/gsd-update/SKILL.md +6 -2
  500. package/skills/gsd-validate-phase/SKILL.md +6 -1
  501. package/skills/gsd-verify-work/SKILL.md +6 -1
  502. package/skills/gsd-workspace/SKILL.md +7 -3
  503. package/skills/gsd-workstreams/SKILL.md +6 -6
  504. package/vscode/package.json +1 -1
  505. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Anti-divergence drift guard for the FRONTMATTER FENCE seam (found while
6
+ * implementing #5105; ADR-5057 one-owner rule).
7
+ *
8
+ * `src/frontmatter-fence.cts`'s `locateFrontmatterFence` is the SINGLE owner of
9
+ * "where does a document's frontmatter block start and stop" — the BOM, the
10
+ * byte-0 opening fence, the whole-line closing fence, an adjacent empty block,
11
+ * the lenient `----` closer. Before it existed that answer was re-derived in
12
+ * some thirty places across `src/` (regexes like `/^---\r?\n([\s\S]*?)\r?\n---/`,
13
+ * `indexOf('\n---', 4)`, `lines[0].trim() === '---'`), and the copies disagreed
14
+ * on a BOM, CRLF, an adjacent empty block, `----`, `--- x`, a `---` inside a
15
+ * value and leading whitespace. This guard keeps a new copy from appearing.
16
+ *
17
+ * Mechanism: the shared `scripts/lib/drift-scan.cjs` tree walk and regex-literal
18
+ * tokenizer (ADR-3180 Decision 4), scanning the WHOLE `src/` and `hooks/` trees
19
+ * (the hooks load the built `bin/lib` behind `ensureRuntimeBuild`), the dev-time
20
+ * `scripts/` and `eslint-rules/` trees (every one of them that runs after
21
+ * `build:lib` requires the owner from `gsd-core/bin/lib/frontmatter-fence.cjs`),
22
+ * the two hand-written entry points that load `bin/lib` (`bin/install.js`,
23
+ * `gsd-core/bin/gsd-tools.cjs`), and the two native plugin adapters
24
+ * (`.opencode/plugins/gsd-core.js`, `.kilo/plugins/gsd-core.js`), with
25
+ * FUNCTION-SCOPED exemptions carrying a written reason — never a bare file
26
+ * allowlist — exactly as `lint-milestone-window-drift.cjs` does.
27
+ *
28
+ * KEPT COPIES (found while implementing #5105). A file that must run where the
29
+ * built owner may not exist keeps a self-contained copy of `locateFrontmatterFence`,
30
+ * and only that one function is allowlisted: `scripts/changeset/parse.cjs` (the
31
+ * `changeset-lint` CI job runs it with no `npm ci` and no build) and the two plugin
32
+ * adapters (a package/git-spec tree may carry no built `bin/lib`). Each copy is
33
+ * pinned to the owner by tests/frontmatter-fence.test.cjs ("kept frontmatter fence
34
+ * copies agree with the owner") over a fixture corpus and a property test.
35
+ *
36
+ * DETECTORS. Three files carry a regex that recognizes a fence idiom in OTHER
37
+ * text — this guard itself, `lint-frontmatter-scalar-broad-grep.cjs` (workflow
38
+ * shell snippets) and the `no-crlf-fragile-split` ESLint rule (regex literals in
39
+ * source). None of them locates a frontmatter block; each is exempt only for its
40
+ * exact fragment (`DETECTOR_EXEMPTIONS`), so any other fence code in the same
41
+ * file is still flagged.
42
+ *
43
+ * A line (comment text stripped) is a fence re-derivation when it carries:
44
+ * (a) a regex literal whose text contains `---` (`/^---/`, `/\n---/`,
45
+ * `/(\r?\n)---$/`), or
46
+ * (b) `new RegExp(` together with a string/template literal containing `---`, or
47
+ * (c) a fence-shaped string literal — `'---'`, `'\n---'`, `'---\n'`,
48
+ * `'---\r\n'`, `'\r\n---'` — that is the direct argument of
49
+ * `.startsWith(`/`.endsWith(`/`.indexOf(`/`.lastIndexOf(`/`.split(`, or an
50
+ * operand of `===`/`!==`/`==`/`!=`, or (only when it carries a line
51
+ * ending) the argument of `.includes(`.
52
+ * A template literal that BUILDS a block (`` `---\n${yaml}\n---` ``) or an array
53
+ * of fence lines to join is a writer emitting a fence, not a reader locating
54
+ * one, and is not flagged.
55
+ *
56
+ * KNOWN, ACCEPTED limits of a per-line textual scan (the sibling guards'
57
+ * tradeoff): a derivation split across lines, or routed through a variable
58
+ * holding the `---` literal, is left to code review and to the consumer-parity
59
+ * tests in `tests/frontmatter-fence.test.cjs`.
60
+ */
61
+
62
+ const fs = require('node:fs');
63
+ const path = require('node:path');
64
+ const driftScan = require('./lib/drift-scan.cjs');
65
+ const { readRegexLiteralAt, MAX_REGEX_LITERAL_LEN, sanitizeForReport, scanTree } = driftScan;
66
+
67
+ // Authored TypeScript source (the generated bin/lib/*.cjs mirror it) and the runtime hooks
68
+ // (`hooks/dist`, their build output, is skipped by the shared walker), plus the two hand-written
69
+ // entry points that load bin/lib and so can — and must — read the owner too.
70
+ const SCAN_TREES = [
71
+ { dirs: ['src'], ext: new Set(['.cts', '.ts', '.mts']) },
72
+ { dirs: ['hooks'], ext: new Set(['.js', '.cjs']) },
73
+ { dirs: ['scripts', 'eslint-rules'], ext: new Set(['.js', '.cjs', '.mjs']) },
74
+ ];
75
+ const SCAN_FILES = [
76
+ path.join('bin', 'install.js'),
77
+ path.join('gsd-core', 'bin', 'gsd-tools.cjs'),
78
+ path.join('.opencode', 'plugins', 'gsd-core.js'),
79
+ path.join('.kilo', 'plugins', 'gsd-core.js'),
80
+ ];
81
+
82
+ const OWNER_FILE = path.join('src', 'frontmatter-fence.cts');
83
+
84
+ // Code outside any top-level function (a module-scope constant) is keyed as this.
85
+ const TOP_LEVEL = '<top-level>';
86
+
87
+ // FUNCTION-SCOPED exemptions, each with its reason:
88
+ // - frontmatter-fence.cts `locateFrontmatterFence`: this IS the owner. Its
89
+ // module-scope fence constants (`CLOSING_FENCE_LINE`, `LENIENT_CLOSING_FENCE_LINE`)
90
+ // sit OUTSIDE this function, at TOP_LEVEL, and are exempted by exact fragment
91
+ // in DETECTOR_EXEMPTIONS below instead — a blanket TOP_LEVEL pass here would
92
+ // let any new top-level helper in this file go unscanned (#5105 fix).
93
+ // - phase.cts `phaseEntryInsertOffset`: its `lastIndexOf('\n---')` finds the
94
+ // ROADMAP's trailing section SEPARATOR (a thematic break closing the phase
95
+ // list) so a new phase entry is appended before it — it never asks where a
96
+ // frontmatter block is.
97
+ // - shell-command-projection.cts `_normalizeMd`: `prevTrimmed !== '---'` is
98
+ // the Markdown normalizer's "no blank line after a thematic break" rule,
99
+ // applied line by line to BODY text; that normalizer's frontmatter skip
100
+ // (`leadingFrontmatterLineCount`) reads the owner.
101
+ // - scripts/changeset/parse.cjs `locateFrontmatterFence`: a kept copy of the
102
+ // owner (#5105) — the `changeset-lint` job in changeset-required.yml runs it
103
+ // with no `npm ci` and no `build:lib`. Pinned to the owner by the parity test
104
+ // tests/frontmatter-fence.test.cjs "kept frontmatter fence copies agree with the owner".
105
+ // - .opencode/plugins/gsd-core.js and .kilo/plugins/gsd-core.js
106
+ // `locateFrontmatterFence`: kept copies of the owner (#5105) — the plugin loads
107
+ // from a package/git-spec tree that may carry no built bin/lib. Pinned to the
108
+ // owner by the same parity test.
109
+ const FUNCTION_SCOPED_EXEMPTIONS = new Map([
110
+ [OWNER_FILE, new Set(['locateFrontmatterFence'])],
111
+ [path.join('src', 'phase.cts'), new Set(['phaseEntryInsertOffset'])],
112
+ [path.join('src', 'shell-command-projection.cts'), new Set(['_normalizeMd'])],
113
+ [path.join('scripts', 'changeset', 'parse.cjs'), new Set(['locateFrontmatterFence'])],
114
+ [path.join('.opencode', 'plugins', 'gsd-core.js'), new Set(['locateFrontmatterFence'])],
115
+ [path.join('.kilo', 'plugins', 'gsd-core.js'), new Set(['locateFrontmatterFence'])],
116
+ ]);
117
+
118
+ // FRAGMENT-SCOPED exemptions for DETECTORS (#5105), each pinned to the exact
119
+ // reported fragment (the literal's source text) — a regex that recognizes a fence
120
+ // idiom in other text, never one that locates a frontmatter block:
121
+ // - this guard's own fence-literal matcher (`FENCE_LITERAL_CONTENT_RE`);
122
+ // - lint-frontmatter-scalar-broad-grep.cjs `FRONTMATTER_SCOPE_RE`, which finds a
123
+ // `^---`-scoped extraction in a workflow's shell snippet;
124
+ // - the no-crlf-fragile-split ESLint rule's `^---` probe, which classifies a
125
+ // regex literal in linted source as frontmatter-shaped.
126
+ const DETECTOR_EXEMPTIONS = new Map([
127
+ [path.join('scripts', 'lint-frontmatter-fence-drift.cjs'), new Set(['/^(?:\\\\r)?(?:\\\\n)?---(?:\\\\r)?(?:\\\\n)?$/'])],
128
+ [path.join('scripts', 'lint-frontmatter-scalar-broad-grep.cjs'), new Set(['/\\^---[\\s\\S]{0,300}?---/'])],
129
+ [path.join('eslint-rules', 'no-crlf-fragile-split.cjs'), new Set(['/\\^---/'])],
130
+ // The owner's own module-scope fence-literal constants (#5105 fix): these ARE
131
+ // the canonical closing-fence patterns, not a re-derivation — exempted by their
132
+ // exact fragment rather than by a blanket TOP_LEVEL pass, so a NEW top-level
133
+ // helper in this file (one that merely happens to sit outside a function) is
134
+ // still caught.
135
+ [OWNER_FILE, new Set(['/^---[ \\t]*$/', '/^-{4,}[ \\t]*$/'])],
136
+ ]);
137
+
138
+ // Only a column-0 top-level `function` declaration updates the current-function
139
+ // tracker (mirrors lint-milestone-window-drift.cjs); ANY OTHER non-blank,
140
+ // non-`}` column-0 line — a `const`, a `module.exports =`, an `export const`
141
+ // arrow, anything — resets it to TOP_LEVEL. (#5105 fix: the previous version
142
+ // only reset on a fixed keyword list, so a top-level statement it didn't
143
+ // recognize — e.g. `module.exports = { ... }` — silently inherited the LAST
144
+ // exempted function's name and went unscanned.)
145
+ const TOP_LEVEL_FUNCTION_RE = /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z0-9_$]+)\s*\(/;
146
+ const COLUMN_ZERO_RESET_RE = /^[^\s}]/;
147
+
148
+ const NEW_REGEXP_RE = /new\s+RegExp\s*\(/;
149
+
150
+ // A fence-shaped string literal's CONTENT (source text between the quotes):
151
+ // `---`, optionally preceded by `\n`/`\r\n` and optionally followed by `\n`/`\r\n`.
152
+ const FENCE_LITERAL_CONTENT_RE = /^(?:\\r)?(?:\\n)?---(?:\\r)?(?:\\n)?$/;
153
+
154
+ const SEARCH_CALL_BEFORE_RE = /\.(startsWith|endsWith|indexOf|lastIndexOf|split|includes)\(\s*$/;
155
+ const COMPARE_BEFORE_RE = /[!=]==?\s*$/;
156
+ const COMPARE_AFTER_RE = /^\s*[!=]==?/;
157
+
158
+ /**
159
+ * Read the quoted or backtick-delimited string literal starting at `line[start]`.
160
+ * Returns `{ text, end }` (delimiters included) or null. Same single-pass,
161
+ * escape-aware, bounded style as the shared `readRegexLiteralAt`.
162
+ */
163
+ function readStringLiteralAt(line, start) {
164
+ const quote = line[start];
165
+ if (quote !== "'" && quote !== '"' && quote !== '`') return null;
166
+ const limit = Math.min(line.length, start + MAX_REGEX_LITERAL_LEN);
167
+ for (let i = start + 1; i < limit; i++) {
168
+ const ch = line[i];
169
+ if (ch === '\\') {
170
+ i++;
171
+ continue;
172
+ }
173
+ if (ch === '\r' || ch === '\n') return null;
174
+ if (ch === quote) return { text: line.slice(start, i + 1), end: i + 1 };
175
+ }
176
+ return null;
177
+ }
178
+
179
+ /**
180
+ * Strip comment text from a line (whole-line `*`, `/*`, `//`, and a trailing
181
+ * `//`), as the sibling guards do — prose about a fence is not a fence.
182
+ */
183
+ function stripComments(line) {
184
+ const trimmed = line.trim();
185
+ if (trimmed.startsWith('*') || trimmed.startsWith('/*') || trimmed.startsWith('//')) return '';
186
+ const idx = line.indexOf('//');
187
+ return idx === -1 ? line : line.slice(0, idx);
188
+ }
189
+
190
+ /** Every regex and string literal on `code`, in order, with its kind and span. */
191
+ function literalsOf(code) {
192
+ const out = [];
193
+ for (let i = 0; i < code.length; i++) {
194
+ const ch = code[i];
195
+ let literal = null;
196
+ let kind = null;
197
+ if (ch === '/' && code[i + 1] !== '/' && code[i + 1] !== '*') {
198
+ literal = readRegexLiteralAt(code, i);
199
+ kind = 'regex';
200
+ } else if (ch === "'" || ch === '"' || ch === '`') {
201
+ literal = readStringLiteralAt(code, i);
202
+ kind = 'string';
203
+ }
204
+ if (!literal) continue;
205
+ out.push({ kind, text: literal.text, start: i, end: literal.end });
206
+ i = literal.end - 1;
207
+ }
208
+ return out;
209
+ }
210
+
211
+ /** The fence re-derivation fragment on a comment-stripped line, or null. */
212
+ function fenceFragment(code) {
213
+ const literals = literalsOf(code);
214
+ const hasNewRegExp = NEW_REGEXP_RE.test(code);
215
+ for (const lit of literals) {
216
+ if (lit.kind === 'regex') {
217
+ if (lit.text.includes('---')) return lit.text;
218
+ continue;
219
+ }
220
+ const content = lit.text.slice(1, -1);
221
+ if (hasNewRegExp && content.includes('---')) return lit.text;
222
+ if (!FENCE_LITERAL_CONTENT_RE.test(content)) continue;
223
+ const before = code.slice(0, lit.start);
224
+ const after = code.slice(lit.end);
225
+ const call = SEARCH_CALL_BEFORE_RE.exec(before);
226
+ if (call && (call[1] !== 'includes' || content.includes('\\n'))) return lit.text;
227
+ if (COMPARE_BEFORE_RE.test(before) || COMPARE_AFTER_RE.test(after)) return lit.text;
228
+ }
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * Pure: every unsanctioned frontmatter-fence re-derivation in `text`.
234
+ * `relPath` is the repo-relative path, for reporting and the exemptions.
235
+ * Returns [{ line, fn, found }].
236
+ */
237
+ function findFrontmatterFenceDrift(text, relPath) {
238
+ const out = [];
239
+ const lines = text.split('\n');
240
+ const exemptFunctions = FUNCTION_SCOPED_EXEMPTIONS.get(relPath) || null;
241
+ const exemptFragments = DETECTOR_EXEMPTIONS.get(relPath) || null;
242
+ let currentFunction = TOP_LEVEL;
243
+ for (let i = 0; i < lines.length; i++) {
244
+ const line = lines[i];
245
+ const fnMatch = TOP_LEVEL_FUNCTION_RE.exec(line);
246
+ if (fnMatch) currentFunction = fnMatch[1];
247
+ else if (COLUMN_ZERO_RESET_RE.test(line)) currentFunction = TOP_LEVEL;
248
+
249
+ const code = stripComments(line);
250
+ if (!code.includes('---')) continue;
251
+ const found = fenceFragment(code);
252
+ if (found === null) continue;
253
+ if (exemptFunctions && exemptFunctions.has(currentFunction)) continue;
254
+ if (exemptFragments && exemptFragments.has(found)) continue;
255
+ out.push({ line: i + 1, fn: currentFunction, found });
256
+ }
257
+ return out;
258
+ }
259
+
260
+ /** Scan the authored source tree and entry points; every violation carries its repo-relative file. */
261
+ function scanRepo(root) {
262
+ const violations = [];
263
+ for (const { dirs, ext } of SCAN_TREES) {
264
+ violations.push(...scanTree({
265
+ root,
266
+ scanDirs: dirs,
267
+ scanExt: ext,
268
+ onFile(rel, text) {
269
+ return findFrontmatterFenceDrift(text, rel).map((d) => ({ file: rel, ...d }));
270
+ },
271
+ }));
272
+ }
273
+ for (const rel of SCAN_FILES) {
274
+ let text;
275
+ try {
276
+ text = fs.readFileSync(path.join(root, rel), 'utf8');
277
+ } catch {
278
+ continue; // absent in a fixture tree
279
+ }
280
+ violations.push(...findFrontmatterFenceDrift(text, rel).map((d) => ({ file: rel, ...d })));
281
+ }
282
+ return violations;
283
+ }
284
+
285
+ function main() {
286
+ const root = path.join(__dirname, '..');
287
+ const violations = scanRepo(root);
288
+ if (violations.length === 0) {
289
+ process.stdout.write('ok frontmatter-fence-drift: no frontmatter fence re-derivations outside frontmatter-fence.cts\n');
290
+ return;
291
+ }
292
+ process.stderr.write('frontmatter-fence-drift: independent re-derivation(s) of the frontmatter fence found.\n');
293
+ process.stderr.write('Use `locateFrontmatterFence` (src/frontmatter-fence.cts) for offsets, or `frontmatterBlock` /\n');
294
+ process.stderr.write('`frontmatterRegion` / `extractFrontmatter` / `stripFrontmatter` (src/frontmatter.cts) for content:\n');
295
+ for (const d of violations) {
296
+ process.stderr.write(` ${sanitizeForReport(d.file)}:${d.line} (${sanitizeForReport(d.fn)}) ${sanitizeForReport(d.found)}\n`);
297
+ }
298
+ process.exitCode = 1;
299
+ }
300
+
301
+ if (require.main === module) main();
302
+
303
+ module.exports = {
304
+ findFrontmatterFenceDrift,
305
+ scanRepo,
306
+ fenceFragment,
307
+ stripComments,
308
+ readStringLiteralAt,
309
+ OWNER_FILE,
310
+ TOP_LEVEL,
311
+ FUNCTION_SCOPED_EXEMPTIONS,
312
+ DETECTOR_EXEMPTIONS,
313
+ };
@@ -17,10 +17,22 @@
17
17
  * `passed+gaps_found+human_needed` instead of `passed` and blocking a
18
18
  * passed phase).
19
19
  *
20
- * The fix-forward is to scope the grep to the leading frontmatter block and
21
- * take only the first match:
20
+ * The fix-forward for a scalar with no owning query is to scope the grep to
21
+ * the leading frontmatter block and take only the first match:
22
22
  * sed -n '/^---$/,/^---$/p' "$f" | grep -m1 "^<key>:" | cut -d: -f2 | tr -d ' '
23
23
  *
24
+ * ## Rule `verification-status-raw-read` (#5118, ADR-5057 Phase 4)
25
+ *
26
+ * A VERIFICATION report's `status` HAS an owning query — its closed enum,
27
+ * staleness check and routing live in src/verification.cts. Reading that
28
+ * scalar with awk / sed / grep `^status:` (scoped to the frontmatter or not)
29
+ * bypasses all three (transition.md's awk read was W15 in the #5118 census),
30
+ * so it is red even when frontmatter-scoped. The fix-forward is the owner:
31
+ * gsd_run query verification.status "$PHASE_DIR" --pick status
32
+ * `findRawVerificationStatusReadsInBlock` flags a logical line (backslash
33
+ * continuations joined) that names a `*VERIFICATION*` path or variable and
34
+ * reads `^status` with awk, sed or grep.
35
+ *
24
36
  * ## What this scans
25
37
  *
26
38
  * Every fenced ```bash / ```sh code block in `gsd-core/workflows/*.md`,
@@ -68,6 +80,17 @@ const FRONTMATTER_SCOPE_RE = /\^---[\s\S]{0,300}?---/;
68
80
 
69
81
  const ALLOW_RE = /#\s*lint-allow:\s*frontmatter-scalar-broad-grep/;
70
82
 
83
+ const RULE_BROAD_GREP = 'frontmatter-scalar-broad-grep';
84
+ const RULE_VERIFICATION_RAW_READ = 'verification-status-raw-read';
85
+
86
+ // #5118: a VERIFICATION report path or variable (`*-VERIFICATION.md`,
87
+ // `$VERIFICATION_FILE`, `${VERIFICATION_PATH}`), a raw `^status` pattern, and
88
+ // the text tool doing the read. Case-INsensitive (#5118 review): a lower-case
89
+ // `$verification_file` / `*-verification.md` read is the same bypass.
90
+ const VERIFICATION_PATH_RE = /verification/i;
91
+ const RAW_STATUS_PATTERN_RE = /\^status\b/;
92
+ const RAW_READ_TOOL_RE = /(^|[\s|;&(`$])(awk|gawk|sed|grep|egrep)\s/;
93
+
71
94
  // `| head -1` / `| head -n 1` immediately after the grep is functionally
72
95
  // equivalent to `-m1` for this check: frontmatter always precedes the body
73
96
  // in these generated reports, so the first grep match is always the
@@ -163,12 +186,63 @@ function findBroadGrepsInBlock(lines) {
163
186
  }
164
187
 
165
188
  if (pipedToTokenTool || comparedLater) {
166
- findings.push({ lineIndex: i, key, snippet: line.trim() });
189
+ findings.push({ lineIndex: i, key, snippet: line.trim(), rule: RULE_BROAD_GREP });
167
190
  }
168
191
  }
169
192
  return findings;
170
193
  }
171
194
 
195
+ /**
196
+ * Join backslash-continued physical lines into logical lines, remembering the
197
+ * index of each logical line's FIRST physical line.
198
+ * @param {string[]} lines
199
+ * @returns {{ lineIndex: number, text: string }[]}
200
+ */
201
+ function logicalLines(lines) {
202
+ const out = [];
203
+ let current = null;
204
+ for (let i = 0; i < lines.length; i += 1) {
205
+ const line = lines[i];
206
+ const continued = /\\\s*$/.test(line);
207
+ const piece = continued ? line.replace(/\\\s*$/, ' ') : line;
208
+ if (current === null) current = { lineIndex: i, text: piece };
209
+ else current.text += piece;
210
+ if (!continued) {
211
+ out.push(current);
212
+ current = null;
213
+ }
214
+ }
215
+ if (current !== null) out.push(current);
216
+ return out;
217
+ }
218
+
219
+ /**
220
+ * Pure (#5118): find every raw read of a VERIFICATION report's `status` in a
221
+ * single fenced bash/sh block — awk / sed / grep over a `*VERIFICATION*`
222
+ * path with a `^status` pattern, whether or not it is frontmatter-scoped.
223
+ * The owner query (`gsd_run query verification.status …`) carries no
224
+ * `^status` pattern and is never flagged. Returns
225
+ * `{ lineIndex, key, snippet, rule }[]` (lineIndex = the logical line's first
226
+ * physical line).
227
+ * @param {string[]} lines
228
+ * @returns {{ lineIndex: number, key: string, snippet: string, rule: string }[]}
229
+ */
230
+ function findRawVerificationStatusReadsInBlock(lines) {
231
+ const findings = [];
232
+ for (const { lineIndex, text } of logicalLines(lines)) {
233
+ if (!VERIFICATION_PATH_RE.test(text)) continue;
234
+ if (!RAW_STATUS_PATTERN_RE.test(text)) continue;
235
+ if (!RAW_READ_TOOL_RE.test(text)) continue;
236
+ findings.push({
237
+ lineIndex,
238
+ key: 'status',
239
+ snippet: text.replace(/\s+/g, ' ').trim(),
240
+ rule: RULE_VERIFICATION_RAW_READ,
241
+ });
242
+ }
243
+ return findings;
244
+ }
245
+
172
246
  function walkMarkdown(dir) {
173
247
  const out = [];
174
248
  let entries;
@@ -198,12 +272,18 @@ function scan(roots = DEFAULT_ROOTS) {
198
272
  for (const file of walkMarkdown(abs)) {
199
273
  const blocks = extractBashBlocks(fs.readFileSync(file, 'utf8'));
200
274
  for (const block of blocks) {
201
- for (const finding of findBroadGrepsInBlock(block.lines)) {
275
+ const rawReads = findRawVerificationStatusReadsInBlock(block.lines);
276
+ const rawReadLines = new Set(rawReads.map((f) => f.lineIndex));
277
+ // A raw VERIFICATION `status` read is reported once, under its own
278
+ // rule, not a second time as a broad grep.
279
+ const broad = findBroadGrepsInBlock(block.lines).filter((f) => !rawReadLines.has(f.lineIndex));
280
+ for (const finding of [...rawReads, ...broad]) {
202
281
  offenders.push({
203
282
  file: path.relative(ROOT, file),
204
283
  line: block.startLine + finding.lineIndex,
205
284
  key: finding.key,
206
285
  snippet: finding.snippet,
286
+ rule: finding.rule,
207
287
  });
208
288
  }
209
289
  }
@@ -217,21 +297,47 @@ function main() {
217
297
  const roots = rootsEnv ? rootsEnv.split(path.delimiter).filter(Boolean) : DEFAULT_ROOTS;
218
298
  const offenders = scan(roots);
219
299
  if (offenders.length > 0) {
220
- const detail = offenders.map((o) => ` ${o.file}:${o.line} ${o.snippet}`).join('\n');
221
- throw new ExitError(
222
- 1,
223
- 'lint-frontmatter-scalar-broad-grep: `grep "^key:"` over the whole file, compared to an\n'
224
- + 'exact token, with no frontmatter scoping and no -m1 (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP).\n'
225
- + 'A body line beginning `key:` is enough to break this. Scope to the frontmatter block:\n'
226
- + ' sed -n \'/^---$/,/^---$/p\' "$f" | grep -m1 "^<key>:" | cut -d: -f2 | tr -d \' \'\n'
227
- + 'or add `# lint-allow: frontmatter-scalar-broad-grep — <reason>` if this is a genuine\n'
228
- + 'whole-body scan:\n'
229
- + detail,
230
- );
300
+ const detailFor = (rule) => offenders
301
+ .filter((o) => o.rule === rule)
302
+ .map((o) => ` ${o.file}:${o.line} ${o.snippet}`)
303
+ .join('\n');
304
+ const sections = [];
305
+ const rawReads = detailFor(RULE_VERIFICATION_RAW_READ);
306
+ if (rawReads) {
307
+ sections.push(
308
+ `[${RULE_VERIFICATION_RAW_READ}] a VERIFICATION report's \`status\` read with awk/sed/grep\n`
309
+ + '(DEFECT.FRONTMATTER-SCALAR-BROAD-GREP, #5118). It bypasses the closed VerificationStatus\n'
310
+ + 'enum, the staleness check and the routing table. Read it through its owner:\n'
311
+ + ' gsd_run query verification.status "$PHASE_DIR" --pick status\n'
312
+ + rawReads,
313
+ );
314
+ }
315
+ const broad = detailFor(RULE_BROAD_GREP);
316
+ if (broad) {
317
+ sections.push(
318
+ `[${RULE_BROAD_GREP}] \`grep "^key:"\` over the whole file, compared to an exact token,\n`
319
+ + 'with no frontmatter scoping and no -m1 (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP).\n'
320
+ + 'A body line beginning `key:` is enough to break this. Scope a scalar that has no owning\n'
321
+ + 'query to the frontmatter block (a VERIFICATION `status` always goes through its owner):\n'
322
+ + ' sed -n \'/^---$/,/^---$/p\' "$f" | grep -m1 "^<key>:" | cut -d: -f2 | tr -d \' \'\n'
323
+ + 'or add `# lint-allow: frontmatter-scalar-broad-grep — <reason>` if this is a genuine\n'
324
+ + 'whole-body scan:\n'
325
+ + broad,
326
+ );
327
+ }
328
+ throw new ExitError(1, `lint-frontmatter-scalar-broad-grep:\n${sections.join('\n')}`);
231
329
  }
232
330
  console.log(`ok lint-frontmatter-scalar-broad-grep: no un-scoped frontmatter-scalar greps in ${roots.length} root(s)`);
233
331
  }
234
332
 
235
- module.exports = { findBroadGrepsInBlock, extractBashBlocks, scan, DEFAULT_ROOTS };
333
+ module.exports = {
334
+ findBroadGrepsInBlock,
335
+ findRawVerificationStatusReadsInBlock,
336
+ extractBashBlocks,
337
+ scan,
338
+ DEFAULT_ROOTS,
339
+ RULE_BROAD_GREP,
340
+ RULE_VERIFICATION_RAW_READ,
341
+ };
236
342
 
237
343
  if (require.main === module) runMain(main);
@@ -84,13 +84,16 @@
84
84
  * shape as `collectDiskPhases` and the `audit.cts` scanners; it must see
85
85
  * every phase directory regardless of milestone window to catch a
86
86
  * naming/duplicate defect wherever it lives.
87
- * - `src/verify.cts` `resolvePhaseDirByToken`: resolves ONE caller-supplied
88
- * `phase` argument to its directory (falling back to an exact-name
89
- * match) — a single-phase LOOKUP, not a current-milestone enumeration.
90
- * Originally `cmdVerifySchemaDrift`'s own inline block; #3348 lifted it
91
- * into this shared helper (also used by the new `cmdVerifyContextDrift`)
92
- * without changing what question it asks, so the exemption moved with
93
- * the call site rather than multiplying.
87
+ * - `src/gate-phase-context.cts` `resolvePhaseDirByToken`: resolves ONE
88
+ * caller-supplied `phase` argument to its directory (falling back to an
89
+ * exact-name match) — a single-phase LOOKUP, not a current-milestone
90
+ * enumeration. Originally `cmdVerifySchemaDrift`'s own inline block;
91
+ * #3348 lifted it into this shared helper (also used by the context-drift
92
+ * check) without changing what question it asks, so the exemption moved
93
+ * with the call site rather than multiplying. #5219 (ADR-5057 §4) moved
94
+ * both checks into gate modules and the helper beside `resolvePhaseDir`
95
+ * in the gate phase-context module (it is not `resolvePhaseDir`: it
96
+ * lists ONE given phases directory and nothing else).
94
97
  * - `src/init.cts` `detectHasPriorPhases`: answers "has this project EVER
95
98
  * completed a phase", explicitly excluding the current one. A history
96
99
  * probe across all milestones, not a current-milestone enumeration.
@@ -309,7 +312,8 @@ const OWNER_FILES = new Set([
309
312
  // `lint-milestone-window-drift.cjs`'s FUNCTION_SCOPED_EXEMPTIONS mechanism.
310
313
  // See the header comment for the full written reason behind each entry.
311
314
  const FUNCTION_SCOPED_EXEMPTIONS = new Map([
312
- [path.join('src', 'verify.cts'), new Set(['cmdValidateHealth', 'resolvePhaseDirByToken'])],
315
+ [path.join('src', 'verify.cts'), new Set(['cmdValidateHealth'])],
316
+ [path.join('src', 'gate-phase-context.cts'), new Set(['resolvePhaseDirByToken'])],
313
317
  [path.join('src', 'init.cts'), new Set(['detectHasPriorPhases', 'detectUiPhaseActive'])],
314
318
  [path.join('src', 'milestone.cts'), new Set(['archivePhaseDirectories', 'cmdMilestoneComplete', 'cmdPhasesClear'])],
315
319
  // #3849: collectSiblingWorktreePhaseNums reads a SIBLING worktree's phases dir —
@@ -283,6 +283,34 @@ function findPhaseHeadingScanLiteralDrift(text) {
283
283
  return out;
284
284
  }
285
285
 
286
+ /**
287
+ * Scan the authored source tree (`src/**\/*.cts`, the same `SCAN_DIRS` root
288
+ * and `phase-id.cts` owner exemption that `scanRepo` applies to this
289
+ * detector — the heading-scan literal is a `src/**\/*.cts` defect, not a
290
+ * markdown one, unlike its sibling wrappers below) for unsanctioned
291
+ * hand-rolled heading-scan literals. Returns [{ file, line, found }] with
292
+ * repo-relative paths.
293
+ */
294
+ function scanMarkdownPhaseHeadingScanLiteral(root) {
295
+ const violations = [];
296
+ for (const dir of SCAN_DIRS) {
297
+ for (const file of walk(path.join(root, dir), [])) {
298
+ const rel = path.relative(root, file);
299
+ if (EXEMPT.has(rel)) continue;
300
+ let text;
301
+ try {
302
+ text = fs.readFileSync(file, 'utf8');
303
+ } catch {
304
+ continue;
305
+ }
306
+ for (const d of findPhaseHeadingScanLiteralDrift(text)) {
307
+ violations.push({ file: rel, kind: 'phase-heading-scan-literal', ...d });
308
+ }
309
+ }
310
+ }
311
+ return violations;
312
+ }
313
+
286
314
  // #4634: ban base-10-forced shell arithmetic (`$((10#...))`) on a variable
287
315
  // that still carries a possibly-decimal/multi-segment phase id — this
288
316
  // construct is exactly the pattern that breaks on a value like `08.5`. The
@@ -857,6 +885,7 @@ module.exports = {
857
885
  findNameValidityDrift,
858
886
  findBranchSlugFallbackDrift,
859
887
  findPhaseHeadingScanLiteralDrift,
888
+ scanMarkdownPhaseHeadingScanLiteral,
860
889
  findShellPhaseArithDrift,
861
890
  findSingleSegmentPhaseRegexDrift,
862
891
  findLetterlessPhaseMirrorDrift,