@opengsd/gsd-core 1.12.0 → 1.13.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 (286) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-executor.md +63 -35
  5. package/agents/gsd-plan-checker.md +76 -57
  6. package/agents/gsd-planner.md +14 -0
  7. package/agents/gsd-ui-checker.md +19 -3
  8. package/agents/gsd-ui-researcher.md +29 -0
  9. package/agents/gsd-verifier.md +23 -1
  10. package/bin/install.js +239 -67
  11. package/commands/gsd/execute-phase.md +1 -1
  12. package/commands/gsd/ns-workflow.md +2 -1
  13. package/commands/gsd/phase.md +1 -1
  14. package/commands/gsd/quick-batch.md +105 -0
  15. package/commands/gsd/surface.md +18 -8
  16. package/gsd-core/bin/gsd-tools.cjs +195 -50
  17. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  18. package/gsd-core/bin/lib/capability-registry.cjs +514 -114
  19. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  20. package/gsd-core/bin/lib/capability-validator.cjs +120 -4
  21. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  22. package/gsd-core/bin/lib/check-command-router.cjs +85 -2
  23. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  24. package/gsd-core/bin/lib/clusters.cjs +1 -0
  25. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  26. package/gsd-core/bin/lib/commands.cjs +337 -13
  27. package/gsd-core/bin/lib/config-loader.cjs +3 -0
  28. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  29. package/gsd-core/bin/lib/decisions.cjs +213 -1
  30. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  31. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  32. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  33. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  34. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  35. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  36. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  37. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  38. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  39. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  40. package/gsd-core/bin/lib/init.cjs +132 -15
  41. package/gsd-core/bin/lib/install-engine.cjs +184 -12
  42. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  43. package/gsd-core/bin/lib/install-profiles.cjs +22 -14
  44. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  45. package/gsd-core/bin/lib/io.cjs +35 -0
  46. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  47. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  48. package/gsd-core/bin/lib/milestone.cjs +22 -2
  49. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  50. package/gsd-core/bin/lib/phase-id.cjs +251 -9
  51. package/gsd-core/bin/lib/phase.cjs +774 -35
  52. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  53. package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
  54. package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
  55. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  56. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  57. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  58. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  59. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  60. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  61. package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
  62. package/gsd-core/bin/lib/roadmap.cjs +187 -58
  63. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
  64. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  65. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
  66. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
  67. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  68. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  69. package/gsd-core/bin/lib/state-document.cjs +30 -5
  70. package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
  71. package/gsd-core/bin/lib/state-transition.cjs +333 -44
  72. package/gsd-core/bin/lib/state.cjs +684 -125
  73. package/gsd-core/bin/lib/surface.cjs +23 -8
  74. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  75. package/gsd-core/bin/lib/uat.cjs +1419 -515
  76. package/gsd-core/bin/lib/update-context.cjs +6 -2
  77. package/gsd-core/bin/lib/validate.cjs +230 -12
  78. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  79. package/gsd-core/bin/lib/verification.cjs +273 -12
  80. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  81. package/gsd-core/bin/lib/verify.cjs +346 -16
  82. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  83. package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
  84. package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
  85. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  86. package/gsd-core/references/agent-contracts.md +3 -3
  87. package/gsd-core/references/edge-probe.md +17 -13
  88. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  89. package/gsd-core/references/execute-phase-response-language.md +6 -0
  90. package/gsd-core/references/executor-examples.md +42 -0
  91. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  92. package/gsd-core/references/mvp-concepts.md +2 -2
  93. package/gsd-core/references/plan-checker-examples.md +41 -0
  94. package/gsd-core/references/planner-antipatterns.md +25 -0
  95. package/gsd-core/references/planner-chunked.md +5 -1
  96. package/gsd-core/references/planner-coupling.md +42 -0
  97. package/gsd-core/references/planner-quick-batch.md +71 -0
  98. package/gsd-core/references/planner-reviews.md +47 -0
  99. package/gsd-core/references/planner-revision.md +75 -2
  100. package/gsd-core/references/planning-config.md +2 -1
  101. package/gsd-core/references/response-language-directive.md +9 -0
  102. package/gsd-core/references/revision-loop.md +118 -11
  103. package/gsd-core/references/tdd.md +14 -9
  104. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  105. package/gsd-core/templates/phase-prompt.md +4 -0
  106. package/gsd-core/templates/verification-report.md +5 -0
  107. package/gsd-core/workflows/add-backlog.md +2 -0
  108. package/gsd-core/workflows/add-phase.md +2 -0
  109. package/gsd-core/workflows/add-tests.md +1 -1
  110. package/gsd-core/workflows/add-todo.md +1 -1
  111. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  112. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  113. package/gsd-core/workflows/audit-fix.md +2 -0
  114. package/gsd-core/workflows/audit-milestone.md +2 -0
  115. package/gsd-core/workflows/audit-uat.md +2 -0
  116. package/gsd-core/workflows/autonomous.md +2 -0
  117. package/gsd-core/workflows/check-todos.md +1 -1
  118. package/gsd-core/workflows/cleanup.md +1 -1
  119. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
  120. package/gsd-core/workflows/code-review-fix.md +2 -0
  121. package/gsd-core/workflows/code-review.md +73 -31
  122. package/gsd-core/workflows/complete-milestone.md +13 -4
  123. package/gsd-core/workflows/debug.md +1 -1
  124. package/gsd-core/workflows/diagnose-issues.md +5 -1
  125. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  126. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  127. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  128. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  129. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  130. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  131. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  132. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  133. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  134. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  135. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  136. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  137. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  138. package/gsd-core/workflows/discuss-phase.md +1 -1
  139. package/gsd-core/workflows/do.md +43 -13
  140. package/gsd-core/workflows/docs-update.md +1 -1
  141. package/gsd-core/workflows/edit-phase.md +2 -0
  142. package/gsd-core/workflows/eval-review.md +1 -1
  143. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
  144. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
  145. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  146. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  147. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  148. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  149. package/gsd-core/workflows/execute-phase.md +32 -14
  150. package/gsd-core/workflows/execute-plan.md +8 -8
  151. package/gsd-core/workflows/explore.md +2 -0
  152. package/gsd-core/workflows/extract-learnings.md +2 -0
  153. package/gsd-core/workflows/fast.md +6 -0
  154. package/gsd-core/workflows/forensics.md +2 -0
  155. package/gsd-core/workflows/graduation.md +1 -1
  156. package/gsd-core/workflows/health.md +1 -1
  157. package/gsd-core/workflows/help/modes/brief.md +2 -0
  158. package/gsd-core/workflows/help/modes/default.md +2 -0
  159. package/gsd-core/workflows/help/modes/full.md +12 -0
  160. package/gsd-core/workflows/help/modes/topic.md +2 -0
  161. package/gsd-core/workflows/help.md +2 -0
  162. package/gsd-core/workflows/import.md +3 -3
  163. package/gsd-core/workflows/inbox.md +1 -1
  164. package/gsd-core/workflows/ingest-docs.md +1 -1
  165. package/gsd-core/workflows/insert-phase.md +2 -0
  166. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  167. package/gsd-core/workflows/list-seeds.md +2 -0
  168. package/gsd-core/workflows/list-workspaces.md +2 -0
  169. package/gsd-core/workflows/manager.md +3 -3
  170. package/gsd-core/workflows/map-codebase.md +2 -0
  171. package/gsd-core/workflows/milestone-summary.md +2 -0
  172. package/gsd-core/workflows/mvp-phase.md +1 -1
  173. package/gsd-core/workflows/new-milestone.md +1 -1
  174. package/gsd-core/workflows/new-project.md +5 -3
  175. package/gsd-core/workflows/new-workspace.md +1 -1
  176. package/gsd-core/workflows/next.md +2 -0
  177. package/gsd-core/workflows/node-repair.md +2 -0
  178. package/gsd-core/workflows/note.md +2 -0
  179. package/gsd-core/workflows/onboard.md +1 -1
  180. package/gsd-core/workflows/pause-work.md +19 -4
  181. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  182. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  183. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  184. package/gsd-core/workflows/plan-phase.md +130 -12
  185. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  186. package/gsd-core/workflows/plant-seed.md +1 -1
  187. package/gsd-core/workflows/pr-branch.md +11 -3
  188. package/gsd-core/workflows/profile-user.md +1 -1
  189. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  190. package/gsd-core/workflows/progress.md +25 -3
  191. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  192. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  193. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  194. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  195. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  196. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  197. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  198. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  199. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  200. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  201. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  202. package/gsd-core/workflows/quick-batch.md +203 -0
  203. package/gsd-core/workflows/quick.md +13 -3
  204. package/gsd-core/workflows/reapply-patches.md +2 -0
  205. package/gsd-core/workflows/remove-phase.md +2 -0
  206. package/gsd-core/workflows/remove-workspace.md +1 -1
  207. package/gsd-core/workflows/resume-project.md +6 -2
  208. package/gsd-core/workflows/review.md +215 -10
  209. package/gsd-core/workflows/scan.md +2 -0
  210. package/gsd-core/workflows/section-manifest.json +12 -0
  211. package/gsd-core/workflows/secure-phase.md +1 -1
  212. package/gsd-core/workflows/session-report.md +2 -0
  213. package/gsd-core/workflows/settings-advanced.md +2 -0
  214. package/gsd-core/workflows/settings-integrations.md +9 -8
  215. package/gsd-core/workflows/settings.md +1 -1
  216. package/gsd-core/workflows/ship.md +10 -10
  217. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  218. package/gsd-core/workflows/sketch.md +1 -1
  219. package/gsd-core/workflows/smart-entry.md +1 -1
  220. package/gsd-core/workflows/spec-phase.md +24 -19
  221. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  222. package/gsd-core/workflows/spike.md +1 -1
  223. package/gsd-core/workflows/stats.md +2 -0
  224. package/gsd-core/workflows/sync-skills.md +12 -4
  225. package/gsd-core/workflows/thread.md +2 -0
  226. package/gsd-core/workflows/transition.md +2 -0
  227. package/gsd-core/workflows/ui-phase.md +26 -5
  228. package/gsd-core/workflows/ui-review.md +1 -1
  229. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  230. package/gsd-core/workflows/undo.md +1 -1
  231. package/gsd-core/workflows/update.md +41 -38
  232. package/gsd-core/workflows/validate-phase.md +1 -1
  233. package/gsd-core/workflows/verify-work.md +49 -3
  234. package/hooks/dist/gsd-check-update-worker.js +19 -2
  235. package/hooks/dist/gsd-context-monitor.js +283 -12
  236. package/hooks/dist/gsd-node-runner.sh +1 -0
  237. package/hooks/dist/gsd-prompt-guard.js +30 -5
  238. package/hooks/dist/gsd-read-guard.js +2 -0
  239. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  240. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  241. package/hooks/dist/gsd-statusline.js +7 -3
  242. package/hooks/dist/gsd-validate-commit.sh +444 -7
  243. package/hooks/dist/gsd-workflow-guard.js +2 -1
  244. package/hooks/dist/lib/git-cmd.js +210 -1
  245. package/hooks/dist/lib/injection-patterns.js +36 -6
  246. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  247. package/hooks/gsd-check-update-worker.js +19 -2
  248. package/hooks/gsd-context-monitor.js +283 -12
  249. package/hooks/gsd-node-runner.sh +1 -0
  250. package/hooks/gsd-prompt-guard.js +30 -5
  251. package/hooks/gsd-read-guard.js +2 -0
  252. package/hooks/gsd-read-injection-scanner.js +5 -5
  253. package/hooks/gsd-secret-read-guard.js +1079 -0
  254. package/hooks/gsd-statusline.js +7 -3
  255. package/hooks/gsd-validate-commit.sh +444 -7
  256. package/hooks/gsd-workflow-guard.js +2 -1
  257. package/hooks/hooks.json +6 -0
  258. package/hooks/lib/git-cmd.js +210 -1
  259. package/hooks/lib/injection-patterns.js +36 -6
  260. package/hooks/managed-hooks-registry.cjs +1 -0
  261. package/package.json +5 -5
  262. package/scripts/build-hooks.js +11 -4
  263. package/scripts/ci-test-scope.cjs +7 -0
  264. package/scripts/docs-guard-registry.cjs +10 -0
  265. package/scripts/gen-loop-host-contract.cjs +67 -15
  266. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  267. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  268. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  269. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  270. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +5 -0
  271. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  272. package/scripts/lint-phase-id-drift.cjs +133 -8
  273. package/scripts/lint-portable-grep.cjs +176 -0
  274. package/scripts/lint-response-language-coverage.cjs +524 -0
  275. package/scripts/lint-test-file-count.allowlist.json +3 -1
  276. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  277. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  278. package/scripts/npm-audit-baseline.cjs +376 -0
  279. package/scripts/prompt-injection-scan.sh +8 -0
  280. package/scripts/require-issue-link-policy.cjs +16 -1
  281. package/skills/gsd-execute-phase/SKILL.md +1 -1
  282. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  283. package/skills/gsd-phase/SKILL.md +1 -1
  284. package/skills/gsd-quick-batch/SKILL.md +105 -0
  285. package/skills/gsd-surface/SKILL.md +18 -8
  286. package/vscode/package.json +1 -1
