@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
@@ -12,12 +12,17 @@
12
12
  * Tests assert on the typed verdict, never on free text.
13
13
  */
14
14
 
15
+ // #2988: the repo's integration/default branch — the base every PR targets.
16
+ // Used as the local fallback when GITHUB_BASE_REF is unset (CI sets it).
17
+ const DEFAULT_BASE = 'next';
18
+
15
19
  const LINT_REASON = Object.freeze({
16
20
  OK_FRAGMENT_PRESENT: 'ok_fragment_present',
17
21
  OK_OPT_OUT_LABEL: 'ok_opt_out_label',
18
22
  OK_NO_USER_FACING_CHANGES: 'ok_no_user_facing_changes',
19
23
  FAIL_MISSING_FRAGMENT: 'fail_missing_fragment',
20
24
  FAIL_INVALID_FRAGMENT: 'fail_invalid_fragment',
25
+ FAIL_PR_FIELD_DRIFT: 'fail_pr_field_drift',
21
26
  });
22
27
 
23
28
  const OPT_OUT_LABEL = 'no-changelog';
@@ -31,8 +36,6 @@ const USER_FACING_PREFIXES = [
31
36
  'agents/',
32
37
  'commands/',
33
38
  'hooks/',
34
- 'sdk/src/',
35
- 'sdk/prompts/',
36
39
  ];
37
40
 
38
41
  // Exact-match user-facing files. Any direct edit to one of these without a
@@ -49,10 +52,47 @@ function isFragment(file) {
49
52
  return /^\.changeset\/[^/]+\.md$/.test(file) && !file.endsWith('/README.md');
50
53
  }
51
54
 
