@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
@@ -210,10 +210,35 @@ const SHA512_INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]{86}==$/;
210
210
  // hard validation error — fail closed so the capability install/load is rejected loudly.
211
211
  const SAFE_HOOK_SCRIPT_RE = /^[A-Za-z0-9._/-]+$/;
212
212
 
213
+ // #3631 (defense-in-depth, mirrors capability-lifecycle.cts — KEEP BOTH IN SYNC): a declared script
214
+ // path must not point into the space bundleContentHash (capability-consent.cts) excludes from the
215
+ // consent-binding digest. A file whose basename ends `.pyc`/`.pyo` can contain perfectly valid
216
+ // JavaScript and would be executed by `node` regardless of extension, and a `__pycache__`/
217
+ // `.pytest_cache` segment marks a directory whose digest marker is suppressed — so a MANIFEST-DECLARED
218
+ // executable surface must never be able to reach either, or the exclusion becomes reachable from a
219
+ // path an attacker fully controls at declare-time rather than only via post-consent tamper.
220
+ // Regex asymmetry is DELIBERATE, KEEP BOTH RULES IN SYNC WITH capability-lifecycle.cts (byte-identical
221
+ // text, verified by the isSafeHookScriptPath parity test in tests/capability-registry.test.cjs):
222
+ // (i) PYCACHE_SUFFIX_RE is case-INSENSITIVE (`/i`) on purpose — a validator should be STRICTER than
223
+ // the digest it defends, so it rejects `x.PYC` too even though bundleContentHash's own suffix
224
+ // match (hasPycacheFileSuffix, capability-consent.cts) is byte-exact and would still hash it.
225
+ // (ii) The __pycache__/.pytest_cache SEGMENT match is case-SENSITIVE to match the digest's own
226
+ // byte-exact, case-sensitive directory-basename comparison (CPython always writes a lowercase
227
+ // `__pycache__`) — a validator segment match looser than the digest here would reject paths the
228
+ // digest would still hash, which is over-strict in the wrong direction for a defense-in-depth
229
+ // check layered on top of an already-correct digest.
230
+ // (iii) The `[/\\]` backslash alternations in both regexes are defensive/UNREACHABLE in practice:
231
+ // SAFE_HOOK_SCRIPT_RE (above) already rejects any backslash character outright, so a script
232
+ // string containing `\` never reaches either PYCACHE_*_RE check.
233
+ const PYCACHE_SEGMENT_RE = /(?:^|[/\\])(__pycache__|\.pytest_cache)(?:[/\\]|$)/;
234
+ const PYCACHE_SUFFIX_RE = /\.(pyc|pyo)$/i;
235
+
213
236
  /**
214
237
  * #1460 (R): true when a relative hook-script path is shell-safe (see SAFE_HOOK_SCRIPT_RE).
215
238
  * Rejects absolute paths, `..` segments, a leading `-` on any path segment, and any char
216
- * outside the allowlist (whitespace / shell metacharacters / control / NUL).
239
+ * outside the allowlist (whitespace / shell metacharacters / control / NUL). #3631: also rejects a
240
+ * path with a `__pycache__`/`.pytest_cache` segment or a `.pyc`/`.pyo` basename suffix (defense in
241
+ * depth — keeps a declared executable surface out of the digest-excluded space).
217
242
  */