@@ -39,7 +39,7 @@ const ioMod = require("./io.cjs");
39
39
  const { output: coreOutput, error: coreError } = ioMod;
40
40
  // eslint-disable-next-line @typescript-eslint/no-require-imports
41
41
  const activationMod = require("./capability-activation.cjs");
42
- const { _resolveActivationValue } = activationMod;
42
+ const { _resolveActivationValue, _resolvePointGate } = activationMod;
43
43
  // eslint-disable-next-line @typescript-eslint/no-require-imports
44
44
  const configLoaderMod = require("./config-loader.cjs");
45
45
  const { loadConfig } = configLoaderMod;
@@ -179,6 +179,12 @@ function resolveCapabilityState(input) {
179
179
  // (mirrors loop-resolver.isActive: `typeof when !== 'string' || when.length === 0` → false)
180
180
  configured = false;
181
181
  }
182
+ // #3661: optional point-selection gate, ANDed in — mirrors loop-resolver.isActive
183
+ // EXACTLY (see capability-activation.cts's _resolvePointGate doc comment; this
184
+ // parity is load-bearing, see tests/capability-precedence-parity.test.cjs).
185
+ if (configured) {
186
+ configured = _resolvePointGate(h['pointFrom'], point, config, cwd, registry);
187
+ }
182
188
  // Hook active = capability-level active AND hook's own config gate.
