@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-allowed-tools-parity.cjs — catch `allowed-tools` frontmatter that
6
+ * declares `Bash` but omits `Grep` (#4394, follow-up to #3085).
7
+ *
8
+ * ## Why
9
+ *
10
+ * `gen-plugin-skills.cjs --check` already guarantees `skills/*` /SKILL.md`
11
+ * matches byte-for-byte what `commands/gsd/*.md` generates, so the two trees
12
+ * cannot silently diverge FROM EACH OTHER. Nothing guarded the shape #3085
13
+ * actually found: a command shipping `Bash` without `Grep` purely by
14
+ * omission, identical in both trees and therefore invisible to a parity
15
+ * check that only compares them to each other.
16
+ *
17
+ * That drift ran long enough for 29 of 71 skills to lack a tool most of
18
+ * their siblings already declared, and it surfaced through a manual audit
19
+ * rather than any gate. A command that can shell out but cannot Grep does
20
+ * not fail loudly — it quietly reaches for `Bash` + `grep` instead, which is
21
+ * slower, less structured, and (per `lint-portable-grep.cjs`) a portability
22
+ * hazard of its own on hosts without GNU grep.
23
+ *
24
+ * ## The rule
25
+ *
26
+ * A command whose `allowed-tools` includes `Bash` must also include `Grep`,
27
+ * unless it is on the exemption list below.
28
+ *
29
+ * Detection only. This lint never edits a command's `allowed-tools`.
30
+ *
31
+ * ## Why an exemption list rather than a heuristic
32
+ *
33
+ * The alternative — inferring from a command's body whether it "really"
34
+ * needs Grep — would make the rule's verdict depend on prose that changes
35
+ * constantly, and produce a lint whose failures nobody can predict. A short
36
+ * literal list keeps every exemption a reviewable one-line diff, and the
37
+ * staleness check below stops it becoming a dumping ground: an entry that no
38
+ * longer needs to be there fails just as loudly as a missing tool.
39
+ */
40
+
41
+ const fs = require('fs');
42
+ const path = require('path');
43
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
44
+
45
+ const ROOT = path.resolve(__dirname, '..');
46
+ const DEFAULT_ROOT = 'commands/gsd';
47
+
48
+ /**
49
+ * Commands allowed to declare `Bash` without `Grep`.
50
+ *
51
+ * Keyed by command stem (the filename without `.md`), with the reason inline
52
+ * so changing the set is a one-line, reviewable diff.
53
+ *
54
+ * #4394 named eight candidates from the #3085 review: the six `gsd-ns-*`
55
+ * namespace dispatchers, `gsd-help`, and `gsd-surface`. Seven of those turn
56
+ * out not to need an entry at all — they do not declare `Bash` in the first
57
+ * place, so the rule never reaches them:
58
+ *
59
+ * ns-context/ns-ideate/ns-manage/ns-project/ns-review/ns-workflow Read + Skill
60
+ * help Read
61
+ *
62
+ * Listing them anyway would be seven pre-forgiven commands: the day one of
63
+ * them gained `Bash`, the omission it was granted an exemption for would
64
+ * pass silently. The staleness check in `scan()` below is what surfaced
65
+ * this, and it is why the list stays minimal — an exemption is a real
66
+ * suppression, not documentation.
67
+ */
68
+ const EXEMPT = new Map([
69
+ // Toggles which skills are surfaced. Mutates the install tree through the
70
+ // installer's own seams rather than by searching project files, so `Bash`
71
+ // here is not standing in for a search it cannot perform.
72
+ ['surface', 'mutates the install surface via installer seams, not by searching the project'],
73
+ ]);
74
+
75
+ /**
76
+ * Parse an `allowed-tools` frontmatter value.
77
+ *
78
+ * Handles both shapes the corpus uses: a YAML block sequence
79
+ *
80
+ * allowed-tools:
81
+ * - Read
82
+ * - Bash
83
+ *
84
+ * and an inline scalar or flow sequence (`allowed-tools: Read, Bash` /
85
+ * `allowed-tools: [Read, Bash]`). Returns `null` when the key is absent,
86
+ * which is NOT a violation — a command with no `allowed-tools` at all
87
+ * declares no `Bash` either, so this rule has nothing to say about it.
88
+ *
89
+ * Deliberately not a YAML parser: the frontmatter here is a fixed, shallow
90
+ * shape, and pulling in a parser to read one list would be a dependency
91
+ * bought for a single key.
92
+ *
93
+ * @param {string} text full file contents
94
+ * @returns {string[] | null} declared tool names, or null when the key is absent
95
+ */
96
+ function parseAllowedTools(text) {
97
+ const lines = String(text).split(/\r?\n/);
98
+ const keyIndex = lines.findIndex((l) => /^allowed-tools:/.test(l));
99
+ if (keyIndex === -1) return null;
100
+
101
+ const inline = lines[keyIndex].replace(/^allowed-tools:/, '').trim();
102
+ if (inline) {
103
+ return inline
104
+ .replace(/^\[/, '')
105
+ .replace(/\]$/, '')
106
+ .split(',')
107
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
108
+ .filter(Boolean);
109
+ }
110
+
111
+ const tools = [];
112
+ for (let i = keyIndex + 1; i < lines.length; i += 1) {
113
+ const line = lines[i];
114
+ const item = line.match(/^\s*-\s+(.+?)\s*$/);
115
+ if (item) {
116
+ tools.push(item[1].replace(/^['"]|['"]$/g, ''));
117
+ continue;
118
+ }
119
+ // The first line that is neither a list item nor blank ends the block —
120
+ // the next frontmatter key, or the closing `---`.
121
+ if (line.trim() === '') continue;
122
+ break;
123
+ }
124
+ return tools;
125
+ }
126
+
127
+ /**
128
+ * List command stems under `dir`, sorted.
129
+ *
130
+ * @param {string} dir absolute path to the commands directory
131
+ * @returns {{ stem: string, file: string }[]}
132
+ */
133
+ function listCommands(dir) {
134
+ let entries;
135
+ try {
136
+ entries = fs.readdirSync(dir, { withFileTypes: true });
137
+ } catch {
138
+ return [];
139
+ }
140
+ return entries
141
+ .filter((e) => e.isFile() && e.name.endsWith('.md'))
142
+ .map((e) => ({ stem: e.name.replace(/\.md$/, ''), file: path.join(dir, e.name) }))
143
+ .sort((a, b) => a.stem.localeCompare(b.stem));
144
+ }
145
+
146
+ /**
147
+ * Evaluate the corpus.
148
+ *
149
+ * Reports two independent failure classes, because an exemption list that
150
+ * can only ever grow rots into a list of things nobody re-examined:
151
+ *
152
+ * - `violations`: declares Bash, omits Grep, not exempt.
153
+ * - `staleExemptions`: on the list but no longer needs to be — either the
154
+ * command is gone, or it no longer declares Bash without Grep. Removing
155
+ * the entry is then a one-line diff, and the next omission in that
156
+ * command is caught rather than silently pre-forgiven.
157
+ *
158
+ * @param {string} [root] repo-relative commands directory
159
+ * @returns {{ scanned: number, violations: {stem: string, tools: string[]}[], staleExemptions: {stem: string, reason: string}[] }}
160
+ */
161
+ function scan(root = DEFAULT_ROOT) {
162
+ const abs = path.isAbsolute(root) ? root : path.join(ROOT, root);
163
+ const commands = listCommands(abs);
164
+ const violations = [];
165
+ const seen = new Set();
166
+
167
+ for (const { stem, file } of commands) {
168
+ const tools = parseAllowedTools(fs.readFileSync(file, 'utf8'));
169
+ if (!tools) continue;
170
+ if (!tools.includes('Bash') || tools.includes('Grep')) continue;
171
+ seen.add(stem);
172
+ if (EXEMPT.has(stem)) continue;
173
+ violations.push({ stem, tools });
174
+ }
175
+
176
+ const present = new Set(commands.map((c) => c.stem));
177
+ const staleExemptions = [];
178
+ for (const [stem, reason] of EXEMPT) {
179
+ if (!present.has(stem)) {
180
+ staleExemptions.push({ stem, reason: `no such command (${reason})` });
181
+ } else if (!seen.has(stem)) {
182
+ staleExemptions.push({ stem, reason: `no longer declares Bash without Grep (${reason})` });
183
+ }
184
+ }
185
+
186
+ return { scanned: commands.length, violations, staleExemptions };
187
+ }
188
+
189
+ function main() {
190
+ const root = process.env.GSD_LINT_ALLOWED_TOOLS_ROOT || DEFAULT_ROOT;
191
+ const { scanned, violations, staleExemptions } = scan(root);
192
+
193
+ if (violations.length > 0 || staleExemptions.length > 0) {
194
+ const parts = [];
195
+ if (violations.length > 0) {
196
+ parts.push(
197
+ 'lint-allowed-tools-parity: these commands declare `Bash` but not `Grep` (#4394).\n' +
198
+ 'A command that can shell out but cannot Grep reaches for `Bash` + `grep` instead —\n' +
199
+ 'slower, unstructured, and a portability hazard on hosts without GNU grep. Add `Grep`\n' +
200
+ 'to the frontmatter, or add the command to EXEMPT in this script with a reason:\n' +
201
+ violations.map((v) => ` ${root}/${v.stem}.md [${v.tools.join(', ')}]`).join('\n'),
202
+ );
203
+ }
204
+ if (staleExemptions.length > 0) {
205
+ parts.push(
206
+ 'lint-allowed-tools-parity: these EXEMPT entries are stale — delete them so the next\n' +
207
+ 'omission in those commands is caught rather than silently pre-forgiven:\n' +
208
+ staleExemptions.map((s) => ` ${s.stem}: ${s.reason}`).join('\n'),
209
+ );
210
+ }
211
+ throw new ExitError(1, parts.join('\n\n'));
212
+ }
213
+
214
+ console.log(
215
+ `ok lint-allowed-tools-parity: ${scanned} command(s) checked, ${EXEMPT.size} exemption(s) all still needed`,
216
+ );
217
+ }
218
+
219
+ module.exports = { parseAllowedTools, scan, EXEMPT, DEFAULT_ROOT };
220
+
221
+ if (require.main === module) runMain(main);
@@ -46,6 +46,7 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
46
46
  'codebuddy-upgrades.test.cjs',
47
47
  'commands.test.cjs',
48
48
  'commit-docs-bypass.test.cjs',
49
+ 'compact-content-partition-guard.test.cjs',
49
50
  'complexity-trigger.test.cjs',
50
51
  'concurrency-safety.test.cjs',
51
52
  'cursor-imperative-reference.test.cjs',
@@ -67,6 +68,7 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
67
68
  'lint-allow-test-rule-refs.test.cjs',
68
69
  'lint-docs-command-form.test.cjs',
69
70
  'lint-docs-required.test.cjs',
71
+ 'lint-retired-runtime-name.test.cjs',
70
72
  'manifest-version-sync.test.cjs',
71
73
  'milestone-archive.test.cjs',
72
74
  'model-resolver.test.cjs',
@@ -80,6 +82,7 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
80
82
  'repo-invariants.test.cjs',
81
83
  'require-issue-link-policy.test.cjs',
82
84
  'reviewer-manifest-body.test.cjs',
85
+ 'reviewer-step-dispatch.test.cjs',
83
86
  'run-tests-harness.test.cjs',
84
87
  'runtime-name-policy.test.cjs',
85
88
  'security-prompt-injection.security.test.cjs',
@@ -123,8 +126,12 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
123
126
  'docs/how-to/some-unrelated-guide.md', 'docs/how-to/x.md', 'docs/some-unrelated-file.md',
124
127
  'docs/totally-unrelated.md',
125
128
  ],
129
+ // #4641: cites docs/adr/4641-windows-selector-consolidation.md in an
130
+ // explanatory comment describing why the retired `windows` scope must not
131
+ // be restored; the file never reads that (or any) docs/ file.
126
132
  'ci-test-scope.test.cjs': [
127
- 'docs/a.md', 'docs/adr', 'docs/adr/22-plan-drift-guard.md', 'docs/how-to/configure-model-profiles.md',
133
+ 'docs/a.md', 'docs/adr', 'docs/adr/22-plan-drift-guard.md',
134
+ 'docs/adr/4641-windows-selector-consolidation.md', 'docs/how-to/configure-model-profiles.md',
128
135
  'docs/installer-migrations.md', 'docs/ja-JP', 'docs/ja-JP/USAGE.md', 'docs/usage.md', 'docs/x.md',
129
136
  ],
130
137
  'cline-install.test.cjs': ['docs/guide.md'],
@@ -135,6 +142,10 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
135
142
  'commit-docs-bypass.test.cjs': [
136
143
  'docs/40-design.md', 'docs/CONFIGURATION.md', 'docs/readme.md', 'docs/tracked-var-mentioning',
137
144
  ],
145
+ // #4403: cites docs/PARTITION-RULES.md as a "see" pointer in the module docstring
146
+ // describing the operational spec this guard implements; the file never reads that
147
+ // (or any) docs/ file.
148
+ 'compact-content-partition-guard.test.cjs': ['docs/PARTITION-RULES.md'],
138
149
  'complexity-trigger.test.cjs': ['docs/readme.md'],
139
150
  // #3884: cites docs/CLI-TOOLS.md:736 in an explanatory comment describing
140
151
  // the real `frontmatter get <file> [--field key]` CLI shape; the file
@@ -168,13 +179,39 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
168
179
  'docs/COMMANDS.md', 'docs/USER-GUIDE.md', 'docs/adr', 'docs/adr/0001-foo.md', 'docs/adr/0099-new.md',
169
180
  'docs/agents', 'docs/agents/triage-labels.md',
170
181
  ],
182
+ // #4729: `writeFile('docs/...', ...)` calls WRITE fabricated fixture
183
+ // content into a throwaway mkdtemp repo; the file never reads real shipped
184
+ // docs/ content — every path here is fixture data for the guard under
185
+ // test.
186
+ // Re-confirmed: every path below is WRITTEN as fabricated fixture content
187
+ // into a throwaway mkdtemp repo; none reads real shipped docs. The a*/b*/c*/
188
+ // d* entries are the adversarial-regression fixtures (dialect-plus-runtime-
189
+ // word, adjacency laundering, version laundering, bare-marker abuse).
190
+ 'lint-retired-runtime-name.test.cjs': [
191
+ 'docs/adr', 'docs/adr/999-retired-runtime-record.md', 'docs/filler/note-',
192
+ 'docs/guides/a1.md', 'docs/guides/a2.md', 'docs/guides/b1.md',
193
+ 'docs/guides/b2.md', 'docs/guides/b3.md', 'docs/guides/c3.md',
194
+ 'docs/guides/c4.md', 'docs/guides/config-homes.md', 'docs/guides/d1.md',
195
+ 'docs/guides/hooks.md', 'docs/guides/one.md', 'docs/guides/runtime-claim.md',
196
+ 'docs/guides/setup.md', 'docs/history/migration-note.md',
197
+ 'docs/ja-JP/guides/runtimes.md', 'docs/pt-BR/guides/a3.md',
198
+ 'docs/pt-BR/guides/hooks.md', 'docs/pt-BR/guides/policy.md',
199
+ 'docs/reference/client-models.md', 'docs/reference/models.md',
200
+ 'docs/zh-CN/guides/a4.md', 'docs/zh-CN/reference/models.md',
201
+ ],
171
202
  'manifest-version-sync.test.cjs': [],
172
203
  // #3884: re-confirmed — the added docs/CLI-TOOLS.md:458 reference is the
173
204
  // same class as the existing docs/TESTING-SUITES.md one (a placement-note
174
205
  // / explanatory comment citing documented CLI behavior for context, never
175
206
  // a read target); the exemption's premise still holds for both.
176
207
  'milestone-archive.test.cjs': ['docs/CLI-TOOLS.md', 'docs/TESTING-SUITES.md'],
177
- 'model-resolver.test.cjs': ['docs/TESTING-SUITES.md'],
208
+ // #4505: additionally cites docs/features/dynamic-routing-with-failure-tier-escalation.md
209
+ // in explanatory comments, quoting the documented first-spawn contract the new rows
210
+ // assert against; the file never reads that (or any) docs/ file — every read it makes
211
+ // targets a tmpdir .planning fixture.
212
+ 'model-resolver.test.cjs': [
213
+ 'docs/TESTING-SUITES.md', 'docs/features/dynamic-routing-with-failure-tier-escalation.md',
214
+ ],
178
215
  'new-project-mvp-prompt.test.cjs': ['docs/CONFIGURATION.md'],
179
216
  'onboard-command.test.cjs': ['docs/adr/0001-runtime.md'],
180
217
  'opencode-command-dir-plural.test.cjs': ['docs/commands'],
@@ -187,11 +224,18 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
187
224
  'repo-invariants.test.cjs': ['docs/FEATURES.md', 'docs/workflows/README'],
188
225
  'require-issue-link-policy.test.cjs': ['docs/-prefixed', 'docs/CONFIGURATION.md', 'docs/a.md', 'docs/b.md', 'docs/guide.md'],
189
226
  'reviewer-manifest-body.test.cjs': ['docs/how-to/ship-a-reviewer-lane.md'],
227
+ // #4209: 'docs/spec.md' is a synthetic, never-read fake path proving dispatchReviewerLanes
228
+ // has no code-review-specific special-casing; 'docs/adr/456-test-rigor-architecture.md' is a
229
+ // prose citation in a code comment justifying the fast-check property tests below (#4209
230
+ // review). Neither is a real filesystem read — the file never reads any docs/ file.
231
+ 'reviewer-step-dispatch.test.cjs': ['docs/adr/456-test-rigor-architecture.md', 'docs/spec.md'],
190
232
  'run-tests-harness.test.cjs': ['docs/TESTING-SUITES.md'],
191
233
  'runtime-name-policy.test.cjs': ['docs/customize/skills'],
192
234
  'security-prompt-injection.security.test.cjs': ['docs/notes.md'],
193
235
  'shipped-reference-cites.test.cjs': [],
194
- 'state.test.cjs': ['docs/CONFIGURATION.md', 'docs/reference/state-md.md'],
236
+ // #4186: the record-session usage-contract test cites the documented
237
+ // signature in docs/CLI-TOOLS.md in its explanatory comment.
238
+ 'state.test.cjs': ['docs/CLI-TOOLS.md', 'docs/CONFIGURATION.md', 'docs/reference/state-md.md'],
195
239
  'worktree-safety.test.cjs': ['docs/SUMMARY.md'],
196
240
  };
197
241
 
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-phase-arg-assignment.cjs — a workflow that READS `${PHASE_ARG}` must
6
+ * also SAY where it comes from (#4777).
7
+ *
8
+ * ## Why
9
+ *
10
+ * Seven workflows shipped an `init.phase-op "${PHASE_ARG}"` call with no
11
+ * assignment anywhere in the file and no instruction to derive one. The
12
+ * variable expanded to the empty string on every run, `init.phase-op ""`
13
+ * resolved `phase_found: false` unconditionally, and each workflow fell
14
+ * through to its own "phase not executed" exit with a blank phase number —
15
+ * so `/gsd:secure-phase 4` reported that phase *nothing* was not executed.
16
+ * Two of them had carried the defect since the files were created in March.
17
+ *
18
+ * Nothing caught it because the working pattern lived in a *different* file:
19
+ * `verify-work.md` derives the value correctly, and a copy of an init block
20
+ * that leaves the derivation line behind is indistinguishable, on review,
21
+ * from one that never needed it.
22
+ *
23
+ * ## The rule
24
+ *
25
+ * A workflow whose bash blocks read `$PHASE_ARG` must carry one of:
26
+ *
27
+ * 1. a **shell assignment** matching one of the frozen `CANONICAL_FORMS`
28
+ * below, textually before the first read; or
29
+ * 2. a **prose derivation step** — a line outside the bash blocks that
30
+ * tells the model to parse `$ARGUMENTS` into `$PHASE_ARG`
31
+ * (`execute-phase.md`: "First positional token → `PHASE_ARG`"); or
32
+ * 3. an entry in `EXEMPT`, with a reason.
33
+ *
34
+ * ## Why the forms are frozen
35
+ *
36
+ * Requiring *an* assignment would be satisfied by a seventh hand-rolled sed
37
+ * pipeline, which is how the two grammars in this tree already drifted apart.
38
+ * The rule therefore pins the exact text. There are three legitimate ways a
39
+ * workflow learns its phase — `$ARGUMENTS` positional, `--phase N`, and a
40
+ * positional `$1` passed by a skill dispatch — and each has exactly one
41
+ * spelling. A new grammar is a deliberate one-line addition here, reviewed
42
+ * once, rather than a copy that diverges silently.
43
+ *
44
+ * Detection only: this lint never edits a workflow.
45
+ */
46
+
47
+ const fs = require('fs');
48
+ const path = require('path');
49
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
50
+
51
+ const ROOT = path.resolve(__dirname, '..');
52
+ const DEFAULT_ROOT = path.join(ROOT, 'gsd-core', 'workflows');
53
+ const SECTIONIZER_PATH = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'markdown-sectionizer.cjs');
54
+
55
+ /**
56
+ * Fenced-block extraction goes through the Markdown Sectionizer seam
57
+ * (ADR-1372) rather than a local fence regex — `local/no-adhoc-markdown-parsing`
58
+ * enforces that, and it is right to: a `~~~` block, an indented fence, or a
59
+ * nested fence inside another would each be misread by a hand-rolled pattern,
60
+ * and this rule's whole job is to know which lines are shell.
61
+ */
62
+ function loadSectionizer() {
63
+ try {
64
+ return require(SECTIONIZER_PATH);
65
+ } catch (e) {
66
+ throw new ExitError(
67
+ 1,
68
+ 'lint-phase-arg-assignment: cannot load the markdown-sectionizer seam at ' +
69
+ `${path.relative(ROOT, SECTIONIZER_PATH)} — run 'npm run build:lib' first (${e.message})`,
70
+ );
71
+ }
72
+ }
73
+
74
+ const SHELL_INFO_STRINGS = new Set(['bash', 'sh', 'shell']);
75
+
76
+ /**
77
+ * The only accepted spellings of a PHASE_ARG assignment, keyed by grammar.
78
+ * Compared after collapsing runs of whitespace, so indentation inside a
79
+ * block is free but the pipeline itself is not.
80
+ *
81
+ * Known limitation (#4777 review): neither `positional` nor `phaseFlag`
82
+ * strips a surrounding quote — `positional('4 --ws "my team"')` leaves
83
+ * `--ws`/`my`/`team` tokens in PHASE_ARG instead of stripping the flag, and
84
+ * `phaseFlag('--phase "4"')` yields an empty PHASE_ARG, reproducing this
85
+ * issue's own defect for that input shape. Every `argument-hint` in the
86
+ * repo documents unquoted usage as primary, and `positional` is copied
87
+ * verbatim from verify-work.md's pre-existing shipped form rather than
88
+ * "improved" here — hardening the regex would mean changing production
89
+ * bash text in all 7 shipped workflows this exact-match check compares
90
+ * against. See tests/lint-phase-arg-assignment.test.cjs's dedicated
91
+ * describe block for a live repro.
92
+ */
93
+ const CANONICAL_FORMS = Object.freeze({
94
+ /** `/gsd:secure-phase 4` — the phase is the whole argument string, minus a `--ws <name>` pair. */
95
+ positional:
96
+ 'PHASE_ARG=$(echo "$ARGUMENTS" | sed -E \'s/--ws[[:space:]]+[A-Za-z0-9._-]+//g\' | xargs)',
97
+ /** `/gsd:review --phase 4 --codex` — the phase is the value of a named flag. */
98
+ phaseFlag:
99
+ 'PHASE_ARG=$(echo "$ARGUMENTS" | sed -nE \'s/.*--phase[[:space:]]+([A-Za-z0-9._-]+).*/\\1/p\')',
100
+ /** Skill dispatch passes the phase as `$1` (`Skill(skill="gsd-code-review", args="4")`). */
101
+ firstPositional: 'PHASE_ARG="${1}"',
102
+ });
103
+
104
+ /**
105
+ * Files that read `$PHASE_ARG` and legitimately do not assign it. Each entry
106
+ * is checked for staleness: one whose file stopped reading the variable, or
107
+ * started assigning it, fails just as loudly as a missing assignment — an
108
+ * exemption nobody can remove is how a list like this becomes a place to
109
+ * hide the next defect.
110
+ */
111
+ const EXEMPT = new Map([
112
+ [
113
+ 'code-review/steps/dispatch-fix.md',
114
+ 'step fragment of code-review.md, which assigns PHASE_ARG="${1}" before dispatching it',
115
+ ],
116
+ ]);
117
+
118
+ const READ_RE = /\$\{?PHASE_ARG\b/;
119
+ const ASSIGN_RE = /^[ \t]*PHASE_ARG=(?!=).*$/gm;
120
+ /**
121
+ * A prose derivation step: a non-bash line that routes `$ARGUMENTS` into
122
+ * `PHASE_ARG`. Matches both shapes in the tree — an arrow ("First positional
123
+ * token → `PHASE_ARG`") and a "store as `$PHASE_ARG`" instruction.
124
+ */
125
+ const PROSE_ASSIGN_RE = /(?:→|->|store as)\s*`?\$?\{?PHASE_ARG/;
126
+
127
+ /** Collapse whitespace runs so indentation does not decide the verdict. */
128
+ function normalize(line) {
129
+ return line.trim().replace(/\s+/g, ' ');
130
+ }
131
+
132
+ const CANONICAL_SET = new Set(Object.values(CANONICAL_FORMS).map(normalize));
133
+
134
+ /** Every `*.md` under `dir`, recursively, as paths relative to `dir`. */
135
+ function collectWorkflows(dir) {
136
+ const out = [];
137
+ const walk = (current, prefix) => {
138
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
139
+ const abs = path.join(current, entry.name);
140
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
141
+ if (entry.isDirectory()) walk(abs, rel);
142
+ else if (entry.isFile() && entry.name.endsWith('.md')) out.push(rel);
143
+ }
144
+ };
145
+ walk(dir, '');
146
+ return out.sort();
147
+ }
148
+
149
+ /**
150
+ * Classify one workflow's PHASE_ARG handling.
151
+ *
152
+ * `reads` counts bash-block reads only: `${PHASE_ARG}` inside prose is a
153
+ * mention (an error message being described, an example invocation), not a
154
+ * consumption, and requiring an assignment for those would make the rule fire
155
+ * on documentation.
156
+ */
157
+ function inspect(text, sectionizer = loadSectionizer()) {
158
+ const lines = text.split('\n');
159
+ const shell = sectionizer
160
+ .scanFencedBlocks(lines)
161
+ .filter((b) => SHELL_INFO_STRINGS.has(b.infoString.trim().split(/\s+/)[0]))
162
+ // closeLineIdx === -1 marks an unterminated fence: take the rest of the
163
+ // file rather than dropping the block, so a workflow whose last fence is
164
+ // missing its closer is still scanned instead of silently exempted.
165
+ .map((b) => lines.slice(b.openLineIdx + 1, b.closeLineIdx === -1 ? lines.length : b.closeLineIdx).join('\n'))
166
+ .join('\n');
167
+
168
+ const assignments = [...shell.matchAll(ASSIGN_RE)].map((m) => m[0]);
169
+ const firstAssignIdx = shell.search(/^[ \t]*PHASE_ARG=(?!=)/m);
170
+ const firstReadIdx = shell.search(READ_RE);
171
+
172
+ // The prose search deliberately runs over the WHOLE file rather than the
173
+ // non-bash remainder: a derivation step is a prose line, and stripping
174
+ // blocks first would only add a way for the two to disagree.
175
+ const hasProse = text.split('\n').some((line) => PROSE_ASSIGN_RE.test(line));
176
+
177
+ return {
178
+ reads: firstReadIdx !== -1,
179
+ assignments,
180
+ nonCanonical: assignments.filter((a) => !CANONICAL_SET.has(normalize(a))),
181
+ // An assignment that lands after the first read is the same defect with
182
+ // extra steps — the read still expands to nothing.
183
+ assignsBeforeRead: firstAssignIdx !== -1 && firstReadIdx !== -1 && firstAssignIdx <= firstReadIdx,
184
+ hasProse,
185
+ };
186
+ }
187
+
188
+ /** Scan a workflows directory. Returns `{ scanned, violations, staleExemptions }`. */
189
+ function scan(rootDir = DEFAULT_ROOT, exempt = EXEMPT) {
190
+ const violations = [];
191
+ const seenExempt = new Set();
192
+ const sectionizer = loadSectionizer();
193
+ let scanned = 0;
194
+
195
+ for (const rel of collectWorkflows(rootDir)) {
196
+ const report = inspect(fs.readFileSync(path.join(rootDir, rel), 'utf8'), sectionizer);
197
+ if (!report.reads && report.assignments.length === 0) continue;
198
+ scanned += 1;
199
+
200
+ // Decide the verdict FIRST, then consult the exemption list. Marking an
201
+ // entry used before knowing whether the file would actually fail lets an
202
+ // exemption outlive its reason: the file gets fixed, the entry stays, and
203
+ // it silently pre-forgives the NEXT unassigned read in that same file.
204
+ let violation = null;
205
+ if (report.nonCanonical.length > 0) {
206
+ violation = { file: rel, kind: 'non-canonical', detail: normalize(report.nonCanonical[0]) };
207
+ } else if (!report.reads) {
208
+ violation = null;
209
+ } else if (report.assignments.length === 0 && !report.hasProse) {
210
+ violation = { file: rel, kind: 'unassigned', detail: 'reads ${PHASE_ARG} with no assignment and no derivation step' };
211
+ } else if (report.assignments.length > 0 && !report.assignsBeforeRead) {
212
+ violation = { file: rel, kind: 'late-assignment', detail: 'PHASE_ARG is assigned after its first use' };
213
+ }
214
+
215
+ if (!violation) continue;
216
+ if (exempt.has(rel)) { seenExempt.add(rel); continue; }
217
+ violations.push(violation);
218
+ }
219
+
220
+ const staleExemptions = [...exempt.keys()]
221
+ .filter((rel) => !seenExempt.has(rel))
222
+ .map((rel) => ({ file: rel, reason: exempt.get(rel) }));
223
+
224
+ return { scanned, violations, staleExemptions };
225
+ }
226
+
227
+ function main() {
228
+ const { scanned, violations, staleExemptions } = scan();
229
+ const parts = [];
230
+
231
+ if (violations.length > 0) {
232
+ parts.push(
233
+ 'lint-phase-arg-assignment: these workflows do not say where ${PHASE_ARG} comes from (#4777).\n' +
234
+ 'An unassigned PHASE_ARG expands to the empty string, init.phase-op resolves\n' +
235
+ 'phase_found:false for every invocation, and the workflow exits reporting that a blank\n' +
236
+ 'phase was not executed. Add one of the canonical forms in this script — or, for a model-\n' +
237
+ 'derived value, a prose derivation step — before the first read:\n' +
238
+ violations.map((v) => ` gsd-core/workflows/${v.file} [${v.kind}] ${v.detail}`).join('\n'),
239
+ );
240
+ }
241
+ if (staleExemptions.length > 0) {
242
+ parts.push(
243
+ 'lint-phase-arg-assignment: these EXEMPT entries are stale — delete them so the next\n' +
244
+ 'unassigned read in those files is caught rather than silently pre-forgiven:\n' +
245
+ staleExemptions.map((s) => ` ${s.file}: ${s.reason}`).join('\n'),
246
+ );
247
+ }
248
+ if (parts.length > 0) throw new ExitError(1, parts.join('\n\n'));
249
+
250
+ console.log(
251
+ `ok lint-phase-arg-assignment: ${scanned} workflow(s) checked, ${EXEMPT.size} exemption(s) all still needed`,
252
+ );
253
+ }
254
+
255
+ module.exports = { CANONICAL_FORMS, EXEMPT, DEFAULT_ROOT, inspect, scan };
256
+
257
+ if (require.main === module) runMain(main);