@opengsd/gsd-core 1.9.1 → 1.11.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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-health-diagnostic-rule-table.cjs — gate: enforces ADR-3180 §8.2's 1:1
6
+ * rule-code invariant and §8.5's fixture-proof invariant for
7
+ * `src/health-diagnostic.cts`'s RULES table (Phase 11, #3309).
8
+ *
9
+ * ## What this enforces
10
+ *
11
+ * 1. (§8.2 rule 1 — 1:1 code invariant) Every `rule.code` in RULES (exported
12
+ * from the compiled `gsd-core/bin/lib/health-diagnostic.cjs`) is unique,
13
+ * and every rule's `severity` is one of `SEVERITY`'s values. The severity
14
+ * check exists only to confirm the compiled artifact was not hand-edited
15
+ * to bypass the `Rule.severity` required field TypeScript already
16
+ * enforces at compile time — "severity is a property of the RULE, never
17
+ * the emit call."
18
+ * 2. (§8.5 — fixture-proof invariant) Every code in RULES has a paired test:
19
+ * the code string (e.g. `'W001'`) appears as a literal AND within a
20
+ * `describe(`/`test(` block whose title also names that exact code, in
21
+ * one of the health-diagnostic test files
22
+ * (`tests/health-diagnostic-rules/*.test.cjs`,
23
+ * `tests/health-diagnostic.test.cjs`). A mere comment/string mention
24
+ * outside a titled block does not count as coverage.
25
+ *
26
+ * EXCEPTION — `PERMANENTLY_INERT_CODES` (below): a rule whose `check`
27
+ * always returns `[]` BY DESIGN (the real check lives outside the rule
28
+ * table entirely, because it needs ambient I/O `Rule.check` cannot
29
+ * perform — §8.1 rule 1) can never satisfy a real fixture-proof, no
30
+ * matter how many tests reference its code. Before this exception
31
+ * existed, W024 "passed" this guard only because an unrelated test title
32
+ * (the RULES-array shape assertion, "exports exactly 5 rules: W024, ...")
33
+ * happened to contain the string "W024" — accidental coverage, not proof
34
+ * the rule can fire. `PERMANENTLY_INERT_CODES` makes that exemption
35
+ * explicit and auditable instead of relying on a coincidental title
36
+ * match, and the PASS output now reports exempted codes SEPARATELY from
37
+ * genuinely fixture-covered ones rather than folding them together.
38
+ * 3. (Phase 12, #3310 — S0NN pass) `cmdStateValidate` (src/state.cts) emits
39
+ * 7 coded diagnostics (S001-S007) built inline via a local
40
+ * `stateDiagnostic()` helper — NOT `Rule`-table entries, so they are not
41
+ * read from the compiled module the way RULES/CONSISTENCY_RULES are.
42
+ * This third pass hardcodes that code list (`STATE_VALIDATE_CODES`,
43
+ * below) and applies the SAME §8.5 fixture-proof check against
44
+ * `tests/state.test.cjs`, reported in its own PASS/FAIL section —
45
+ * mirroring the C0NN pass's structure, just with a hardcoded list
46
+ * instead of a `Rule[]` array as the code source.
47
+ *
48
+ * Design: .gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md
49
+ * ("The lint guard (§8.2 1:1 invariant + §8.5 fixture proof)").
50
+ *
51
+ * ## Deviation from the design doc's original plan
52
+ *
53
+ * The design doc assumed fixtures would live as separate files at
54
+ * `tests/fixtures/health-diagnostic/<code>.*`. That did not happen during
55
+ * implementation — all 8 rule-group test files
56
+ * (`tests/health-diagnostic-rules/*.test.cjs`) build fixtures INLINE via
57
+ * real temp directories (`createTempDir()` from `tests/helpers.cjs`) and a
58
+ * real, non-mocked `buildPlanningSnapshot(tmpCwd)` call (see
59
+ * `tests/health-diagnostic-rules/root-existence.test.cjs`). This guard
60
+ * therefore verifies the fixture-proof invariant STATICALLY against the
61
+ * test files' own text — mirroring `scripts/lint-fix-has-regression-test.cjs`'s
62
+ * house style — rather than dynamically re-running fixture-building code
63
+ * this guard does not own.
64
+ */
65
+
66
+ const fs = require('node:fs');
67
+ const path = require('node:path');
68
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
69
+
70
+ const REPO_ROOT = path.join(__dirname, '..');
71
+ const COMPILED_MODULE_REL = 'gsd-core/bin/lib/health-diagnostic.cjs';
72
+ const COMPILED_MODULE_PATH = path.join(REPO_ROOT, COMPILED_MODULE_REL);
73
+ const TEST_GROUP_DIR = path.join(REPO_ROOT, 'tests', 'health-diagnostic-rules');
74
+ const SKELETON_TEST_FILE = path.join(REPO_ROOT, 'tests', 'health-diagnostic.test.cjs');
75
+
76
+ // Phase 12 (#3310, ADR-3180 §8.4) — the C0NN namespace's own fixture-proof
77
+ // test file. §8.5 extends to `CONSISTENCY_RULES`'s NEW codes only (C001-C004
78
+ // — W006/W007 are already fixture-proofed above, against the SAME `Rule`
79
+ // objects; re-checking them here would be redundant, not additional
80
+ // coverage).
81
+ const CONSISTENCY_TEST_FILE = path.join(REPO_ROOT, 'tests', 'health-diagnostic-rules', 'consistency.test.cjs');
82
+ const CONSISTENCY_CODE_PREFIX_RE = /^C\d{3}$/;
83
+
84
+ // Phase 12 (#3310, ADR-3180 §8.5 extension) — the S0NN namespace's own
85
+ // fixture-proof pass. These 7 codes are NOT collected in any exported
86
+ // `Rule[]` array: `cmdStateValidate` (src/state.cts) builds `Diagnostic[]`
87
+ // directly via a local `stateDiagnostic()` helper, not via the rule-table
88
+ // evaluator (out of scope for the RULES/CONSISTENCY_RULES-keyed passes
89
+ // above). The list is therefore hardcoded here instead of read from the
90
+ // compiled module.
91
+ const STATE_VALIDATE_TEST_FILE = path.join(REPO_ROOT, 'tests', 'state.test.cjs');
92
+ const STATE_VALIDATE_CODES = ['S001', 'S002', 'S003', 'S004', 'S005', 'S006', 'S007'];
93
+
94
+ // Matches `describe(`/`test(`/`it(` calls whose first argument is a string
95
+ // literal, capturing that literal as the block's title. Line/regex-based
96
+ // (not full AST) per this repo's existing lint-guard house style
97
+ // (scripts/lint-planning-snapshot-bypass-drift.cjs's scanCode precedent).
98
+ const TITLED_BLOCK_RE = /\b(describe|test|it)\(\s*(['"`])((?:\\.|(?!\2)[^\\])*)\2/g;
99
+
100
+ // Rule codes whose `check` is a documented PERMANENT no-op (always returns
101
+ // `[]`) because the real check requires ambient I/O forbidden inside a
102
+ // `Rule.check(snapshot)` (§8.1 rule 1) — the real check runs elsewhere,
103
+ // outside the rule table. These can never be proven via a real
104
+ // diagnostic-firing fixture, so they are exempted from the §8.5 fixture-proof
105
+ // invariant explicitly here rather than via an accidental test-title match.
106
+ // Adding an entry is a deliberate, reviewed decision — see each reason.
107
+ const PERMANENTLY_INERT_CODES = new Map([
108
+ [
109
+ 'W024',
110
+ 'readStateHeadFreshness requires a git-log shell-out, forbidden ambient I/O for Rule.check ' +
111
+ '(§8.1 rule 1) — the real check runs in cmdValidateHealth itself, outside the rule table ' +
112
+ '(src/verify.cts). This rule-table entry is a permanent no-op by design, not a fixture gap.',
113
+ ],
114
+ ]);
115
+
116
+ /**
117
+ * Load the compiled health-diagnostic module. Throws a clear ExitError
118
+ * (not a raw MODULE_NOT_FOUND) if `npm run build:lib` has not run.
119
+ */
120
+ function loadCompiledModule(compiledPath = COMPILED_MODULE_PATH) {
121
+ if (!fs.existsSync(compiledPath)) {
122
+ throw new ExitError(
123
+ 2,
124
+ `lint-health-diagnostic-rule-table: compiled artifact not found at ${COMPILED_MODULE_REL}.\n` +
125
+ 'Run `npm run build:lib` first.',
126
+ );
127
+ }
128
+ return require(compiledPath);
129
+ }
130
+
131
+ /**
132
+ * §8.2 rule 1 — 1:1 code invariant: every rule.code is unique, and every
133
+ * rule's severity is a member of SEVERITY's values.
134
+ *
135
+ * @param {Array<{code: string, severity: string}>} rules
136
+ * @param {Record<string, string>} severity SEVERITY export (code -> value)
137
+ * @returns {{duplicates: Array<{code: string, count: number}>, badSeverities: Array<{code: string, severity: unknown}>}}
138
+ */
139
+ function checkOneToOneInvariant(rules, severity) {
140
+ const severityValues = new Set(Object.values(severity));
141
+ const counts = new Map();
142
+ const badSeverities = [];
143
+
144
+ for (const rule of rules) {
145
+ counts.set(rule.code, (counts.get(rule.code) || 0) + 1);
146
+ if (!severityValues.has(rule.severity)) {
147
+ badSeverities.push({ code: rule.code, severity: rule.severity });
148
+ }
149
+ }
150
+
151
+ const duplicates = [...counts.entries()]
152
+ .filter(([, count]) => count > 1)
153
+ .map(([code, count]) => ({ code, count }));
154
+
155
+ return { duplicates, badSeverities };
156
+ }
157
+
158
+ /**
159
+ * Extracts every `describe(`/`test(`/`it(` block title found in `text`.
160
+ *
161
+ * @param {string} text
162
+ * @returns {string[]}
163
+ */
164
+ function extractTitledBlocks(text) {
165
+ const titles = [];
166
+ TITLED_BLOCK_RE.lastIndex = 0;
167
+ let match;
168
+ while ((match = TITLED_BLOCK_RE.exec(text)) !== null) {
169
+ titles.push(match[3]);
170
+ }
171
+ return titles;
172
+ }
173
+
174
+ /**
175
+ * True iff `code` appears verbatim, as a whole token, inside at least one of
176
+ * `titles`. Whole-token match guards against a shorter code accidentally
177
+ * substring-matching inside an unrelated longer token.
178
+ *
179
+ * @param {string} code
180
+ * @param {string[]} titles
181
+ */
182
+ function codeAppearsInTitle(code, titles) {
183
+ const codeRe = new RegExp(`(?:^|[^A-Za-z0-9])${code}(?:$|[^A-Za-z0-9])`);
184
+ return titles.some((title) => codeRe.test(`|${title}|`));
185
+ }
186
+
187
+ /**
188
+ * Locates every health-diagnostic test file this guard scans for §8.5
189
+ * fixture-proof coverage.
190
+ *
191
+ * @param {string} repoRoot
192
+ * @returns {string[]} absolute paths, sorted
193
+ */
194
+ function findHealthDiagnosticTestFiles(repoRoot = REPO_ROOT) {
195
+ const groupDir = path.join(repoRoot, 'tests', 'health-diagnostic-rules');
196
+ const files = [];
197
+ if (fs.existsSync(groupDir)) {
198
+ for (const entry of fs.readdirSync(groupDir)) {
199
+ if (entry.endsWith('.test.cjs')) {
200
+ files.push(path.join(groupDir, entry));
201
+ }
202
+ }
203
+ }
204
+ const skeletonTestFile = path.join(repoRoot, 'tests', 'health-diagnostic.test.cjs');
205
+ if (fs.existsSync(skeletonTestFile)) {
206
+ files.push(skeletonTestFile);
207
+ }
208
+ return files.sort();
209
+ }
210
+
211
+ /**
212
+ * §8.5 — fixture-proof invariant: for every code in `rules`, confirm at
213
+ * least one test file in `testFiles` has a `describe(`/`test(`/`it(` block
214
+ * whose title names that exact code — UNLESS the code is listed in
215
+ * `PERMANENTLY_INERT_CODES`, in which case it is reported separately as
216
+ * `exempted` (visibly, not folded into "covered") and never fails the guard
217
+ * regardless of test coverage.
218
+ *
219
+ * @param {Array<{code: string}>} rules
220
+ * @param {string[]} testFiles absolute paths to *.test.cjs files to scan
221
+ * @param {Map<string, string>} inertCodes PERMANENTLY_INERT_CODES (injectable for tests)
222
+ * @returns {{uncovered: string[], exempted: string[], testFilesScanned: string[]}}
223
+ */
224
+ function checkFixtureProofInvariant(rules, testFiles, inertCodes = PERMANENTLY_INERT_CODES) {
225
+ const allTitles = [];
226
+ for (const file of testFiles) {
227
+ const text = fs.readFileSync(file, 'utf8');
228
+ allTitles.push(...extractTitledBlocks(text));
229
+ }
230
+
231
+ const uncovered = [];
232
+ const exempted = [];
233
+ for (const rule of rules) {
234
+ if (inertCodes.has(rule.code)) {
235
+ exempted.push(rule.code);
236
+ continue;
237
+ }
238
+ if (!codeAppearsInTitle(rule.code, allTitles)) {
239
+ uncovered.push(rule.code);
240
+ }
241
+ }
242
+
243
+ return { uncovered, exempted, testFilesScanned: testFiles };
244
+ }
245
+
246
+ function formatRepoRelative(absPath) {
247
+ return path.relative(REPO_ROOT, absPath).split(path.sep).join('/');
248
+ }
249
+
250
+ function main() {
251
+ const { RULES, CONSISTENCY_RULES, SEVERITY } = loadCompiledModule();
252
+
253
+ const { duplicates, badSeverities } = checkOneToOneInvariant(RULES, SEVERITY);
254
+
255
+ const testFiles = findHealthDiagnosticTestFiles(REPO_ROOT);
256
+ const { uncovered, exempted } = checkFixtureProofInvariant(RULES, testFiles);
257
+
258
+ // ─── C0NN check pass (Phase 12, #3310) — separate from the W/E/I pass
259
+ // above, own reporting section, does NOT re-check W006/W007's fixture
260
+ // proof (already covered above against the same `Rule` objects).
261
+ const consistencyNewRules = CONSISTENCY_RULES.filter((r) => CONSISTENCY_CODE_PREFIX_RE.test(r.code));
262
+ const { duplicates: consistencyDuplicates, badSeverities: consistencyBadSeverities } = checkOneToOneInvariant(
263
+ consistencyNewRules,
264
+ SEVERITY,
265
+ );
266
+ const consistencyTestFiles = fs.existsSync(CONSISTENCY_TEST_FILE) ? [CONSISTENCY_TEST_FILE] : [];
267
+ const { uncovered: consistencyUncovered } = checkFixtureProofInvariant(
268
+ consistencyNewRules,
269
+ consistencyTestFiles,
270
+ new Map(),
271
+ );
272
+
273
+ // ─── S0NN check pass (Phase 12, #3310) — separate from the W/E/I and C0NN
274
+ // passes above, own reporting section. Hardcoded code list (see the
275
+ // STATE_VALIDATE_CODES comment above) rather than read from a Rule[] array.
276
+ const stateValidateTestFiles = fs.existsSync(STATE_VALIDATE_TEST_FILE) ? [STATE_VALIDATE_TEST_FILE] : [];
277
+ const { uncovered: stateValidateUncovered } = checkFixtureProofInvariant(
278
+ STATE_VALIDATE_CODES.map((code) => ({ code })),
279
+ stateValidateTestFiles,
280
+ new Map(),
281
+ );
282
+
283
+ const problems = [];
284
+
285
+ if (duplicates.length > 0) {
286
+ const list = duplicates.map((d) => ` ${d.code} (${d.count} occurrences)`).join('\n');
287
+ problems.push(
288
+ `§8.2 rule 1 violated: ${duplicates.length} duplicated rule code(s) in RULES ` +
289
+ `(${COMPILED_MODULE_REL}):\n${list}\n` +
290
+ ' remedy: codes are append-only and 1:1 with a single Rule — rename or remove the duplicate.',
291
+ );
292
+ }
293
+
294
+ if (badSeverities.length > 0) {
295
+ const list = badSeverities
296
+ .map((b) => ` ${b.code}: severity=${JSON.stringify(b.severity)}`)
297
+ .join('\n');
298
+ problems.push(
299
+ `§8.2 rule 3 violated: ${badSeverities.length} rule(s) with a severity not in SEVERITY's values:\n${list}\n` +
300
+ ' remedy: severity is a property of the RULE — set it to SEVERITY.ERROR/WARNING/INFO.',
301
+ );
302
+ }
303
+
304
+ if (uncovered.length > 0) {
305
+ const scannedList = testFiles.map(formatRepoRelative).join('\n ');
306
+ problems.push(
307
+ `§8.5 violated: ${uncovered.length} rule code(s) with no describe()/test() block naming them ` +
308
+ `(a comment or bare string mention does not count):\n ${uncovered.join(', ')}\n\n` +
309
+ ` Searched these test files:\n ${scannedList}\n\n` +
310
+ ' remedy: add or extend a describe()/test() title in the matching ' +
311
+ 'tests/health-diagnostic-rules/<group>.test.cjs file so the block title ' +
312
+ `names the code verbatim (e.g. describe('${uncovered[0]} — ...', () => { ... })), ` +
313
+ 'and drive the rule to fire against a real fixture built via createTempDir() + buildPlanningSnapshot() ' +
314
+ '(see tests/health-diagnostic-rules/root-existence.test.cjs).',
315
+ );
316
+ }
317
+
318
+ if (consistencyDuplicates.length > 0) {
319
+ const list = consistencyDuplicates.map((d) => ` ${d.code} (${d.count} occurrences)`).join('\n');
320
+ problems.push(
321
+ `§8.2 rule 1 violated (C0NN namespace): ${consistencyDuplicates.length} duplicated rule code(s) in ` +
322
+ `CONSISTENCY_RULES (${COMPILED_MODULE_REL}):\n${list}\n` +
323
+ ' remedy: codes are append-only and 1:1 with a single Rule — rename or remove the duplicate.',
324
+ );
325
+ }
326
+
327
+ if (consistencyBadSeverities.length > 0) {
328
+ const list = consistencyBadSeverities
329
+ .map((b) => ` ${b.code}: severity=${JSON.stringify(b.severity)}`)
330
+ .join('\n');
331
+ problems.push(
332
+ `§8.2 rule 3 violated (C0NN namespace): ${consistencyBadSeverities.length} rule(s) with a severity not ` +
333
+ `in SEVERITY's values:\n${list}\n` +
334
+ ' remedy: severity is a property of the RULE — set it to SEVERITY.ERROR/WARNING/INFO.',
335
+ );
336
+ }
337
+
338
+ if (consistencyUncovered.length > 0) {
339
+ problems.push(
340
+ `§8.5 violated (C0NN namespace): ${consistencyUncovered.length} rule code(s) with no describe()/test() ` +
341
+ `block naming them (a comment or bare string mention does not count):\n ${consistencyUncovered.join(', ')}\n\n` +
342
+ ` Searched: ${formatRepoRelative(CONSISTENCY_TEST_FILE)}` +
343
+ (consistencyTestFiles.length === 0 ? ' (file not found)' : '') +
344
+ '\n\n remedy: add or extend a describe()/test() title in tests/health-diagnostic-rules/consistency.test.cjs ' +
345
+ `so the block title names the code verbatim (e.g. describe('${consistencyUncovered[0]} — ...', () => { ... })), ` +
346
+ 'and drive the rule to fire against a real fixture built via createTempDir() + buildPlanningSnapshot().',
347
+ );
348
+ }
349
+
350
+ if (stateValidateUncovered.length > 0) {
351
+ problems.push(
352
+ `§8.5 violated (S0NN namespace): ${stateValidateUncovered.length} code(s) with no describe()/test() ` +
353
+ `block naming them (a comment or bare string mention does not count):\n ${stateValidateUncovered.join(', ')}\n\n` +
354
+ ` Searched: ${formatRepoRelative(STATE_VALIDATE_TEST_FILE)}` +
355
+ (stateValidateTestFiles.length === 0 ? ' (file not found)' : '') +
356
+ '\n\n remedy: add or extend a describe()/test() title in tests/state.test.cjs ' +
357
+ `so the block title names the code verbatim (e.g. test('${stateValidateUncovered[0]}: ...', () => { ... })), ` +
358
+ 'mirroring the existing `#3310 state validate — S0NN coded diagnostics` describe block.',
359
+ );
360
+ }
361
+
362
+ if (problems.length > 0) {
363
+ throw new ExitError(1, `${problems.join('\n\n')}\n`);
364
+ }
365
+
366
+ const coveredCount = RULES.length - exempted.length;
367
+ const exemptedDetail = exempted
368
+ .map((code) => `${code} (${PERMANENTLY_INERT_CODES.get(code)})`)
369
+ .join('; ');
370
+
371
+ console.log(
372
+ `lint-health-diagnostic-rule-table: PASS — ${RULES.length} rule code(s): ${coveredCount} covered by a ` +
373
+ `real fixture, ${exempted.length} exempted across ${testFiles.length} test file(s).` +
374
+ (exempted.length > 0 ? `\n Exempted: ${exemptedDetail}` : ''),
375
+ );
376
+ console.log(
377
+ `lint-health-diagnostic-rule-table: PASS (C0NN namespace) — ${consistencyNewRules.length} new rule code(s) ` +
378
+ `(${consistencyNewRules.map((r) => r.code).join(', ')}), all fixture-covered in ` +
379
+ `${formatRepoRelative(CONSISTENCY_TEST_FILE)}.`,
380
+ );
381
+ console.log(
382
+ `lint-health-diagnostic-rule-table: PASS (S0NN namespace) — ${STATE_VALIDATE_CODES.length} code(s) ` +
383
+ `(${STATE_VALIDATE_CODES.join(', ')}), all fixture-covered in ` +
384
+ `${formatRepoRelative(STATE_VALIDATE_TEST_FILE)}.`,
385
+ );
386
+ }
387
+
388
+ runMain(main);
389
+
390
+ module.exports = {
391
+ loadCompiledModule,
392
+ checkOneToOneInvariant,
393
+ extractTitledBlocks,
394
+ codeAppearsInTitle,
395
+ findHealthDiagnosticTestFiles,
396
+ checkFixtureProofInvariant,
397
+ PERMANENTLY_INERT_CODES,
398
+ COMPILED_MODULE_PATH,
399
+ TEST_GROUP_DIR,
400
+ SKELETON_TEST_FILE,
401
+ CONSISTENCY_TEST_FILE,
402
+ STATE_VALIDATE_TEST_FILE,
403
+ STATE_VALIDATE_CODES,
404
+ };
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-hooks-runtime-build-seam.cjs — every `hooks/**` file that requires a
6
+ * compiled runtime-library module (`gsd-core/bin/lib/*.cjs`) must also go
7
+ * through the self-healing build seam (`gsd-core/bin/ensure-runtime-build.cjs`,
8
+ * #2002) before that require can be reached (#3582).
9
+ *
10
+ * ## Why
11
+ *
12
+ * `gsd-core/bin/lib/*.cjs` are compiled from `src/*.cts` by `npm run
13
+ * build:lib` (ADR-457, "build-at-publish") and are gitignored. The npm
14
+ * package builds before publishing, so the artifacts exist on that path.
15
+ * `gsd-core/bin/gsd-tools.cjs` calls `ensureRuntimeBuild()` before requiring
16
+ * its own `./lib` — but a plugin-marketplace / git-clone install that never
17
+ * runs `npm run build:lib` materializes the raw tree with the compiled
18
+ * `./lib` absent, and NO hook self-healed before this issue. A hook that
19
+ * requires a compiled module directly dies at module load (or first call)
20
+ * with `Cannot find module`, and — in a guard whose own catch treats "cannot
21
+ * resolve" as fail-closed — that `Cannot find module` gets misreported as an
22
+ * unrelated "could not read or resolve …configuration" denial instead of the
23
+ * seam's own actionable message (#3050 lesson: a guard that cannot verify
24
+ * must not silently misreport why).
25
+ *
26
+ * ## What this enforces
27
+ *
28
+ * For every `.js`/`.cjs` file under `hooks/` (excluding the generated
29
+ * `hooks/dist/` build-output copy, which is not source): if the file's code
30
+ * (comments stripped) contains a `require(...)` of a path that names
31
+ * `gsd-core/bin/lib/<name>.cjs` (any relative-path spelling — `../gsd-core/…`,
32
+ * `../../gsd-core/…`, etc. — the segment `gsd-core/bin/lib/` plus a trailing
33
+ * `.cjs` is the discriminator, not the exact prefix), the SAME file must ALSO
34
+ * contain BOTH:
35
+ * 1. a `require(...)` of a path naming `gsd-core/bin/ensure-runtime-build.cjs`
36
+ * (the seam module itself), AND
37
+ * 2. an actual INVOCATION `ensureRuntimeBuild(` — not merely a destructuring
38
+ * import with no call, which would import the seam but never run it.
39
+ *
40
+ * File-level co-occurrence, not line-by-line ordering: this repo's hooks
41
+ * define helper functions in whatever order reads best and call them in a
42
+ * different order at runtime (e.g. `hooks/gsd-cursor-subagent-start.js`'s
43
+ * `resolveFallbackIsolation` is defined textually ABOVE the single
44
+ * `ensureRuntimeBuild()` call site in `evaluateRootIsolation`, which is its
45
+ * only caller) — a strict "seam call must appear on an earlier LINE than the
46
+ * require" check would false-positive on exactly that, real, correct
47
+ * pattern. This guard is therefore a drift ratchet at the file level ("did
48
+ * the seam get wired in at all"), not a full control-flow verifier; exact
49
+ * placement (does the call precede every reaching path) is a code-review
50
+ * concern, same tradeoff every other structural drift guard in this repo
51
+ * makes (see e.g. `lint-unreachable-guard-drift.cjs`'s own per-line, not
52
+ * per-flow, scope).
53
+ *
54
+ * ## What PASSES
55
+ *
56
+ * - A hook that never requires `gsd-core/bin/lib/*.cjs` at all (most of
57
+ * `hooks/`) — nothing to check.
58
+ * - A hook that requires a compiled module AND requires + calls
59
+ * `ensureRuntimeBuild()` somewhere in the same file.
60
+ * - A prose comment that mentions `gsd-core/bin/lib/…` without a real,
61
+ * well-formed `require('....cjs')` call (comments are stripped before
62
+ * scanning).
63
+ *
64
+ * ## What FAILS
65
+ *
66
+ * - A hook that requires a compiled `gsd-core/bin/lib/*.cjs` module but never
67
+ * requires `ensure-runtime-build.cjs`, or requires it but never calls
68
+ * `ensureRuntimeBuild(...)`.
69
+ *
70
+ * ## Known limitations (documented, not defects — read before trusting a green run)
71
+ *
72
+ * - **Literal-string matching only.** `REQUIRE_RE` matches `require(...)`
73
+ * called with a single- OR double-quoted string literal argument. A
74
+ * concatenated/computed path (`require(base + '/gsd-core/bin/lib/x.cjs')`),
75
+ * a template literal (`` require(`${dir}/x.cjs`) ``), or `createRequire(...)`
76
+ * / `require.resolve` used indirectly all evade `isCompiledLibRequire` and
77
+ * `isSeamRequire` — none of them appear as a literal quoted `require(...)`
78
+ * call, so a file using one of these forms scans as "requires nothing",
79
+ * even if it genuinely needs the seam.
80
+ * - **`hooks/` only.** `scanRepo`'s `walk()` only descends `SCAN_ROOT`
81
+ * (`hooks/`, minus `hooks/dist/`). A compiled `gsd-core/bin/lib/*.cjs`
82
+ * require sitting inside a NON-hooks helper module that a hook file then
83
+ * requires (directly or transitively) is invisible to this scan — the scan
84
+ * only ever reads the hook file's OWN text, never follows its require
85
+ * graph. Deliberate: this is a same-file drift ratchet ("did the hook wire
86
+ * the seam in at all"), not a whole-program static analyzer.
87
+ *
88
+ * Because of both limits, a file that passes this lint is not a
89
+ * correctness proof — it is a co-occurrence heuristic that catches the
90
+ * exact regression shape #3582 fixed (a bare literal compiled-lib require
91
+ * with no seam call anywhere in the same hook file). Anything routed around
92
+ * a literal require or around `hooks/` needs a code-review check, not this
93
+ * script, to catch a missing self-heal.
94
+ */
95
+
96
+ const fs = require('fs');
97
+ const path = require('path');
98
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
99
+
100
+ const ROOT = path.join(__dirname, '..');
101
+ const SCAN_ROOT = 'hooks';
102
+ const SCAN_EXT = new Set(['.js', '.cjs']);
103
+ // hooks/dist/ is a generated build-output copy (scripts/build-hooks.js),
104
+ // gitignored and not source — never scanned.
105
+ const EXCLUDE_DIR_NAMES = new Set(['dist']);
106
+
107
+ // Line-based comment stripper (deliberately NOT the naive two-regex
108
+ // `/\*[\s\S]*?\*\//g` then `//` approach other tests in this repo use for
109
+ // simpler files): this module's own source comments legitimately spell
110
+ // `gsd-core/bin/lib/*.cjs` (a glob) inside a `//` line, which forms a bare
111
+ // `/*` token — a whole-text block-comment regex applied naively would read
112
+ // that as an OPENING block comment and swallow everything up to the next
113
+ // unrelated `*/` anywhere later in the file, silently deleting real code
114
+ // (verified while authoring this guard: it ate the very require() line the
115
+ // guard exists to check for). Processing line-by-line and stripping a
116
+ // trailing `//` comment BEFORE ever checking that line's remainder for `/*`
117
+ // means a `/*`-shaped token inside a `//` comment is never seen at all.
118
+ function stripComments(text) {
119
+ const lines = text.split(/\r?\n/);
120
+ const out = [];
121
+ let inBlock = false;
122
+ for (const raw of lines) {
123
+ let line = raw;
124
+ if (inBlock) {
125
+ const end = line.indexOf('*/');
126
+ if (end === -1) { out.push(''); continue; }
127
+ line = line.slice(end + 2);
128
+ inBlock = false;
129
+ }
130
+ if (line.trim().startsWith('//')) { out.push(''); continue; }
131
+ // Strip a trailing `//` comment (not a `://` URL) BEFORE any `/*` check
132
+ // on the remainder — see the function header for why this ordering
133
+ // matters.
134
+ const trailing = /(^|[^:])\/\/.*$/.exec(line);
135
+ if (trailing) line = line.slice(0, trailing.index + trailing[1].length);
136
+ // Same-line and newly-opened block comments in what remains.
137
+ for (;;) {
138
+ const start = line.indexOf('/*');
139
+ if (start === -1) break;
140
+ const end = line.indexOf('*/', start + 2);
141
+ if (end !== -1) {
142
+ line = line.slice(0, start) + line.slice(end + 2);
143
+ // keep scanning from the same position in case of a second
144
+ // same-line block comment
145
+ } else {
146
+ line = line.slice(0, start);
147
+ inBlock = true;
148
+ break;
149
+ }
150
+ }
151
+ out.push(line);
152
+ }
153
+ return out.join('\n');
154
+ }
155
+
156
+ // Captures the quoted path of every require(...) call. Single bounded
157
+ // negated-class quantifier per alternative, no nesting.
158
+ const REQUIRE_RE = /require\(\s*(['"])([^'"]+)\1\s*\)/g;
159
+
160
+ function isCompiledLibRequire(requirePath) {
161
+ return requirePath.includes('gsd-core/bin/lib/') && requirePath.endsWith('.cjs');
162
+ }
163
+
164
+ function isSeamRequire(requirePath) {
165
+ return requirePath.endsWith('gsd-core/bin/ensure-runtime-build.cjs');
166
+ }
167
+
168
+ // An actual invocation, not merely a `{ ensureRuntimeBuild }` destructuring
169
+ // import — the identifier immediately followed by `(`.
170
+ const SEAM_CALL_RE = /\bensureRuntimeBuild\s*\(/;
171
+
172
+ /**
173
+ * Pure: scan one file's contents for the violation described above.
174
+ * Returns `{ compiledLibRequires: string[], hasSeamRequire: boolean,
175
+ * hasSeamCall: boolean }`. `compiledLibRequires` is `[]` when the file
176
+ * requires no compiled module — the caller treats that as "nothing to
177
+ * check" regardless of the other two fields.
178
+ */
179
+ function scanFile(text) {
180
+ const code = stripComments(text);
181
+ const compiledLibRequires = [];
182
+ let hasSeamRequire = false;
183
+ let m;
184
+ REQUIRE_RE.lastIndex = 0;
185
+ while ((m = REQUIRE_RE.exec(code)) !== null) {
186
+ const requirePath = m[2];
187
+ if (isCompiledLibRequire(requirePath)) compiledLibRequires.push(requirePath);
188
+ if (isSeamRequire(requirePath)) hasSeamRequire = true;
189
+ }
190
+ const hasSeamCall = SEAM_CALL_RE.test(code);
191
+ return { compiledLibRequires, hasSeamRequire, hasSeamCall };
192
+ }
193
+
194
+ function walk(dir) {
195
+ const out = [];
196
+ let entries;
197
+ try {
198
+ entries = fs.readdirSync(dir, { withFileTypes: true });
199
+ } catch {
200
+ return out; // missing root is not an error here — caller decides
201
+ }
202
+ for (const entry of entries) {
203
+ if (entry.isDirectory() && EXCLUDE_DIR_NAMES.has(entry.name)) continue;
204
+ const full = path.join(dir, entry.name);
205
+ if (entry.isDirectory()) {
206
+ out.push(...walk(full));
207
+ } else if (entry.isFile() && SCAN_EXT.has(path.extname(entry.name))) {
208
+ out.push(full);
209
+ }
210
+ }
211
+ return out;
212
+ }
213
+
214
+ /**
215
+ * Scan `root`'s hooks/ tree and return every violating file.
216
+ * @param {string} root repo root (or a fixture root for tests)
217
+ * @returns {{ file: string, compiledLibRequires: string[], missing: string[] }[]}
218
+ */
219
+ function scanRepo(root) {
220
+ const violations = [];
221
+ for (const full of walk(path.join(root, SCAN_ROOT))) {
222
+ const text = fs.readFileSync(full, 'utf8');
223
+ const { compiledLibRequires, hasSeamRequire, hasSeamCall } = scanFile(text);
224
+ if (compiledLibRequires.length === 0) continue;
225
+ if (hasSeamRequire && hasSeamCall) continue;
226
+ const missing = [];
227
+ if (!hasSeamRequire) missing.push('require(".../gsd-core/bin/ensure-runtime-build.cjs")');
228
+ if (!hasSeamCall) missing.push('a call to ensureRuntimeBuild(...)');
229
+ violations.push({
230
+ file: path.relative(root, full).split(path.sep).join('/'),
231
+ compiledLibRequires,
232
+ missing,
233
+ });
234
+ }
235
+ return violations;
236
+ }
237
+
238
+ function main() {
239
+ const violations = scanRepo(ROOT);
240
+ if (violations.length > 0) {
241
+ const detail = violations
242
+ .map((v) => (
243
+ ` ${v.file}\n` +
244
+ ` requires: ${v.compiledLibRequires.join(', ')}\n` +
245
+ ` missing: ${v.missing.join(' AND ')}`
246
+ ))
247
+ .join('\n');
248
+ throw new ExitError(
249
+ 1,
250
+ `lint-hooks-runtime-build-seam: ${violations.length} hooks/ file(s) require a compiled\n` +
251
+ `gsd-core/bin/lib/*.cjs module without also self-healing via\n` +
252
+ `ensureRuntimeBuild() from gsd-core/bin/ensure-runtime-build.cjs first (#3582) — on a\n` +
253
+ `plugin-marketplace/git-clone install that never ran \`npm run build:lib\`, that\n` +
254
+ `require crashes (or is silently misreported) instead of self-building:\n\n${detail}\n`,
255
+ );
256
+ }
257
+ console.log('ok lint-hooks-runtime-build-seam: every hooks/ compiled-lib require goes through ensureRuntimeBuild()');
258
+ }
259
+
260
+ module.exports = { scanFile, scanRepo, walk, stripComments, isCompiledLibRequire, isSeamRequire, SEAM_CALL_RE };
261
+
262
+ if (require.main === module) runMain(main);