183
189
  // The capability's `active` constant (= enabled && configActivation) is
184
190
  // used here so that a config-disabled capability (active=false) cannot
@@ -1643,6 +1643,28 @@ function validateRuntimeBody(cap) {
1643
1643
  ' (or "undocumented") (got: ' + JSON.stringify(d.isolation) + ')',
1644
1644
  );
1645
1645
  }
1646
+
1647
+ // maxConcurrency — ADR-1239 Phase 1 (#3673). A numeric dispatch
1648
+ // sub-field (not a closed-vocabulary enum member — same numeric
1649
+ // dispatch sub-field treatment as maxDepth above; unlike maxDepth,
1650
+ // 0 and negative values are invalid — 1 is the fail-closed floor, not
1651
+ // a legitimate "no concurrency" declaration).
1652
+ // OPTIONAL, like isolation: added after existing descriptors, so an
1653
+ // omitted maxConcurrency is legitimate — negotiateHostCapabilities
1654
+ // degrades it to 1 (the safe floor) and warns, exactly as for any
1655
+ // other undeclared dispatch sub-field. Only a PRESENT value is
1656
+ // checked against the positive-safe-integer contract.
1657
+ if (d.maxConcurrency === undefined) {
1658
+ // absent — nothing to validate; negotiateHostCapabilities fails it closed.
1659
+ } else if (
1660
+ d.maxConcurrency !== 'undocumented' &&
1661
+ (!Number.isSafeInteger(d.maxConcurrency) || d.maxConcurrency < 1)
1662
+ ) {
1663
+ errors.push(
1664
+ 'runtime.hostIntegration.dispatch.maxConcurrency must be a positive safe integer or "undocumented" ' +
1665
+ '(got: ' + JSON.stringify(d.maxConcurrency) + ')',
1666
+ );
1667
+ }
1646
1668
  }