52
- function evaluateLint({ changedFiles, labels, fragmentFailures = [] }) {
55
+ /**
56
+ * DEFECT.CHANGESET-PR-FIELD-DRIFT (#3316, #3325): a fragment's `pr:` field is
57
+ * a guess (issue number, stacked-PR leftover) that never got backfilled to
58
+ * the real PR number after `gh api POST /pulls` returned it.
59
+ *
60
+ * Pure: given `prEntries` (`[{ file, pr }]`, one entry per successfully
61
+ * parsed changed fragment — `pr` is always a positive integer, since
62
+ * `parseFragment` already rejects `pr: 0` / non-numeric values as
63
+ * `invalid_pr` before a fragment ever reaches this function) and
64
+ * `realPrNumber` (the PR this run belongs to, or `null` when unknown —
65
+ * push/non-PR runs), returns every fragment whose `pr` disagrees with
66
+ * `realPrNumber`. `realPrNumber == null` always yields `[]`: with no PR
67
+ * event payload to compare against, there is nothing to drift-check.
68
+ *
69
+ * `pr === 0` is always silent regardless of `realPrNumber` — CONTRIBUTING.md
70
+ * documents `pr: 0` as the deliberate placeholder used "during initial
71
+ * commit" before `gh api POST /pulls` returns the real number, so it is not
72
+ * yet a drifted value, just an unbackfilled one. (In practice `parseFragment`
73
+ * already rejects `pr: 0` as `invalid_pr` before a fragment reaches this
74
+ * function via `main()`'s wiring — this guard documents and locks in the
75
+ * pure function's own contract independent of that upstream check.)
76
+ */
77
+ function findPrFieldDrift(prEntries, realPrNumber) {
78
+ if (realPrNumber == null) return [];
79
+ const drift = [];
80
+ for (const { file, pr } of prEntries) {
81
+ if (pr === 0) continue;
82
+ if (pr !== realPrNumber) {
83
+ drift.push({ file, found: pr, expected: realPrNumber });
84
+ }
85
+ }
86
+ return drift;
87
+ }
88
+
89
+ function evaluateLint({ changedFiles, labels, fragmentFailures = [], prFieldDrift = [] }) {
53
90
  if (fragmentFailures.length > 0) {
54
91
  return { ok: false, reason: LINT_REASON.FAIL_INVALID_FRAGMENT, failures: fragmentFailures };
55
92
  }
93
+ if (prFieldDrift.length > 0) {
94
+ return { ok: false, reason: LINT_REASON.FAIL_PR_FIELD_DRIFT, drift: prFieldDrift };
95
+ }
56
96
  if (changedFiles.some(isFragment)) {
57
97
  return { ok: true, reason: LINT_REASON.OK_FRAGMENT_PRESENT };
58
98
  }
@@ -74,13 +114,24 @@ function main() {
74
114
  // GitHub Actions event payload path
75
115
  const eventPath = process.env.GITHUB_EVENT_PATH;
76
116
  let labels = [];
117
+ // DEFECT.CHANGESET-PR-FIELD-DRIFT: the real PR number this run belongs to,
118
+ // read from the same event payload. `null` on a push / non-PR run (no
119
+ // `pull_request` in the payload, or no payload at all) — the drift check
120
+ // below is a no-op in that case, it never fails a push run.
121
+ let realPrNumber = null;
77
122
  if (eventPath && fs.existsSync(eventPath)) {
78
123
  try {
79
124
  const event = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
80
125
  labels = (event.pull_request?.labels || []).map((l) => l.name);
126
+ if (event.pull_request && Number.isInteger(event.pull_request.number)) {
127
+ realPrNumber = event.pull_request.number;
128
+ }
81
129
  } catch { /* fall through */ }
82
130
  }
83
- const base = process.env.GITHUB_BASE_REF || 'main';
131
+ // #2988: local fallback must match the repo's integration branch (`next`),
132
+ // not the release branch (`main`). CI sets GITHUB_BASE_REF explicitly; the
133
+ // fallback only fires locally, where `next` is the base every PR targets.
134
+ const base = process.env.GITHUB_BASE_REF || DEFAULT_BASE;
84
135
  let changedFiles = [];
85
136
  try {
86
137
  // Use execFileSync with an argv array — the base ref is interpolated
@@ -100,6 +151,7 @@ function main() {
100
151
 
101
152
  // Validate the content of every changed fragment file.
102
153
  const fragmentFailures = [];
154
+ const prEntries = [];
103
155
  for (const file of changedFiles) {
104
156
  if (!isFragment(file)) continue;
105
157
  // A fragment path in the diff that no longer exists on disk was deleted in
@@ -118,10 +170,13 @@ function main() {
118
170
  const result = parseFragment(src);
119
171
  if (!result.ok) {
120
172
  fragmentFailures.push({ file, reason: result.reason, detail: result.detail });
173
+ continue;
121
174
  }
175
+ prEntries.push({ file, pr: result.fragment.pr });
122
176
  }
123
177
 
124
- const verdict = evaluateLint({ changedFiles, labels, fragmentFailures });
178
+ const prFieldDrift = findPrFieldDrift(prEntries, realPrNumber);
179
+ const verdict = evaluateLint({ changedFiles, labels, fragmentFailures, prFieldDrift });
125
180
  if (process.argv.includes('--json')) {
126
181
  process.stdout.write(JSON.stringify({ ...verdict, changedFiles, labels }, null, 2) + '\n');
127
182
  } else if (verdict.ok) {
@@ -134,6 +189,13 @@ function main() {
134
189
  process.stderr.write(` ${f.file}: ${f.reason}${detail}\n`);
135
190
  }
136
191
  process.stderr.write(`Fix the fragment(s) above before merging.\n`);
192
+ } else if (verdict.reason === LINT_REASON.FAIL_PR_FIELD_DRIFT) {
193
+ process.stderr.write(`\nERROR changeset-lint: ${verdict.reason}\n`);
194
+ process.stderr.write(`The following .changeset fragment(s) have a stale \`pr:\` field (DEFECT.CHANGESET-PR-FIELD-DRIFT):\n`);
195
+ for (const d of verdict.drift) {
196
+ process.stderr.write(` ${d.file}: pr: ${d.found}, expected pr: ${d.expected}\n`);
197
+ }
198
+ process.stderr.write(`Backfill \`pr:\` with this PR's real number (see .changeset/README.md), then push again.\n`);
137
199
  } else {
138
200
  process.stderr.write(`\nERROR changeset-lint: ${verdict.reason}\n`);
139
201
  process.stderr.write(`PR touches user-facing files but does not include a .changeset/*.md fragment.\n`);
@@ -145,4 +207,4 @@ function main() {
145
207
 
146
208
  if (require.main === module) runMain(main);
147
209
 
148
- module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment };
210
+ module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment, DEFAULT_BASE, findPrFieldDrift };
@@ -26,7 +26,11 @@ function serializeChangelog(ir) {
26
26
  lines.push(`### ${section.type}`);
27
27
  lines.push('');
28
28
  for (const b of section.bullets) {
29
- lines.push(`- ${b.body} (#${b.pr})`);
29
+ // #3001: indent continuation lines so parseChangelog's continuation-fold
30
+ // (/^\s+/) picks them up instead of terminating the bullet at the first
31
+ // blank line. A multi-paragraph body round-trips with content preserved.
32
+ const body = b.body.replace(/\n/g, '\n ');
33
+ lines.push(`- ${body} (#${b.pr})`);
30
34
  }
31
35
  lines.push('');
32
36
  }
@@ -4,9 +4,10 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
6
  const { ExitError, runMain } = require('./lib/cli-exit.cjs');
7
+ const { FAMILIES, ALIAS_TABLE } = require('./lib/alias-drift-families.cjs');
7
8
 
8
9
  const ROOT = path.resolve(__dirname, '..');
9
- const aliasesPath = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'command-aliases.cjs');
10
+ const aliasesPath = path.join(ROOT, 'gsd-core', 'bin', 'lib', `${ALIAS_TABLE}.cjs`);
10
11
 
11
12
  function fail(message) {
12
13
  process.stderr.write(`${message}\n`);
@@ -36,48 +37,11 @@ function main() {
36
37
 
37
38
  const aliases = require(aliasesPath);
38
39
 
39
- const families = [
40
- {
41
- commandAliases: 'STATE_COMMAND_ALIASES',
42
- subcommands: 'STATE_SUBCOMMANDS',
43
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'state-command-router.cjs'),
44
- },
45
- {
46
- commandAliases: 'VERIFY_COMMAND_ALIASES',
47
- subcommands: 'VERIFY_SUBCOMMANDS',
48
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'verify-command-router.cjs'),
49
- },
50
- {
51
- commandAliases: 'INIT_COMMAND_ALIASES',
52
- subcommands: 'INIT_SUBCOMMANDS',
53
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'init-command-router.cjs'),
54
- },
55
- {
56
- commandAliases: 'PHASE_COMMAND_ALIASES',
57
- subcommands: 'PHASE_SUBCOMMANDS',
58
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phase-command-router.cjs'),
59
- },
60
- {
61
- commandAliases: 'PHASES_COMMAND_ALIASES',
62
- subcommands: 'PHASES_SUBCOMMANDS',
63
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phases-command-router.cjs'),
64
- },
65
- {
66
- commandAliases: 'VALIDATE_COMMAND_ALIASES',
67
- subcommands: 'VALIDATE_SUBCOMMANDS',
68
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'validate-command-router.cjs'),
69
- },
70
- {
71
- commandAliases: 'ROADMAP_COMMAND_ALIASES',
72
- subcommands: 'ROADMAP_SUBCOMMANDS',
73
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'roadmap-command-router.cjs'),
74
- },
75
- {
76
- commandAliases: 'EVAL_COMMAND_ALIASES',
77
- subcommands: 'EVAL_SUBCOMMANDS',
78
- routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'eval-command-router.cjs'),
79
- },
80
- ];
40
+ const families = FAMILIES.map((family) => ({
41
+ commandAliases: family.commandAliases,
42
+ subcommands: family.subcommands,
43
+ routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', `${family.router}.cjs`),
44
+ }));
81
45
 
82
46
  for (const family of families) {
83
47
  const commandAliases = aliases[family.commandAliases];
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-contract-drift.cjs
4
+ *
5
+ * Enforces that gsd-core/references/agent-contracts.md's `## Agent Registry`
6
+ * table stays in sync with reality:
7
+ *
8
+ * 1. The table itself must parse cleanly (no malformed rows).
9
+ * 2. Every agents/*.md file must have its fenced code blocks properly
10
+ * closed (an unclosed fence makes in-fence marker detection unreliable).
11
+ * 3. Every marker an agent actually emits in-fence, and every marker the
12
+ * registry declares for it, must agree (contractViolations' declared/
13
+ * emitted checks) -- unless the row opts out via `kind:
14
+ * artifact+query`/`structured-return`, in which case any emitted
15
+ * marker is itself a violation (vestigial_marker).
16
+ * 4. Every `sentinel-match` row's declared markers must have at least one
17
+ * exact-case consumer somewhere under gsd-core/workflows/, commands/,
18
+ * or agents/ (excluding the producing agent's own file).
19
+ * 5. Registry roster coverage: every agents/*.md file has exactly one
20
+ * row, every row names an agent file that exists
21
+ * (agent_without_contract / duplicate_registry_row / unknown_producer),
22
+ * and every file-shaped `Consumed by` entry resolves (unknown_consumer).
23
+ * 6. Read-tag arm: no `<files_to_read>` survives anywhere in the consumer
24
+ * corpus (legacy_read_tag), and whenever a declared consumer emits
25
+ * `<required_reading>` the producing agent's file must reference the
26
+ * gate (read_tag_gate_missing).
27
+ * 7. Reverse direction: a workflow/command matching a quoted `## TOKEN`
28
+ * no agent declares or emits is dispatch-on-phantom
29
+ * (unmatched_consumer_token) — F9's shape from the consumer side.
30
+ *
31
+ * Exit 0 = clean. Exit 1 = violations (with diagnostics on stderr).
32
+ */
33
+
34
+ 'use strict';
35
+
36
+ const fs = require('fs');
37
+ const path = require('path');
38
+
39
+ function resolveRoot(argv) {
40
+ const idx = argv.indexOf('--root');
41
+ if (idx === -1) return path.join(__dirname, '..');
42
+ const value = argv[idx + 1];
43
+ if (!value) {
44
+ throw new Error('check-contract-drift: --root requires a directory argument');
45
+ }
46
+ return path.resolve(value);
47
+ }
48
+
49
+ const ROOT = resolveRoot(process.argv.slice(2));
50
+ const CONTRACTS_FILE = path.join(ROOT, 'gsd-core', 'references', 'agent-contracts.md');
51
+ const AGENTS_DIR = path.join(ROOT, 'agents');
52
+ const WORKFLOWS_DIR = path.join(ROOT, 'gsd-core', 'workflows');
53
+ const COMMANDS_DIR = path.join(ROOT, 'commands');
54
+
55
+ const {
56
+ extractMarkers,
57
+ parseAgentContracts,
58
+ contractViolations,
59
+ readTagViolations,
60
+ parseConsumedByCell,
61
+ unmatchedConsumerTokens,
62
+ sanitizeEcho,
63
+ REMEDIES,
64
+ } = require('./command-contract-helpers.cjs');
65
+
66
+ const { runMain } = require('./lib/cli-exit.cjs');
67
+
68
+ // ─── helpers ────────────────────────────────────────────────────────────────
69
+
70
+ function walkMarkdownFiles(dir, acc) {
71
+ let entries;
72
+ try {
73
+ entries = fs.readdirSync(dir, { withFileTypes: true });
74
+ } catch {
75
+ return acc;
76
+ }
77
+ for (const entry of entries) {
78
+ const full = path.join(dir, entry.name);
79
+ if (entry.isDirectory()) {
80
+ walkMarkdownFiles(full, acc);
81
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
82
+ acc.push(full);
83
+ }
84
+ }
85
+ return acc;
86
+ }
87
+
88
+ function toRepoRelative(absPath) {
89
+ return path.relative(ROOT, absPath).split(path.sep).join('/');
90
+ }
91
+
92
+ /**
93
+ * referenceIncludes(content)
94
+ *
95
+ * Plain scan for `@~/.claude/gsd-core/references/*.md` tokens anywhere in an
96
+ * agent file's content -- inside an `<execution_context>` block (already
97
+ * covered structurally by `executionContextRefs` in command-contract-helpers,
98
+ * but a raw regex over the whole string picks those up too) and, just as
99
+ * importantly, OUTSIDE one: agents frequently point at a reference doc from
100
+ * plain prose (e.g. "See @~/.claude/gsd-core/references/planner-guidance.md
101
+ * for ...") rather than from the eager `<execution_context>` include list.
102
+ * An agent's completion-marker contract can be authored in such a reference
103
+ * file rather than the agent file itself -- gsd-planner declares
104
+ * `PLANNING COMPLETE` in its registry row, but the example heading itself
105
+ * lives in `gsd-core/references/planner-guidance.md`, which the agent only
106
+ * `@`-includes -- so the producer scan below must follow these includes to
107
+ * see markers an agent's contract legitimately delegates to a reference doc.
108
+ * Returns ROOT-relative paths (`gsd-core/references/foo.md`), de-duplicated.
109
+ */
110
+ function referenceIncludes(content) {
111
+ const seen = new Set();
112
+ const re = /@~\/\.claude\/gsd-core\/references\/[A-Za-z0-9._-]+\.md/g;
113
+ let m;
114
+ while ((m = re.exec(content)) !== null) {
115
+ const relPath = 'gsd-core/references/' + m[0].slice('@~/.claude/gsd-core/references/'.length);
116
+ seen.add(relPath);
117
+ }
118
+ return [...seen];
119
+ }
120
+
121
+ function remedyFor(kind) {
122
+ return REMEDIES[kind] || 'review the registry row and agent file for drift';
123
+ }
124
+
125
+ // ─── run ─────────────────────────────────────────────────────────────────────
126
+
127
+ function main() {
128
+ if (!fs.existsSync(CONTRACTS_FILE)) {
129
+ process.stderr.write(`\nERROR check-contract-drift: contracts file not found at ${toRepoRelative(CONTRACTS_FILE)}\n\n`);
130
+ return 1;
131
+ }
132
+
133
+ const contractsMd = fs.readFileSync(CONTRACTS_FILE, 'utf-8');
134
+ const { rows: registry, errors: parseErrors } = parseAgentContracts(contractsMd);
135
+
136
+ // knownMarkers: every marker string declared anywhere in the registry --
137
+ // extractMarkers only ever resolves a heading against this vocabulary, it
138
+ // never falls back to guessing a shape. Includes `(unconsumed: …)` entries
139
+ // so their declared↔emitted agreement is checked too.
140
+ const knownMarkers = new Set();
141
+ for (const row of registry) {
142
+ for (const m of row.completion_markers || []) knownMarkers.add(m);
143
+ for (const m of row.unconsumed_markers || []) knownMarkers.add(m);
144
+ }
145
+
146
+ // producerMarkers: agent -> [in-fence marker strings that matched knownMarkers]
147
+ // candidateMarkers: agent -> [in-fence marker-shaped headings NOT in knownMarkers,
148
+ // deduped per marker — "emitted but undeclared" is a fact about the
149
+ // marker, not about each line or file it appears in]
150
+ // agentTexts: agent -> file content CONCATENATED with every
151
+ // references/*.md file the agent @-includes, for the read-tag arm's gate
152
+ // check. The fold is load-bearing: planner/executor/phase-researcher
153
+ // deliver the MUST-Read gate via the shared mandatory-initial-read.md
154
+ // include, so the agent file alone would report a gate that actually
155
+ // arrives (the same producer-scope gap referenceIncludes() fixes for
156
+ // markers, one layer up).
157
+ const producerMarkers = new Map();
158
+ const candidateMarkers = new Map();
159
+ const agentTexts = new Map();
160
+ const unclosedFenceViolations = [];
161
+
162
+ const agentFiles = fs.existsSync(AGENTS_DIR)
163
+ ? fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md'))
164
+ : [];
165
+
166
+ for (const file of agentFiles) {
167
+ const agent = file.replace(/\.md$/, '');
168
+ const abs = path.join(AGENTS_DIR, file);
169
+ const content = fs.readFileSync(abs, 'utf-8');
170
+ // Single pass over the agent's @-included references: each file is read
171
+ // once and feeds BOTH the read-tag fold (agentTexts) and marker
172
+ // extraction (producer/candidate attribution).
173
+ const includeTexts = [];
174
+ for (const refRelPath of referenceIncludes(content)) {
175
+ try {
176
+ includeTexts.push(fs.readFileSync(path.join(ROOT, refRelPath), 'utf-8'));
177
+ } catch {
178
+ // include miss — lint-command-contract rule 4 owns @-ref existence
179
+ }
180
+ }
181
+ agentTexts.set(agent, [content, ...includeTexts].join('\n'));
182
+ const { markers, candidates, unclosedFence } = extractMarkers(agentTexts.get(agent), knownMarkers);
183
+ const inFenceMarkers = markers.filter(m => m.inFence).map(m => m.marker);
184
+ const inFenceCandidates = candidates.filter(m => m.inFence).map(m => m.marker);
185
+
186
+ producerMarkers.set(agent, inFenceMarkers);
187
+ candidateMarkers.set(agent, [...new Set(inFenceCandidates)]);
188
+
189
+ if (unclosedFence) {
190
+ unclosedFenceViolations.push({
191
+ kind: 'unclosed_fence',
192
+ agent,
193
+ marker: null,
194
+ detail: `${toRepoRelative(abs)} has an unterminated code fence`,
195
+ });
196
+ }
197
+ }
198
+
199
+ // consumerTexts: every *.md under gsd-core/workflows/, commands/, agents/
200
+ // — plus every file-shaped `Consumed by` entry that resolves on disk, so
201
+ // a row citing a reference doc or an ADR (e.g. intel-updater's
202
+ // docs/adr/22-plan-drift-guard.md) is validated against the real file and
203
+ // its text participates in consumer matching, not just workflows.
204
+ const consumerTexts = new Map();
205
+ const consumerDirs = [WORKFLOWS_DIR, COMMANDS_DIR, AGENTS_DIR];
206
+ for (const dir of consumerDirs) {
207
+ for (const abs of walkMarkdownFiles(dir, [])) {
208
+ consumerTexts.set(toRepoRelative(abs), fs.readFileSync(abs, 'utf-8'));
209
+ }
210
+ }
211
+ for (const row of registry) {
212
+ for (const rel of parseConsumedByCell(row.consumed_by)) {
213
+ if (consumerTexts.has(rel)) continue;
214
+ const abs = path.join(ROOT, rel);
215
+ try {
216
+ consumerTexts.set(rel, fs.readFileSync(abs, 'utf-8'));
217
+ } catch {
218
+ // absent — contractViolations reports it as unknown_consumer
219
+ }
220
+ }
221
+ }
222
+
223
+ const contractViolationsList = contractViolations({ registry, producerMarkers, candidateMarkers, consumerTexts });
224
+ const readTagViolationsList = readTagViolations({ registry, agentTexts, consumerTexts });
225
+ const reverseViolationsList = unmatchedConsumerTokens({ consumerTexts, vocabulary: knownMarkers });
226
+
227
+ const parseViolations = parseErrors.map(e => ({
228
+ kind: 'parse_error',
229
+ agent: null,
230
+ marker: null,
231
+ detail: `agent-contracts.md:${e.line} — ${e.reason}`,
232
+ }));
233
+
234
+ const allViolations = [
235
+ ...parseViolations,
236
+ ...unclosedFenceViolations,
237
+ ...contractViolationsList,
238
+ ...readTagViolationsList,
239
+ ...reverseViolationsList,
240
+ ];
241
+
242
+ const agentCount = registry.length;
243
+ const markerCount = registry.reduce((n, r) => n + (r.completion_markers || []).length, 0);
244
+
245
+ // --json: the typed surface tests consume (CONTRIBUTING "Raw Text
246
+ // Matching" rule — the human formatter below is for operators only).
247
+ if (process.argv.includes('--json')) {
248
+ console.log(
249
+ JSON.stringify({
250
+ check: 'check-contract-drift',
251
+ status: allViolations.length === 0 ? 'ok' : 'violations',
252
+ agents: agentCount,
253
+ markers: markerCount,
254
+ violations: allViolations.map((v) => ({
255
+ kind: v.kind,
256
+ agent: v.agent ?? null,
257
+ marker: v.marker ?? null,
258
+ detail: sanitizeEcho(v.detail),
259
+ })),
260
+ }),
261
+ );
262
+ return allViolations.length === 0 ? 0 : 1;
263
+ }
264
+
265
+ if (allViolations.length === 0) {
266
+ console.log(`ok check-contract-drift: ${agentCount} agents, ${markerCount} markers, 0 violations`);
267
+ return 0;
268
+ }
269
+
270
+ // group by violation kind
271
+ const byKind = new Map();
272
+ for (const v of allViolations) {
273
+ if (!byKind.has(v.kind)) byKind.set(v.kind, []);
274
+ byKind.get(v.kind).push(v);
275
+ }
276
+
277
+ process.stderr.write(
278
+ `\nERROR check-contract-drift: ${allViolations.length} violation(s) across ${byKind.size} kind(s)\n\n`,
279
+ );
280
+
281
+ for (const [kind, violations] of byKind) {
282
+ process.stderr.write(` ${kind} (${violations.length}):\n`);
283
+ for (const v of violations) {
284
+ const agentLabel = v.agent ? sanitizeEcho(v.agent) : '(registry)';
285
+ const markerLabel = v.marker ? ` marker "${sanitizeEcho(v.marker)}"` : '';
286
+ process.stderr.write(` - ${agentLabel}${markerLabel}: ${sanitizeEcho(v.detail)}\n`);
287
+ process.stderr.write(` remedy: ${remedyFor(kind)}\n`);
288
+ }
289
+ process.stderr.write('\n');
290
+ }
291
+
292
+ process.stderr.write('See gsd-core/references/agent-contracts.md for the registry contract spec.\n\n');
293
+
294
+ return 1;
295
+ }
296
+
297
+ runMain(main);
@@ -300,6 +300,23 @@ const RULES = [
300
300
  'tests/require-fs-op-fallback.rule.test.cjs',
301
301
  ],
302
302
  },
303
+ {
304
+ // ADR-3212 Phase 4 (#3415): no-unbounded-quantifier and the shared
305
+ // readfilesync-trace helper it uses (also now imported by
306
+ // no-crlf-fragile-split). NOT part of the ADR-1703 portability family above
307
+ // — kept as its own bucket so this rule's tests re-run without pulling in
308
+ // the ADR-1703 disable-ban / vocab-drift suites it is not governed by.
309
+ name: 'no-unbounded-quantifier + readfilesync-trace (ADR-3212 Phase 4)',
310
+ match: path => [
311
+ 'eslint-rules/no-unbounded-quantifier.cjs',
312
+ 'eslint-rules/lib/readfilesync-trace.cjs',
313
+ 'eslint-rules/no-crlf-fragile-split.cjs',
314
+ ].includes(path),
315
+ tests: [
316
+ 'tests/no-unbounded-quantifier.rule.test.cjs',
317
+ 'tests/readfilesync-trace-parity.test.cjs',
318
+ ],
319
+ },
303
320
  ];
304
321
 
305
322
  /**
@@ -474,8 +491,8 @@ function classify(files) {
474
491
  // the scoped windows lane instead of triggering the three full parity
475
492
  // lanes. (full_matrix fired on 15/15 sampled PRs because test-driven
476
493
  // PRs always touch tests/, costing ~25 runner-minutes each.) Changed
477
- // tests already run on ubuntu-22 and ubuntu-24 via targeted_tests; the
478
- // residual macOS / windows-node-22 cross-product is covered by the full
494
+ // tests already run on the two ubuntu-24 lanes via targeted_tests; the
495
+ // residual macOS / windows cross-product is covered by the full
479
496
  // matrix on every push to next.
480
497
  windows.add(file);
481
498
  }