218
243
  function isSafeHookScriptPath(script) {
219
244
  if (typeof script !== 'string' || script.length === 0) return false;
@@ -225,6 +250,8 @@ function isSafeHookScriptPath(script) {
225
250
  for (const seg of segments) {
226
251
  if (seg.startsWith('-')) return false;
227
252
  }
253
+ if (PYCACHE_SEGMENT_RE.test(script)) return false;
254
+ if (PYCACHE_SUFFIX_RE.test(path.basename(script))) return false;
228
255
  return true;
229
256
  }
230
257
 
@@ -707,7 +734,6 @@ const VALID_CONVERTER_NAMES = new Set([
707
734
  'convertClaudeCommandToCodebuddySkill',
708
735
  'convertClaudeCommandToCodexSkill',
709
736
  'convertClaudeCommandToCopilotSkill',
710
- 'convertClaudeCommandToCursorCommand',
711
737
  'convertClaudeCommandToCursorSkill',
712
738
  'convertClaudeCommandToKiloSkill',
713
739
  'convertClaudeCommandToKimiSkill',
@@ -728,6 +754,16 @@ const VALID_CONVERTER_NAMES = new Set([
728
754
  'convertClaudeAgentToCodexAgent',
729
755
  // ADR-1239 / #2092 Phase B Upgrade 1 — native .qwen/agents/*.md subagent projection.
730
756
  'convertClaudeAgentToQwenAgent',
757
+ // #3384 — ZCode agents are Claude-shaped but its dispatcher treats mcp__* tools
758
+ // grants as required MCP servers; this converter strips them at install time.
759
+ 'convertClaudeAgentToZcodeAgent',
760
+ // #2875 Part 2 (the agents-bypass closure) — data-driven Hermes branding
761
+ // converter (reads hostBehaviors.brandingRewrites rather than a hardcode),
762
+ // and the kilo/opencode agent converters (shared with those runtimes'
763
+ // commands-kind entries, options-bag signature `(content, {isAgent, modelOverride})`).
764
+ 'convertClaudeAgentToHermesAgent',
765
+ 'convertClaudeToKiloFrontmatter',
766
+ 'convertClaudeToOpencodeFrontmatter',
731
767
  ]);
732
768
 
733
769
  // C3: Validate role:runtime body
@@ -746,6 +782,23 @@ const VALID_EXTENSION_EVENTS = new Set(['opencode', 'pi', 'hermes', 'kilo', 'non
746
782
  const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
747
783
  const VALID_ARTIFACT_KIND_NAMES = new Set(['commands', 'agents', 'skills', 'kimi-agents']);
748
784
  const VALID_ARTIFACT_NESTINGS = new Set(['flat', 'nested']);
785
+ // #2871 Phase 2 — only `commands` and `skills` are trigger-bearing (a `/gsd-<name>`
786
+ // the USER types). `agents` and `kimi-agents` are a separate dispatch interface
787
+ // point (subagent invocation via `subagent_type`/named dispatch), never a trigger —
788
+ // see 40-design.md's "agents are not trigger-bearing" correction. A narrower set
789
+ // than VALID_ARTIFACT_KIND_NAMES on purpose: an artifact KIND can be agents; a
790
+ // trigger-precedence MEMBER never can.
791
+ const VALID_TRIGGER_PRECEDENCE_KINDS = new Set(['commands', 'skills']);
792
+ // The default runtime.triggerPrecedence (highest priority first) applied when a
793
+ // descriptor omits the axis (see validateRuntimeBody's required-with-default
794
+ // handling below). Not invented: `['skills', 'commands']` is the ordering every
795
+ // in-tree runtime that emits both kinds (from the same trigger stems) wants —
796
+ // claude's local/global collision and the same-scope collision (codebuddy, kilo,
797
+ // opencode, zcode) all resolve to skills winning. claude's shipped descriptor
798
+ // declares this SAME value explicitly, and
799
+ // tests/runtime-artifact-layout-trigger-surface.test.cjs asserts the two agree
800
+ // (a parity assertion — two surfaces reading one rule must not silently drift).
801
+ const DEFAULT_TRIGGER_PRECEDENCE = Object.freeze(['skills', 'commands']);
749
802
  const FEATURE_FIELDS_FORBIDDEN_ON_RUNTIME = ['skills', 'agents', 'steps', 'contributions', 'gates', 'hooks', 'activationKey'];
750
803
  // 'none' added #2103 — Marketplace/VSIX-distributed hosts (e.g. VS Code) that
751
804
  // are never CLI-installed (no allRuntimes membership, no install flag).
@@ -852,7 +905,36 @@ const VALID_LANE_HANDLERS = new Set(['antigravity', 'openai-compatible', 'op
852
905
  // BOTH sub-shapes, or from NEITHER, has undefined meaning and fails validation.
853
906
  // The discriminator is explicit rather than inferred from field presence, which
854
907
  // is precisely the ambiguity these two sets exist to detect.
855
- const SPAWN_ONLY_INVOKE_FIELDS = ['binary', 'args', 'promptChannel', 'outputChannel', 'outputArg', 'modelArg'];
908
+ // `env` added by #2483. It belongs in THIS set and not merely in validateSpawnInvoke: an environment
909
+ // pair has no meaning for a transport that issues an HTTP POST, so a manifest declaring it alongside
910
+ // `openai-http` fields is the undefined-meaning case the comment above describes. Registering it here
911
+ // is what makes that case reportable — the openai-http arm rejects exactly the members of this list,
912
+ // so a field absent from it is silently accepted on the wrong transport. Note `effortChannel` is
913
+ // deliberately in NEITHER set: D2 defines it for both transports, so it is shared, not spawn-only.
914
+ const SPAWN_ONLY_INVOKE_FIELDS = ['binary', 'args', 'promptChannel', 'outputChannel', 'outputArg', 'modelArg', 'env'];
915
+ // Environment names refused outright on a reviewer lane (#2483): each one turns a declared pair into
916
+ // arbitrary code execution in the spawned child. DEFENCE IN DEPTH, NOT THE BOUNDARY — say so plainly,
917
+ // because a future reader who mistakes this for the control will under-invest in the one that is.
918
+ // The boundary is install-time consent: `capability-trust` discloses every declared env pair, shows
919
+ // its value, and binds it to the consent signature, so an unlisted name is still SEEN before it runs.
920
+ //
921
+ // Deliberately incomplete, and it cannot be otherwise: the spawned binary is arbitrary third-party
922
+ // code, so the exhaustive set is every interpreter's injection variables. What this list buys is that
923
+ // the highest-confidence, lowest-legitimacy routes cannot be taken quietly. `PATH` is included — it is
924
+ // the most complete primitive of the set (repoint it at a directory holding a fake binary) and no
925
+ // shipped reviewer manifest declares it; a lane that needs a specific executable declares an absolute
926
+ // `invoke.binary` rather than reshaping the child's `PATH`.
927
+ //
928
+ // Matched CASE-INSENSITIVELY (members stored uppercase) — Windows environment lookup is
929
+ // case-insensitive, so an exact-case set is bypassed by declaring `Path` or `node_options`.
930
+ const DENIED_LANE_ENV_KEYS = new Set([
931
+ 'PATH', 'NODE_OPTIONS', 'NODE_REPL_EXTERNAL_MODULE', 'NODE_PATH',
932
+ 'LD_PRELOAD', 'LD_AUDIT', 'LD_LIBRARY_PATH',
933
+ 'DYLD_INSERT_LIBRARIES', 'DYLD_LIBRARY_PATH',
934
+ 'PYTHONSTARTUP', 'PYTHONPATH', 'BASH_ENV', 'ENV',
935
+ 'PERL5OPT', 'RUBYOPT', 'JAVA_TOOL_OPTIONS', '_JAVA_OPTIONS', 'CLASSPATH',
936
+ 'GIT_SSH_COMMAND', 'GIT_EXTERNAL_DIFF',
937
+ ]);
856
938
  // `defaultHost` / `fallbackModel` added by Phase 5b (#2799). Phase 4 federated every
857
939
  // `review.*_host` key with a default of `""`, so the REAL fallback destination and model
858
940
  // (`http://localhost:11434` / `llama3` and friends) existed only inside the bash leg. Once the
@@ -1075,7 +1157,18 @@ function validateArtifactLayout(capId, layout) {
1075
1157
  errors.push(ctx + '.' + scope + ' must be an array');
1076
1158
  } else {
1077
1159
  for (let i = 0; i < arr.length; i++) {
1078
- errors.push(...validateArtifactKindEntry(capId, arr[i], 'artifactLayout.' + scope + '[' + i + ']'));
1160
+ const entry = arr[i];
1161
+ errors.push(...validateArtifactKindEntry(capId, entry, 'artifactLayout.' + scope + '[' + i + ']'));
1162
+ if (
1163
+ scope === 'local' &&
1164
+ typeof entry === 'object' && entry !== null && !Array.isArray(entry) &&
1165
+ Object.prototype.hasOwnProperty.call(entry, 'home')
1166
+ ) {
1167
+ errors.push(
1168
+ ctx + '.local[' + i + '].home is not allowed; ' +
1169
+ 'local artifact layout entries must remain project-scoped',
1170
+ );
1171
+ }
1079
1172
  }
1080
1173
  }
1081
1174
  }
@@ -1547,6 +1640,45 @@ function validateRuntimeBody(cap) {
1547
1640
  }
1548
1641
  }
1549
1642
 
1643
+ // triggerPrecedence — #2871 Phase 2 amendment to ADR-1016's runtime body.
1644
+ // REQUIRED-WITH-DEFAULT: no other axis in this validator uses this shape —
1645
+ // every axis above either hard-requires the field (throws when absent) or
1646
+ // treats absence as fully unconstrained (effortSurface/isolation: "nothing to
1647
+ // validate" when undefined). This axis does neither: an ABSENT value is
1648
+ // substituted with DEFAULT_TRIGGER_PRECEDENCE and then validated exactly as
1649
+ // if it HAD been supplied, so a third-party capability.json authored before
1650
+ // this phase keeps validating (ADR-894 additive-only / Hyrum's Law, ADR-1244)
1651
+ // while a PRESENT-but-malformed value still fails loudly instead of silently
1652
+ // passing through unchecked.
1653
+ const triggerPrecedenceValue = Object.prototype.hasOwnProperty.call(r, 'triggerPrecedence')
1654
+ ? r.triggerPrecedence
1655
+ : DEFAULT_TRIGGER_PRECEDENCE;
1656
+ if (!Array.isArray(triggerPrecedenceValue)) {
1657
+ errors.push(
1658
+ 'runtime.triggerPrecedence must be an array of trigger-bearing kind names (' +
1659
+ [...VALID_TRIGGER_PRECEDENCE_KINDS].join(', ') + ') (got: ' + JSON.stringify(triggerPrecedenceValue) + ')',
1660
+ );
1661
+ } else if (triggerPrecedenceValue.length === 0) {
1662
+ errors.push('runtime.triggerPrecedence must not be empty');
1663
+ } else {
1664
+ const seenKinds = new Set();
1665
+ for (let i = 0; i < triggerPrecedenceValue.length; i++) {
1666
+ const k = triggerPrecedenceValue[i];
1667
+ if (k === '__proto__' || k === 'constructor' || k === 'prototype') {
1668
+ errors.push('runtime.triggerPrecedence[' + i + '] "' + k + '" is a reserved name');
1669
+ } else if (typeof k !== 'string' || !VALID_TRIGGER_PRECEDENCE_KINDS.has(k)) {
1670
+ errors.push(
1671
+ 'runtime.triggerPrecedence[' + i + '] must be one of: ' + [...VALID_TRIGGER_PRECEDENCE_KINDS].join(', ') +
1672
+ ' (got: ' + JSON.stringify(k) + ')',
1673
+ );
1674
+ } else if (seenKinds.has(k)) {
1675
+ errors.push('runtime.triggerPrecedence contains a duplicate kind: ' + JSON.stringify(k));
1676
+ } else {
1677
+ seenKinds.add(k);
1678
+ }
1679
+ }
1680
+ }
1681
+
1550
1682
  return errors;
1551
1683
  }
1552
1684
 
@@ -1568,6 +1700,122 @@ const KNOWN_REVIEWER_FIELDS = new Set([
1568
1700
  'handler',
1569
1701
  ]);
1570
1702
 
1703
+ /**
1704
+ * The closed `runtime.hostBehaviors` vocabulary (ADR-1016, closed via #2801).
1705
+ *
1706
+ * ADR-1016's core principle is that every per-runtime difference is a value over
1707
+ * a closed primitive vocabulary, and that a host needing a new shape gets a
1708
+ * reviewed first-party primitive rather than "an open escape hatch in the
1709
+ * descriptor" (§Alternatives #2 rejects exactly that). `hostBehaviors` was the
1710
+ * one hole left in that closure: 59 keys across 18 manifests, 39 of them set by
1711
+ * a single capability, validated by nothing. It was described in the reference
1712
+ * docs as a deliberate open seam sanctioned by ADR-1016 — ADR-1016 does not
1713
+ * mention `hostBehaviors` at all, and its stated principle is the opposite.
1714
+ *
1715
+ * Adding a key here is deliberate and reviewed. That friction IS the decision
1716
+ * (ADR-1016 §Consequences: "the closed vocabulary must grow (reviewed) when a
1717
+ * genuinely new host shape appears — intentional friction, the trust boundary").
1718
+ *
1719
+ * WARNING, never error. Two reasons:
1720
+ * 1. It matches the ADR-2782 D4.3 treatment of an unknown `reviewer` field, so
1721
+ * a manifest built against a newer GSD degrades visibly instead of failing
1722
+ * the build of a repo that merely reads it.
1723
+ * 2. An error would hard-break an out-of-tree descriptor carrying a bespoke
1724
+ * key, with no deprecation window — the exact mistake #2801's own alias
1725
+ * removal spent a full release avoiding. Escalating to an error is a later
1726
+ * step and needs its own window.
1727
+ *
1728
+ * Kept in sorted order, and `tests/reviewer-manifest-body.test.cjs` asserts this
1729
+ * set equals the keys the shipped manifests actually declare, so the list cannot
1730
+ * silently rot away from reality (DEFECT.GENERATIVE-FIX).
1731
+ */
1732
+ const KNOWN_HOST_BEHAVIORS = new Set([
1733
+ 'agentFileExtension',
1734
+ 'agentFrontmatterExtensions',
1735
+ 'agentManifestStyle',
1736
+ 'agentTomlFiles',
1737
+ 'attributionConfigResolver',
1738
+ 'attributionSource',
1739
+ 'authorsCanonicalWorkflow',
1740
+ 'brandingRewrites',
1741
+ 'cleanupSkillSidecars',
1742
+ 'clineRulesSurface',
1743
+ 'combinedFamilyInstall',
1744
+ 'commandBodyConverter',
1745
+ 'doneBannerStyle',
1746
+ 'flatCommandDir',
1747
+ 'frontmatterDialect',
1748
+ 'globalDirResolver',
1749
+ 'hookPathStyle',
1750
+ 'hooksJsonSurface',
1751
+ 'hyphenNameAgentBody',
1752
+ 'installsCommandBodiesForWorkflowDelegation',
1753
+ 'legacyCommandsGsdCleanup',
1754
+ 'legacyCommandsGsdInstallMigration',
1755
+ 'legacyCommandsGsdUninstall',
1756
+ 'legacyDevinSkillsCleanup',
1757
+ 'localCommandsViaRules',
1758
+ 'localInstallDeferred',
1759
+ 'localInstallStyle',
1760
+ 'localTargetIsProjectRoot',
1761
+ 'managedHookEvents',
1762
+ 'mcpCompanion',
1763
+ 'namedSubagentsSupported',
1764
+ 'nativeModelAliases',
1765
+ 'nativePlugin',
1766
+ 'noPathRewrite',
1767
+ 'ownsClaudePaths',
1768
+ 'permissionsSchema',
1769
+ 'pluginOnlyInstall',
1770
+ 'projectInstructionFile',
1771
+ 'reapplyCommand',
1772
+ 'reportCommandsDir',
1773
+ 'reportSkillsCount',
1774
+ 'retiredArtifacts',
1775
+ 'settingsFileByScope',
1776
+ 'sharedHooksDirName',
1777
+ 'skillFrontmatterVersion',
1778
+ 'skillPriorityFrontmatter',
1779
+ 'skillsGlobalOnboarding',
1780
+ 'skillsManifestPrefix',
1781
+ 'skipCodexSkillsManifest',
1782
+ 'skipHomePrefixSubstitution',
1783
+ 'skipSettingsUi',
1784
+ 'skipSharedHooksInstall',
1785
+ 'skipUpdateBannerCommand',
1786
+ 'soloStageMetadata',
1787
+ 'sourceMarkerFile',
1788
+ 'tomlConfigInstall',
1789
+ 'trackCategoryDescription',
1790
+ 'verificationStyle',
1791
+ 'writeCategoryDescription',
1792
+ ]);
1793
+
1794
+ /**
1795
+ * Frozen reason codes for the non-fatal reviewer diagnostics (ADR-2782 D4.3).
1796
+ *
1797
+ * The IR behind `collectReviewerWarnings`' rendered strings. Tests assert on
1798
+ * these codes; the rendered `message` is operator console output and tests must
1799
+ * not depend on it (CONTRIBUTING.md, "Prohibited: Raw Text Matching on Test
1800
+ * Outputs"), which is the same split `bin/verify-reapply-patches.cjs` uses.
1801
+ *
1802
+ * Adding a code is THREE coordinated changes: this enum, the emitting site in
1803
+ * `collectReviewerWarningRecordFields`, and the test that locks
1804
+ * `Object.keys(REVIEWER_WARNING).sort()`. That coupling is the point — it stops
1805
+ * the code surface drifting from the test surface.
1806
+ */
1807
+ const REVIEWER_WARNING = Object.freeze({
1808
+ /** A key inside a `reviewer` body that this GSD version does not know. */
1809
+ UNKNOWN_REVIEWER_FIELD: 'unknown_reviewer_field',
1810
+ /** A `runtime.hostBehaviors` key that was removed from the vocabulary. */
1811
+ REMOVED_HOST_BEHAVIOR: 'removed_host_behavior',
1812
+ /** A `runtime.hostBehaviors` key outside the closed vocabulary. */
1813
+ UNKNOWN_HOST_BEHAVIOR: 'unknown_host_behavior',
1814
+ });
1815
+
1816
+ /** Dotted path of the field removed by ADR-2782 D9 / #2801. */
1817
+ const REMOVED_REVIEWER_CLI_FIELD = 'runtime.hostBehaviors.reviewerCli';
1818
+
1571
1819
  const KNOWN_PROBE_FIELDS = new Set(['kind', 'binary', 'needle', 'timeoutMs', 'hostConfigKey', 'path']);
1572
1820
 
1573
1821
  /** A bounded probe timeout must be a finite, positive INTEGER of milliseconds. */
@@ -1605,6 +1853,41 @@ function describeValue(v) {
1605
1853
  }
1606
1854
  }
1607
1855
 
1856
+ /**
1857
+ * Ceiling on how many undeclared-key diagnostics one capability may produce.
1858
+ *
1859
+ * The loops below iterate MANIFEST-SUPPLIED keys, and an installed third-party
1860
+ * manifest is bounded only by MANIFEST_MAX_BYTES (8MB). Unbounded, one manifest
1861
+ * of 800k keys yields 800k records and ~139MB of message text, retained for the
1862
+ * registry's lifetime in OverlayMeta.diagnostics. Ten is enough to act on; the
1863
+ * rest are summarized. Mirrors the existing truncation idiom in
1864
+ * `capability-loader.cts` (`crossErrs.slice(0, 3)`).
1865
+ */
1866
+ const MAX_REPORTED_UNKNOWN_KEYS = 10;
1867
+
1868
+ /** Ceiling on how much of one manifest-supplied key name a diagnostic repeats. */
1869
+ const MAX_REPORTED_KEY_CHARS = 80;
1870
+
1871
+ /**
1872
+ * Render a manifest-supplied KEY for a diagnostic: control-safe and bounded.
1873
+ *
1874
+ * Key names carry no grammar anywhere — unlike `cap.id`, which `validateCapability`
1875
+ * gates on KEBAB_RE before these diagnostics run — so a key is fully
1876
+ * attacker-controlled text heading for stderr and OverlayMeta.warnings. C0/C1
1877
+ * controls (ESC, CR, LF) become U+FFFD so a key cannot emit terminal escapes or
1878
+ * forge a log line, and the result is clipped so one key cannot carry megabytes
1879
+ * into a retained diagnostic.
1880
+ *
1881
+ * @param {*} key
1882
+ * @returns {string}
1883
+ */
1884
+ function describeKey(key) {
1885
+ const raw = typeof key === 'string' ? key : String(key);
1886
+ // eslint-disable-next-line no-control-regex
1887
+ const safe = raw.replace(/[\x00-\x1f\x7f-\x9f]/g, '�');
1888
+ return safe.length > MAX_REPORTED_KEY_CHARS ? safe.slice(0, MAX_REPORTED_KEY_CHARS) + '…' : safe;
1889
+ }
1890
+
1608
1891
  /** Extract a message from an unknown thrown value without throwing again. */
1609
1892
  function safeErrorMessage(err) {
1610
1893
  try {
@@ -1651,7 +1934,39 @@ function validateEnumField(ctx, label, value, validSet) {
1651
1934
  }
1652
1935
 
1653
1936
  /**
1654
- * Collect NON-FATAL diagnostics for a reviewer body (ADR-2782 D4.3).
1937
+ * Collect NON-FATAL reviewer diagnostics for a capability manifest as TYPED
1938
+ * RECORDS (ADR-2782 D4.3, plus the D9 `hostBehaviors.reviewerCli` removal
1939
+ * notice, #2801).
1940
+ *
1941
+ * This is the IR. `collectReviewerWarnings` below renders it to strings for the
1942
+ * two production consumers; tests assert on these records instead of matching
1943
+ * the rendered prose (CONTRIBUTING.md, "Prohibited: Raw Text Matching on Test
1944
+ * Outputs").
1945
+ *
1946
+ * Record shape — `code` and `capId` are always present; the rest is per-code:
1947
+ * { code: REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD,
1948
+ * capId, field: 'reviewer.<key>', knownFields: string[], message }
1949
+ * { code: REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR,
1950
+ * capId, field: 'runtime.hostBehaviors.reviewerCli',
1951
+ * replacement: 'reviewer', docs: '<how-to path>', message }
1952
+ *
1953
+ * TOTAL: returns an array for ANY input and never throws. It runs on
1954
+ * loadRegistry's ACCEPT path, so a diagnostic that throws would cost the user a
1955
+ * lane that is otherwise perfectly valid (#1461 OVL-1).
1956
+ *
1957
+ * @param {object} cap A capability manifest.
1958
+ * @returns {Array<object>} Warning records; empty when there is nothing to say.
1959
+ */
1960
+ function collectReviewerWarningRecords(cap) {
1961
+ try {
1962
+ return collectReviewerWarningRecordFields(cap);
1963
+ } catch {
1964
+ return [];
1965
+ }
1966
+ }
1967
+
1968
+ /**
1969
+ * Render the IR for operator console output.
1655
1970
  *
1656
1971
  * Kept separate from validateReviewerBody so validateCapability's contract
1657
1972
  * (`=> string[]` of ERRORS) is unchanged for its two existing callers. The
@@ -1659,35 +1974,135 @@ function validateEnumField(ctx, label, value, validSet) {
1659
1974
  * through OverlayMeta.warnings. A warning written only to a build log nobody
1660
1975
  * reads is not a warning (ADR-2782 D4, "Where warnings surface").
1661
1976
  *
1977
+ * The `string[]` shape is load-bearing for both consumers and does not change.
1978
+ *
1662
1979
  * @param {object} cap A capability manifest.
1663
1980
  * @returns {string[]} Warning strings; empty when there is nothing to say.
1664
1981
  */
1665
1982
  function collectReviewerWarnings(cap) {
1666
- // Same totality contract as validateReviewerBody, for the same reason: this is
1667
- // called from loadRegistry's accept path, and a diagnostic that throws must
1668
- // never cost the user a working lane.
1669
- try {
1670
- return collectReviewerWarningFields(cap);
1671
- } catch {
1672
- return [];
1673
- }
1983
+ return collectReviewerWarningRecords(cap).map((record) => record.message);
1674
1984
  }
1675
1985
 
1676
- function collectReviewerWarningFields(cap) {
1677
- const warnings = [];
1678
- if (typeof cap !== 'object' || cap === null || Array.isArray(cap)) return warnings;
1679
- const r = cap.reviewer;
1680
- if (typeof r !== 'object' || r === null || Array.isArray(r)) return warnings;
1986
+ function collectReviewerWarningRecordFields(cap) {
1987
+ const records = [];
1988
+ if (typeof cap !== 'object' || cap === null || Array.isArray(cap)) return records;
1681
1989
 
1682
1990
  const capId = typeof cap.id === 'string' ? cap.id : '(unknown)';
1991
+
1992
+ // ADR-2782 D9 / #2801 — the REMOVED `runtime.hostBehaviors.reviewerCli` alias.
1993
+ //
1994
+ // Emitted BEFORE the `reviewer`-body early-return below, deliberately. The
1995
+ // manifest this notice exists for is the ALIAS-ONLY one, which by definition
1996
+ // carries no body; after that guard the check would fire only for
1997
+ // capabilities that already declare a lane — exactly the set that does not
1998
+ // need telling.
1999
+ //
2000
+ // Presence-based, not `=== true`: the key is unknown at ANY value now, which
2001
+ // is the same rule the unknown-`reviewer.*`-field loop below applies. Own-key
2002
+ // read, so a polluted prototype cannot manufacture this warning on every
2003
+ // otherwise-innocent manifest. This is ONE keyed removal notice, not general
2004
+ // `hostBehaviors` validation — the closed vocabulary below handles that (#2801).
2005
+ const runtimeBody = cap.runtime;
2006
+ if (typeof runtimeBody === 'object' && runtimeBody !== null && !Array.isArray(runtimeBody)) {
2007
+ const hostBehaviors = runtimeBody.hostBehaviors;
2008
+ if (
2009
+ typeof hostBehaviors === 'object'
2010
+ && hostBehaviors !== null
2011
+ && !Array.isArray(hostBehaviors)
2012
+ && Object.prototype.hasOwnProperty.call(hostBehaviors, 'reviewerCli')
2013
+ ) {
2014
+ records.push({
2015
+ code: REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR,
2016
+ capId,
2017
+ field: REMOVED_REVIEWER_CLI_FIELD,
2018
+ replacement: 'reviewer',
2019
+ docs: 'docs/how-to/ship-a-reviewer-lane.md',
2020
+ message:
2021
+ '⚠ capability "' + capId + '" ' + REMOVED_REVIEWER_CLI_FIELD + ' was removed (ADR-2782 D9) ' +
2022
+ '— ignored, and it contributes no reviewer lane. Declare a `reviewer` body instead; see ' +
2023
+ 'docs/how-to/ship-a-reviewer-lane.md',
2024
+ });
2025
+ }
2026
+
2027
+ // #2801 — the closed `hostBehaviors` vocabulary (ADR-1016). `reviewerCli` is
2028
+ // excluded here: it already drew its own removal notice above, and a second,
2029
+ // generic "unknown key" record for the same key would be noise, not signal.
2030
+ if (typeof hostBehaviors === 'object' && hostBehaviors !== null && !Array.isArray(hostBehaviors)) {
2031
+ let reported = 0;
2032
+ let omitted = 0;
2033
+ for (const key of Object.keys(hostBehaviors)) {
2034
+ if (isReservedName(key) || key === 'reviewerCli' || KNOWN_HOST_BEHAVIORS.has(key)) continue;
2035
+ if (reported >= MAX_REPORTED_UNKNOWN_KEYS) {
2036
+ omitted += 1;
2037
+ continue;
2038
+ }
2039
+ reported += 1;
2040
+ const safeKey = describeKey(key);
2041
+ records.push({
2042
+ code: REVIEWER_WARNING.UNKNOWN_HOST_BEHAVIOR,
2043
+ capId,
2044
+ field: 'runtime.hostBehaviors.' + safeKey,
2045
+ message:
2046
+ '⚠ capability "' + capId + '" runtime.hostBehaviors.' + safeKey + ' is not a known host behavior ' +
2047
+ 'in this GSD version — ignored. Adding one is a reviewed first-party change (ADR-1016).',
2048
+ });
2049
+ }
2050
+ if (omitted > 0) {
2051
+ records.push({
2052
+ code: REVIEWER_WARNING.UNKNOWN_HOST_BEHAVIOR,
2053
+ capId,
2054
+ field: 'runtime.hostBehaviors',
2055
+ truncated: true,
2056
+ omittedCount: omitted,
2057
+ message:
2058
+ '⚠ capability "' + capId + '" declares ' + omitted + ' further unknown runtime.hostBehaviors ' +
2059
+ 'key(s), not listed. A manifest this far outside the vocabulary is likely built for a ' +
2060
+ 'different GSD version.',
2061
+ });
2062
+ }
2063
+ }
2064
+ }
2065
+
2066
+ const r = cap.reviewer;
2067
+ if (typeof r !== 'object' || r === null || Array.isArray(r)) return records;
2068
+
2069
+ // Same ceiling and the same key sanitization as the hostBehaviors sweep above.
2070
+ // This loop predates #2801 and carried both defects; fixing only the new copy
2071
+ // would leave the identical defect one screen away from its own fix.
2072
+ let reportedFields = 0;
2073
+ let omittedFields = 0;
1683
2074
  for (const key of Object.keys(r)) {
1684
2075
  if (isReservedName(key) || KNOWN_REVIEWER_FIELDS.has(key)) continue;
1685
- warnings.push(
1686
- '⚠ capability "' + capId + '" reviewer.' + key + ' is not a known reviewer field ' +
1687
- 'in this GSD version — ignored. Known fields: ' + [...KNOWN_REVIEWER_FIELDS].join(', '),
1688
- );
1689
- }
1690
- return warnings;
2076
+ if (reportedFields >= MAX_REPORTED_UNKNOWN_KEYS) {
2077
+ omittedFields += 1;
2078
+ continue;
2079
+ }
2080
+ reportedFields += 1;
2081
+ const safeKey = describeKey(key);
2082
+ records.push({
2083
+ code: REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD,
2084
+ capId,
2085
+ field: 'reviewer.' + safeKey,
2086
+ knownFields: [...KNOWN_REVIEWER_FIELDS],
2087
+ message:
2088
+ '⚠ capability "' + capId + '" reviewer.' + safeKey + ' is not a known reviewer field ' +
2089
+ 'in this GSD version — ignored. Known fields: ' + [...KNOWN_REVIEWER_FIELDS].join(', '),
2090
+ });
2091
+ }
2092
+ if (omittedFields > 0) {
2093
+ records.push({
2094
+ code: REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD,
2095
+ capId,
2096
+ field: 'reviewer',
2097
+ knownFields: [...KNOWN_REVIEWER_FIELDS],
2098
+ truncated: true,
2099
+ omittedCount: omittedFields,
2100
+ message:
2101
+ '⚠ capability "' + capId + '" declares ' + omittedFields + ' further unknown reviewer field(s), ' +
2102
+ 'not listed.',
2103
+ });
2104
+ }
2105
+ return records;
1691
2106
  }
1692
2107
 
1693
2108
  /**
@@ -2036,6 +2451,66 @@ function validateSpawnInvoke(ctx, invoke) {
2036
2451
 
2037
2452
  errors.push(...validateEnumField(ctx, 'reviewer.invoke.effortChannel', invoke.effortChannel, VALID_LANE_EFFORT_CHANNELS));
2038
2453
 
2454
+ // `env` — per-invocation environment pairs (#2483). OPTIONAL, unlike every field above: only a lane
2455
+ // that needs to shape its child's environment declares it, and absent is the common case. Validated
2456
+ // when present, because `resolveLanePlan` DROPS a non-string value rather than coercing it — so an
2457
+ // unvalidated manifest declares a pair that silently never reaches the spawn, which is the failure
2458
+ // mode a shipped env-guard can least afford. Keys are held to the portable POSIX
2459
+ // environment-name grammar, which is a POLICY — not a claim about what an environment can physically
2460
+ // hold. Measured: of the names refused below only NUL is actually rejected by `spawnSync`; `=`, a
2461
+ // leading digit, a dash and a space are all carried through to the child (an `A=B` key arrives as
2462
+ // the raw entry `A=B=value`, and reads back via `process.env['A=B']`). They are refused because a
2463
+ // name outside the grammar is not portably addressable by the program meant to read it.
2464
+ if (invoke.env !== undefined) {
2465
+ if (typeof invoke.env !== 'object' || invoke.env === null || Array.isArray(invoke.env)) {
2466
+ errors.push(
2467
+ ctx + ' reviewer.invoke.env must be an object of environment name/value pairs ' +
2468
+ '(got: ' + describeValue(invoke.env) + ')',
2469
+ );
2470
+ } else {
2471
+ for (const key of Object.keys(invoke.env)) {
2472
+ // `__proto__` passes the grammar below and IS a real own key once a manifest is JSON-parsed,
2473
+ // but assigning it onto a plain accumulator goes through the inherited `__proto__` SETTER
2474
+ // instead of creating an own property. For the string values this field permits the setter is
2475
+ // a no-op — the prototype is not even changed — so the pair would validate and then simply
2476
+ // vanish before the spawn: the declared-but-never-delivered failure this block exists to catch.
2477
+ // Refused by name, because the grammar cannot see it.
2478
+ //
2479
+ // Only `__proto__` needs this. Sibling reserved-name guards in this file reject
2480
+ // `constructor`/`prototype` alongside it, but those guard bracket LOOKUPS that resolve
2481
+ // prototype members; here the read is `Object.keys` + an own-value read, and `constructor`
2482
+ // assigns as an ordinary own key. Refusing it too would reject a name the spawn could carry.
2483
+ // CASE-INSENSITIVE, and that is not pedantry: Windows environment lookups are
2484
+ // case-insensitive, so `Path` / `node_options` reach the child as `PATH` / `NODE_OPTIONS`
2485
+ // and an exact-case set is bypassed by changing one letter. The grammar above already
2486
+ // constrains keys to ASCII, so a plain uppercase fold is sufficient here.
2487
+ if (DENIED_LANE_ENV_KEYS.has(key.toUpperCase())) {
2488
+ errors.push(
2489
+ ctx + ' reviewer.invoke.env key "' + key + '" is not permitted ' +
2490
+ '(it makes the spawned reviewer run code of the manifest\'s choosing; ' +
2491
+ 'declare an absolute `invoke.binary` instead of reshaping the child\'s environment)',
2492
+ );
2493
+ } else if (key === '__proto__') {
2494
+ errors.push(
2495
+ ctx + ' reviewer.invoke.env key "__proto__" is not permitted ' +
2496
+ '(it is silently dropped when the spawn plan is assembled, so it would never reach the child)',
2497
+ );
2498
+ } else if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
2499
+ errors.push(
2500
+ ctx + ' reviewer.invoke.env key ' + describeValue(key) +
2501
+ ' is not a valid environment variable name',
2502
+ );
2503
+ }
2504
+ if (typeof invoke.env[key] !== 'string') {
2505
+ errors.push(
2506
+ ctx + ' reviewer.invoke.env.' + key + ' must be a string ' +
2507
+ '(got: ' + describeValue(invoke.env[key]) + ')',
2508
+ );
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+
2039
2514
  return errors;
2040
2515
  }
2041
2516
 
@@ -3145,6 +3620,8 @@ module.exports = {
3145
3620
  VALID_SANDBOX_TIERS,
3146
3621
  VALID_ARTIFACT_KIND_NAMES,
3147
3622
  VALID_ARTIFACT_NESTINGS,
3623
+ VALID_TRIGGER_PRECEDENCE_KINDS,
3624
+ DEFAULT_TRIGGER_PRECEDENCE,
3148
3625
  FEATURE_FIELDS_FORBIDDEN_ON_RUNTIME,
3149
3626
  // ADR-2782 D1/D2/D3/D6/D7/D8 — reviewer lane body
3150
3627
  FEATURE_FIELDS_FORBIDDEN_ON_REVIEWER,
@@ -3160,8 +3637,14 @@ module.exports = {
3160
3637
  VALID_EVIDENCE_CLASSES,
3161
3638
  VALID_LANE_HANDLERS,
3162
3639
  KNOWN_REVIEWER_FIELDS,
3640
+ KNOWN_HOST_BEHAVIORS,
3641
+ MAX_REPORTED_UNKNOWN_KEYS,
3642
+ MAX_REPORTED_KEY_CHARS,
3163
3643
  validateReviewerBody,
3164
3644
  collectReviewerWarnings,
3645
+ collectReviewerWarningRecords,
3646
+ REVIEWER_WARNING,
3647
+ REMOVED_REVIEWER_CLI_FIELD,
3165
3648
  VALID_INSTALL_SURFACES,
3166
3649
  VALID_PERMISSION_WRITERS,
3167
3650
  VALID_EXTENDED_HOOK_EVENTS,
@@ -270,8 +270,9 @@ function setCapabilityState(cwd, runtimeConfigDir, desired, opts) {
270
270
  // access to getCommitAttribution (which lives in bin/install.js). Until that
271
271
  // is refactored into a shared module, surface-path agents for descriptor-
272
272
  // driven runtimes will lack the Co-Authored-By trailer that install adds.
273
- // Parity is proven when resolveAttribution IS provided (see
274
- // tests/issue-1575-agent-descriptor-parity.test.cjs).
273
+ // Parity is proven when resolveAttribution IS provided (see the
274
+ // folded:issue-1575-agent-descriptor-parity describe block in
275
+ // tests/golden-parity-single-source.test.cjs).
275
276
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
276
277
  applySurface(resolvedConfigDir, layout, manifest, undefined, registry, opts?.materialize?.resolveAttribution
277
278
  ? { resolveAttribution: opts.materialize.resolveAttribution }