1647
1669
  }
1648
1670
 
@@ -1824,9 +1846,29 @@ const KNOWN_REVIEWER_FIELDS = new Set([
1824
1846
  // missed a configured model and silently disabled the pinned-model escape hatch
1825
1847
  // #2073 added. A convention one shipped lane already violates is not a contract.
1826
1848
  'modelConfigKey',
1849
+ // `timeoutConfigKey` added by #3274, same optional/backward-compatible shape as
1850
+ // `modelConfigKey` above: a manifest authored before this field existed must keep validating.
1851
+ 'timeoutConfigKey',
1852
+ // `effortConfigKey` / `defaultEffort` added by #4255, same optional/backward-compatible shape
1853
+ // as the two above. Lane effort used to be resolved by querying the `gsd-plan-checker` AGENT
1854
+ // through a hardcoded id, so every prompt-fed lane ran at that verifier's frontmatter effort;
1855
+ // it is a property of the review, so the lane declares it. A manifest authored before these
1856
+ // fields existed must keep validating — absent is read as "this lane declares no review
1857
+ // effort", which emits no effort argument at all.
1858
+ 'effortConfigKey',
1859
+ 'defaultEffort',
1827
1860
  'handler',
1828
1861
  ]);
1829
1862
 
1863
+ /**
1864
+ * The effort levels a lane may declare as its review default (#4255, #3533 vocabulary).
1865
+ *
1866
+ * `inherit` is deliberately NOT a member. It is a legitimate CONFIGURED value — it selects "emit
1867
+ * no argument, the CLI's own configuration decides" — but as a DECLARED default it would be a
1868
+ * second spelling of `null` and split one behaviour across two shapes.
1869
+ */
1870
+ const REVIEWER_EFFORT_LEVELS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
1871
+
1830
1872
  /**
1831
1873
  * The closed `runtime.hostBehaviors` vocabulary (ADR-1016, closed via #2801).
1832
1874
  *
@@ -2371,6 +2413,50 @@ function validateReviewerBodyFields(cap) {
2371
2413
  );
2372
2414
  }
2373
2415
 
2416
+ // OPTIONAL, mirroring modelConfigKey's D4 forward/backward-compat treatment (#3274): a manifest
2417
+ // authored before this field existed must keep validating. Absent/null means "no override for
2418
+ // this lane, use timeoutFloorMs". An empty string is neither absent nor a key, and is rejected.
2419
+ if (r.timeoutConfigKey !== undefined && r.timeoutConfigKey !== null &&
2420
+ (typeof r.timeoutConfigKey !== 'string' || r.timeoutConfigKey.length === 0)) {
2421
+ errors.push(
2422
+ ctx + ' reviewer.timeoutConfigKey must be a dotted config key or null ' +
2423
+ '(got: ' + describeValue(r.timeoutConfigKey) + ')',
2424
+ );
2425
+ }
2426
+
2427
+ // OPTIONAL, mirroring the two keys above (#4255). Absent/null means "this lane declares no
2428
+ // review effort", which is the shape that emits no effort argument at all — the correct state
2429
+ // for the nine lanes with no effort channel to feed. An empty string is neither.
2430
+ if (r.effortConfigKey !== undefined && r.effortConfigKey !== null &&
2431
+ (typeof r.effortConfigKey !== 'string' || r.effortConfigKey.length === 0)) {
2432
+ errors.push(
2433
+ ctx + ' reviewer.effortConfigKey must be a dotted config key or null ' +
2434
+ '(got: ' + describeValue(r.effortConfigKey) + ')',
2435
+ );
2436
+ }
2437
+
2438
+ // A declared default must be a level the effort axis knows, or the lane would render an
2439
+ // argument the reviewer CLI rejects and kill itself on every run. Closed vocabulary, checked
2440
+ // here rather than at resolution time so a malformed manifest fails at the trust boundary.
2441
+ if (r.defaultEffort !== undefined && r.defaultEffort !== null
2442
+ && !(typeof r.defaultEffort === 'string' && REVIEWER_EFFORT_LEVELS.has(r.defaultEffort))) {
2443
+ errors.push(
2444
+ ctx + ' reviewer.defaultEffort must be one of ' +
2445
+ Array.from(REVIEWER_EFFORT_LEVELS).join('/') + ' or null ' +
2446
+ '(got: ' + describeValue(r.defaultEffort) + ')',
2447
+ );
2448
+ }
2449
+
2450
+ // A default with nothing to configure it through is a value the operator cannot change — the
2451
+ // shape #4255 exists to end. Declared together or not at all.
2452
+ if ((r.defaultEffort !== undefined && r.defaultEffort !== null)
2453
+ && (r.effortConfigKey === undefined || r.effortConfigKey === null)) {
2454
+ errors.push(
2455
+ ctx + ' reviewer.defaultEffort is declared without an effortConfigKey, so the level ' +
2456
+ 'could never be overridden',
2457
+ );
2458
+ }
2459
+
2374
2460
  // `null` is the declared "no per-lane budget"; an empty string is not.
2375
2461
  if (r.promptBudgetKey !== null && (typeof r.promptBudgetKey !== 'string' || r.promptBudgetKey.length === 0)) {
2376
2462
  errors.push(
@@ -2858,6 +2944,10 @@ function validateStep(step, prefix, declaredSkills, declaredAgents) {
2858
2944
  errors.push(prefix + '.when must be a string if present');
2859
2945
  }
2860
2946
 
2947
+ if (step.pointFrom !== undefined && typeof step.pointFrom !== 'string') {
2948
+ errors.push(prefix + '.pointFrom must be a string if present');
2949
+ }
2950
+
2861
2951
  if (step.fragment !== undefined) {
2862
2952
  errors.push(...validateFragment(step.fragment, prefix + '.fragment'));
2863
2953
  }
@@ -2997,6 +3087,31 @@ function validateAgainstContract(cap, capId) {
2997
3087
  }
2998
3088
  }
2999
3089
 
3090
+ // pointFrom (#3661): selects which of possibly several same-capability steps is
3091
+ // active for its own `point`, based on an enum config key. Require it references
3092
+ // an enum key in cap.config whose values include THIS step's own point — otherwise
3093
+ // the step could never activate (a silently-dead step).
3094
+ for (const step of cap.steps) {
3095
+ if (step.pointFrom !== undefined) {
3096
+ if (typeof step.pointFrom !== 'string') continue; // already reported above
3097
+ const slice = typeof cap.config === 'object' && cap.config !== null ? cap.config[step.pointFrom] : undefined;
3098
+ if (!slice || typeof slice !== 'object') {
3099
+ errors.push(
3100
+ prefix + ' step.pointFrom "' + step.pointFrom + '" is not defined in capability config keys',
3101
+ );
3102
+ } else if (slice.type !== 'enum') {
3103
+ errors.push(
3104
+ prefix + ' step.pointFrom "' + step.pointFrom + '" must reference an enum config key (got type: ' + slice.type + ')',
3105
+ );
3106
+ } else if (!Array.isArray(slice.values) || !slice.values.includes(step.point)) {
3107
+ errors.push(
3108
+ prefix + ' step.pointFrom "' + step.pointFrom + '" enum values do not include this step\'s own point "' +
3109
+ step.point + '" — the step could never activate',
3110
+ );
3111
+ }
3112
+ }
3113
+ }
3114
+
3000
3115
  for (const contrib of cap.contributions) {
3001
3116
  if (contrib.when !== undefined) {
3002
3117
  if (typeof contrib.when !== 'string') continue;
@@ -3605,11 +3720,12 @@ const HOOK_GROUP_KINDS = Object.freeze({
3605
3720
  * double-report.
3606
3721
  *
3607
3722
  * KNOWN LIMITATION (#3606): coverage is the UNION across all call sites for a
3608
- * point in the five STEP_WORKFLOWS host files. Consumers outside that universe
3609
- * (quick.md, autonomous.md, code-review*.md, audit-milestone.md,
3723
+ * point in HOST_LOOP_FILES. Quick's plan:pre planner contribution seam has an
3724
+ * additional generator-owned per-file check. Other consumers outside that
3725
+ * universe (autonomous.md, code-review*.md, audit-milestone.md,
3610
3726
  * secure-phase.md, validate-phase.md) are not per-file checked — a narrowed
3611
- * consumer there passes as long as one host file covers the point. Per-file
3612
- * coverage maps are the tightening path.
3727
+ * consumer there passes as long as one host file covers the point. More
3728
+ * per-file coverage maps are the tightening path.
3613
3729
  *
3614
3730
  * @param {object} cap Validated capability object.
3615
3731
  * @param {Map<string, Set<string>>} wiredKinds Per point, the hook kinds the
@@ -184,6 +184,7 @@ function setCapabilityState(cwd, runtimeConfigDir, desired, opts) {
184
184
  };
185
185
  }
186
186
  // ── Step 4: APPLY PASS ────────────────────────────────────────────────────
187
+ let pendingSurfaceForCommit;
187
188
  // ── Surface writes ────────────────────────────────────────────────────────
188
189
  if (needsSurface && (idsToDisable.length > 0 || idsToEnable.length > 0)) {
189
190
  const existing = readSurface(resolvedConfigDir);
@@ -242,13 +243,19 @@ function setCapabilityState(cwd, runtimeConfigDir, desired, opts) {
242
243
  }
243
244
  }
244
245
  if (surfaceChanged) {
245
- writeSurface(resolvedConfigDir, pendingSurface);
246
+ pendingSurfaceForCommit = pendingSurface;
246
247
  }
247
248
  }
248
249
  // ── Config writes (once, batched) ─────────────────────────────────────────
249
250
  if (pendingGateWrites.length > 0) {
250
251
  setConfigValues(cwd, pendingGateWrites);
251
252
  }
253
+ // State-only callers preserve the existing direct-write behavior. A caller
254
+ // that requests materialization lets applySurface publish the candidate only
255
+ // after every artifact kind has staged and synchronized successfully.
256
+ if (pendingSurfaceForCommit && !opts?.materialize) {
257
+ writeSurface(resolvedConfigDir, pendingSurfaceForCommit);
258
+ }
252
259
  // ── Materialize (optional) ────────────────────────────────────────────────
253
260
  if (opts?.materialize) {
254
261
  const { runtime, scope } = opts.materialize;
@@ -274,9 +281,12 @@ function setCapabilityState(cwd, runtimeConfigDir, desired, opts) {
274
281
  // folded:issue-1575-agent-descriptor-parity describe block in
275
282
  // tests/golden-parity-single-source.test.cjs).
276
283
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
277
- applySurface(resolvedConfigDir, layout, manifest, undefined, registry, opts?.materialize?.resolveAttribution
278
- ? { resolveAttribution: opts.materialize.resolveAttribution }
279
- : undefined);
284
+ applySurface(resolvedConfigDir, layout, manifest, undefined, registry, {
285
+ ...(pendingSurfaceForCommit ? { surfaceState: pendingSurfaceForCommit } : {}),
286
+ ...(opts.materialize.resolveAttribution
287
+ ? { resolveAttribution: opts.materialize.resolveAttribution }
288
+ : {}),
289
+ });
280
290
  }
281
291
  catch (err) {
282
292
  const msg = err instanceof Error ? err.message : String(err);
@@ -31,7 +31,7 @@ const ui_safety_gate_cjs_1 = require("./ui-safety-gate.cjs");
31
31
  const ui_frontend_evidence_cjs_1 = require("./ui-frontend-evidence.cjs");
32
32
  // eslint-disable-next-line @typescript-eslint/no-require-imports
33
33
  const verifyModule = require("./verify.cjs");
34
- const { cmdVerifySchemaDrift, cmdVerifyCodebaseDrift } = verifyModule;
34
+ const { cmdVerifySchemaDrift, cmdVerifyCodebaseDrift, cmdVerifyContextDrift } = verifyModule;
35
35
  // eslint-disable-next-line @typescript-eslint/no-require-imports
36
36
  const roadmapModule = require("./roadmap.cjs");
37
37
  const { getRoadmapPhaseWithFallback } = roadmapModule;
@@ -39,6 +39,7 @@ const { getRoadmapPhaseWithFallback } = roadmapModule;
39
39
  const gapCheckerModule = require("./gap-checker.cjs");
40
40
  const { runGapAnalysis } = gapCheckerModule;
41
41
  const prohibition_enforcement_cjs_1 = require("./prohibition-enforcement.cjs");
42
+ const tdd_red_evidence_cjs_1 = require("./tdd-red-evidence.cjs");
42
43
  // eslint-disable-next-line @typescript-eslint/no-require-imports
43
44
  const gatePredicateEval = require("./gate-predicate-evaluator.cjs");
44
45
  const { evaluatePredicate } = gatePredicateEval;
@@ -825,6 +826,74 @@ function cmdTddReviewCheckpoint(projectDir, args, raw) {
825
826
  // the host-loop dispatch's advisory branch can surface it.
826
827
  output(result, raw, undefined);
827
828
  }
829
+ // ─── tdd-red-evidence (#3770) ──────────────────────────────────────────────────
830
+ /**
831
+ * tdd-red-evidence: validates a persisted RED-phase test-run record for a
832
+ * `type: tdd` plan (#3770). Only an INTENTIONAL failure of the target test
833
+ * (verdict RED_EVIDENCE_OK) may authorize GREEN; zero-test discovery, fixture/
834
+ * load crashes, nonzero exits without a failing test, unrelated failures, and
835
+ * unexpected greens are INVALID_RED and block GREEN.
836
+ *
837
+ * The record is the JSON the executor persists after running the RED command:
838
+ * { command, exitCode, output, targetTest, targetFile?, expected?, actual? }
839
+ * Fail-closed: a missing/unreadable/unparseable record is INVALID_RED
840
+ * (reason unreadable_record), never a pass.
841
+ *
842
+ * Args: check tdd-red-evidence <record.json>
843
+ */
844
+ function cmdTddRedEvidence(_projectDir, args, raw) {
845
+ const recordPath = typeof args[2] === 'string' ? args[2] : '';
846
+ if (!recordPath) {
847
+ error('tdd-red-evidence requires a record path: check tdd-red-evidence <record.json>', ERROR_REASON.SDK_MISSING_ARG);
848
+ return;
849
+ }
850
+ const resolved = node_path_1.default.resolve(recordPath);
851
+ const text = readIfExists(resolved);
852
+ const input = (() => {
853
+ if (!text)
854
+ return null;
855
+ try {
856
+ return (JSON.parse(text) ?? {});
857
+ }
858
+ catch {
859
+ return null;
860
+ }
861
+ })();
862
+ if (!input) {
863
+ output({
864
+ passed: false,
865
+ block: true,
866
+ verdict: 'INVALID_RED',
867
+ reason: 'unreadable_record',
868
+ record: resolved,
869
+ readError: text ? `record is not valid JSON: ${resolved}` : `record not found or unreadable: ${resolved}`,
870
+ }, raw, undefined);
871
+ return;
872
+ }
873
+ const evidenceInput = {
874
+ command: input['command'],
875
+ exitCode: input['exitCode'],
876
+ output: input['output'],
877
+ targetTest: input['targetTest'],
878
+ targetFile: input['targetFile'],
879
+ expected: input['expected'],
880
+ actual: input['actual'],
881
+ };
882
+ const result = (0, tdd_red_evidence_cjs_1.classifyRedEvidence)(evidenceInput);
883
+ const record = (0, tdd_red_evidence_cjs_1.buildRedEvidenceRecord)(evidenceInput, result);
884
+ output({
885
+ // Uniform gate contract: block = !passed. INVALID_RED blocks GREEN.
886
+ passed: result.verdict === 'RED_EVIDENCE_OK',
887
+ block: result.verdict !== 'RED_EVIDENCE_OK',
888
+ verdict: result.verdict,
889
+ reason: result.reason,
890
+ evidence: result.evidence,
891
+ record,
892
+ message: result.verdict === 'RED_EVIDENCE_OK'
893
+ ? `RED evidence verified: target test "${result.evidence.target_test}" failed as expected (exit ${result.evidence.exit_code}). GREEN authorized.`
894
+ : `INVALID_RED (${result.reason}): GREEN blocked. Fix the RED phase — only an intentional failure of target test "${result.evidence.target_test}" authorizes production edits.`,
895
+ }, raw, undefined);
896
+ }
828
897
  /**
829
898
  * Resolve a phase argument to an absolute phase directory, or '' when it
830
899
  * cannot be resolved. Shared by every `check` arm that probes a phase's
@@ -1472,6 +1541,12 @@ function routeCheckCommand({ args, cwd, raw }) {
1472
1541
  cmdTddReviewCheckpoint(cwd, args, raw);
1473
1542
  return;
1474
1543
  }
1544
+ if (subcommand === 'tdd-red-evidence') {
1545
+ // #3770: intentional-RED evidence gate — only a target-test failure may
1546
+ // authorize GREEN. Validates the persisted record; never executes anything.
1547
+ cmdTddRedEvidence(cwd, args, raw);
1548
+ return;
1549
+ }
1475
1550
  if (subcommand === 'ui-safety-gate') {
1476
1551
  cmdUiSafetyGate(cwd, args, raw);
1477
1552
  return;
@@ -1491,6 +1566,13 @@ function routeCheckCommand({ args, cwd, raw }) {
1491
1566
  cmdVerifyCodebaseDrift(cwd, raw);
1492
1567
  return;
1493
1568
  }
1569
+ if (subcommand === 'verify-context-drift') {
1570
+ // Delegates to verify.context-drift — drift capability gate at plan:pre (non-blocking).
1571
+ // Dot-to-hyphen normalization means query "verify.context-drift" routes here.
1572
+ const phaseArg = typeof args[2] === 'string' ? args[2] : '';
1573
+ cmdVerifyContextDrift(cwd, phaseArg, raw);
1574
+ return;
1575
+ }
1494
1576
  if (subcommand === 'predicate') {
1495
1577
  // Generic gate-predicate evaluator (#2008). The workflow gate-dispatch calls
1496
1578
  // this for any gate whose `check` carries a `predicate` (instead of a `query`),
@@ -1508,7 +1590,7 @@ function routeCheckCommand({ args, cwd, raw }) {
1508
1590
  (0, prohibition_enforcement_cjs_1.routeProhibitionEnforcement)(args, raw);
1509
1591
  return;
1510
1592
  }
1511
- error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-command-paths, verify-failure-directions, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
1593
+ error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-red-evidence, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-command-paths, verify-failure-directions, verify-schema-drift, verify-codebase-drift, verify-context-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
1512
1594
  }
1513
1595
  module.exports = {
1514
1596
  routeCheckCommand,
@@ -1520,6 +1602,7 @@ module.exports = {
1520
1602
  cmdVerifyCommandPaths,
1521
1603
  cmdVerifyFailureDirections,
1522
1604
  cmdTddReviewCheckpoint,
1605
+ cmdTddRedEvidence,
1523
1606
  cmdCheckPredicate,
1524
1607
  buildPredicateDeps,
1525
1608
  parsePredicateFlags,
@@ -43,6 +43,9 @@
43
43
  *
44
44
  * Zero external dependencies. Pure functions. Never throws on bad input.
45
45
  */
46
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- file-overlap-partitioner.cjs is an export= CommonJS module
47
+ const fileOverlapPartitionerMod = require("./file-overlap-partitioner.cjs");
48
+ const { partitionByFileOverlap } = fileOverlapPartitionerMod;
46
49
  // ─── Constants ────────────────────────────────────────────────────────────────
47
50
  /**
48
51
  * The Agent SDK version that introduced the Workflow tool (#1143 prior art).
@@ -226,33 +229,15 @@ function detectWorkflowBackend(input) {
226
229
  *
227
230
  * This is the same overlap rule execute-phase applies inline — the only difference
228
231
  * is the execution vehicle (Workflow `parallel()` vs one-agent-per-message).
232
+ *
233
+ * #3674: the algorithm itself now lives in the generic, dependency-free
234
+ * `file-overlap-partitioner.cts` module (`partitionByFileOverlap`) — this is a
235
+ * thin adapter mapping this module's own `Plan[]` shape onto that module's
236
+ * generic `{ id, files }[]` input and back. Behavior-preserving extraction:
237
+ * same greedy first-fit algorithm, same output, only the implementation moved.
229
238
  */
230
239
  function partitionStages(plans) {
231
- const stages = [];
232
- for (const plan of plans) {
233
- const fileSet = new Set(plan.files_modified);
234
- let placed = false;
235
- for (const stage of stages) {
236
- let overlap = false;
237
- for (const f of fileSet) {
238
- if (stage.files.has(f)) {
239
- overlap = true;
240
- break;
241
- }
242
- }
243
- if (!overlap) {
244
- stage.plans.push(plan);
245
- for (const f of fileSet)
246
- stage.files.add(f);
247
- placed = true;
248
- break;
249
- }
250
- }
251
- if (!placed) {
252
- stages.push({ plans: [plan], files: new Set(fileSet) });
253
- }
254
- }
255
- return stages.map((s) => s.plans.map((p) => p.id));
240
+ return partitionByFileOverlap(plans.map((p) => ({ id: p.id, files: p.files_modified })));
256
241
  }
257
242
  /**
258
243
  * Quote a free-text value for safe embedding as a JavaScript/Workflow double-quoted
@@ -95,6 +95,7 @@ exports.CLUSTERS = Object.freeze({
95
95
  'undo',
96
96
  'fast',
97
97
  'quick',
98
+ 'quick-batch',
98
99
  'autonomous',
99
100
  'config',
100
101
  'progress',
@@ -248,6 +248,14 @@ exports.VERIFY_COMMAND_ALIASES = [
248
248
  ],
249
249
  "subcommand": "codebase-drift",
250
250
  "mutation": false
251
+ },
252
+ {
253
+ "canonical": "verify.context-drift",
254
+ "aliases": [
255
+ "verify context-drift"
256
+ ],
257
+ "subcommand": "context-drift",
258
+ "mutation": false
251
259
  }
252
260
  ];
253
261
  exports.INIT_COMMAND_ALIASES = [
@@ -299,6 +307,14 @@ exports.INIT_COMMAND_ALIASES = [
299
307
  "subcommand": "quick",
300
308
  "mutation": false
301
309
  },
310
+ {
311
+ "canonical": "init.quick-batch",
312
+ "aliases": [
313
+ "init quick-batch"
314
+ ],
315
+ "subcommand": "quick-batch",
316
+ "mutation": false
317
+ },
302
318
  {
303
319
  "canonical": "init.ingest-docs",
304
320
  "aliases": [