@opengsd/gsd-core 1.18.0 → 1.20.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 (308) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-code-fixer.compact.md +7 -7
  5. package/agents/gsd-code-fixer.md +13 -7
  6. package/agents/gsd-code-reviewer.compact.md +49 -8
  7. package/agents/gsd-code-reviewer.md +53 -14
  8. package/agents/gsd-executor.md +8 -4
  9. package/agents/gsd-verifier.md +1 -1
  10. package/bin/install.js +422 -103
  11. package/commands/gsd/update.md +5 -2
  12. package/gsd-core/bin/gsd-tools.cjs +179 -30
  13. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  14. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  15. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  16. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  17. package/gsd-core/bin/lib/capability-lock.cjs +13 -2
  18. package/gsd-core/bin/lib/capability-registry.cjs +376 -103
  19. package/gsd-core/bin/lib/capability-state.cjs +14 -8
  20. package/gsd-core/bin/lib/capability-validator.cjs +32 -0
  21. package/gsd-core/bin/lib/commands.cjs +27 -17
  22. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  23. package/gsd-core/bin/lib/config.cjs +256 -58
  24. package/gsd-core/bin/lib/core-utils.cjs +45 -0
  25. package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
  26. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  27. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  28. package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
  29. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
  30. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
  31. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  32. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  33. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  34. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
  35. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  36. package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
  37. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
  38. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  39. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  40. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  41. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  42. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  43. package/gsd-core/bin/lib/init.cjs +245 -68
  44. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  45. package/gsd-core/bin/lib/install-profiles.cjs +9 -4
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  47. package/gsd-core/bin/lib/io.cjs +6 -0
  48. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  49. package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
  50. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  51. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  52. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  54. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  55. package/gsd-core/bin/lib/milestone.cjs +61 -51
  56. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  57. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  58. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  59. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  60. package/gsd-core/bin/lib/phase-id.cjs +629 -38
  61. package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
  62. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  63. package/gsd-core/bin/lib/phase.cjs +3066 -653
  64. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  65. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  66. package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
  67. package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
  68. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  69. package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
  70. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  71. package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
  72. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  73. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  74. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  75. package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
  76. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  77. package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
  78. package/gsd-core/bin/lib/roadmap.cjs +383 -123
  79. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
  80. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  81. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  82. package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
  83. package/gsd-core/bin/lib/smart-entry.cjs +35 -11
  84. package/gsd-core/bin/lib/state-contract.cjs +39 -15
  85. package/gsd-core/bin/lib/state-document.cjs +344 -27
  86. package/gsd-core/bin/lib/state-transition.cjs +156 -32
  87. package/gsd-core/bin/lib/state.cjs +527 -186
  88. package/gsd-core/bin/lib/surface.cjs +9 -0
  89. package/gsd-core/bin/lib/task-command-router.cjs +5 -25
  90. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  91. package/gsd-core/bin/lib/template.cjs +2 -2
  92. package/gsd-core/bin/lib/validate.cjs +3 -2
  93. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  94. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  95. package/gsd-core/bin/lib/verification.cjs +96 -9
  96. package/gsd-core/bin/lib/verify.cjs +4 -2
  97. package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
  98. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  99. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  100. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  101. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  102. package/gsd-core/references/cicd-review-patterns.md +45 -0
  103. package/gsd-core/references/git-integration.md +11 -0
  104. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  105. package/gsd-core/references/necessity-review.md +48 -0
  106. package/gsd-core/references/planning-config.md +2 -0
  107. package/gsd-core/references/tdd.md +2 -2
  108. package/gsd-core/templates/DEBUG.md +16 -0
  109. package/gsd-core/templates/config.json +2 -1
  110. package/gsd-core/templates/verification-report.md +1 -1
  111. package/gsd-core/workflows/audit-fix.md +2 -1
  112. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  113. package/gsd-core/workflows/code-review-fix.md +46 -5
  114. package/gsd-core/workflows/code-review.md +64 -7
  115. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  116. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  117. package/gsd-core/workflows/complete-milestone.md +37 -150
  118. package/gsd-core/workflows/diagnose-issues.md +29 -5
  119. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  120. package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
  121. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  122. package/gsd-core/workflows/discuss-phase.md +6 -10
  123. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  124. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
  126. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
  127. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  128. package/gsd-core/workflows/execute-phase.md +10 -6
  129. package/gsd-core/workflows/execute-plan.md +1 -1
  130. package/gsd-core/workflows/health.md +2 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
  133. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  134. package/gsd-core/workflows/plan-phase.md +14 -14
  135. package/gsd-core/workflows/pr-branch.md +2 -0
  136. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  137. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  138. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  139. package/gsd-core/workflows/quick.md +1 -1
  140. package/gsd-core/workflows/reapply-patches.md +104 -0
  141. package/gsd-core/workflows/review.md +70 -154
  142. package/gsd-core/workflows/section-manifest.json +5 -0
  143. package/gsd-core/workflows/secure-phase.md +1 -1
  144. package/gsd-core/workflows/settings-advanced.md +57 -9
  145. package/gsd-core/workflows/settings.md +1 -0
  146. package/gsd-core/workflows/ship.md +15 -4
  147. package/gsd-core/workflows/spec-phase.md +3 -6
  148. package/gsd-core/workflows/verify-work.md +4 -3
  149. package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
  150. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  151. package/hooks/dist/gsd-graphify-update.sh +6 -0
  152. package/hooks/dist/gsd-path-export.js +95 -0
  153. package/hooks/dist/gsd-secret-read-guard.js +172 -39
  154. package/hooks/dist/gsd-statusline.js +37 -3
  155. package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
  156. package/hooks/dist/gsd-workflow-guard.js +3 -2
  157. package/hooks/dist/gsd-worktree-path-guard.js +29 -2
  158. package/hooks/dist/lib/git-probe.js +16 -0
  159. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  160. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  161. package/hooks/gsd-agent-isolation-guard.js +11 -1
  162. package/hooks/gsd-cursor-subagent-start.js +6 -2
  163. package/hooks/gsd-graphify-update.sh +6 -0
  164. package/hooks/gsd-path-export.js +95 -0
  165. package/hooks/gsd-secret-read-guard.js +172 -39
  166. package/hooks/gsd-statusline.js +37 -3
  167. package/hooks/gsd-windsurf-pre-write.js +29 -2
  168. package/hooks/gsd-workflow-guard.js +3 -2
  169. package/hooks/gsd-worktree-path-guard.js +29 -2
  170. package/hooks/hooks.json +1 -0
  171. package/hooks/lib/git-probe.js +16 -0
  172. package/hooks/lib/isolation-sentinel.js +44 -0
  173. package/hooks/managed-hooks-registry.cjs +1 -0
  174. package/package.json +15 -8
  175. package/pi/gsd.cjs +1047 -10
  176. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  177. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  178. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  179. package/pi/package-agents/gsd-code-fixer.md +774 -0
  180. package/pi/package-agents/gsd-code-reviewer.md +441 -0
  181. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  182. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  183. package/pi/package-agents/gsd-debugger.md +1274 -0
  184. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  185. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  186. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  187. package/pi/package-agents/gsd-doc-writer.md +626 -0
  188. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  189. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  190. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  191. package/pi/package-agents/gsd-eval-planner.md +149 -0
  192. package/pi/package-agents/gsd-executor.md +898 -0
  193. package/pi/package-agents/gsd-framework-selector.md +159 -0
  194. package/pi/package-agents/gsd-integration-checker.md +476 -0
  195. package/pi/package-agents/gsd-intel-updater.md +338 -0
  196. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  197. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  198. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  199. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  200. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  201. package/pi/package-agents/gsd-planner.md +1003 -0
  202. package/pi/package-agents/gsd-project-researcher.md +611 -0
  203. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  204. package/pi/package-agents/gsd-roadmapper.md +779 -0
  205. package/pi/package-agents/gsd-security-auditor.md +171 -0
  206. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  207. package/pi/package-agents/gsd-ui-checker.md +417 -0
  208. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  209. package/pi/package-agents/gsd-user-profiler.md +174 -0
  210. package/pi/package-agents/gsd-verifier.md +963 -0
  211. package/pi/package-agents/manifest.json +39 -0
  212. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  213. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  214. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  215. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  216. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  217. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  218. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  219. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  220. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  221. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  222. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  223. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  224. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  225. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  226. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  227. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  228. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  229. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  230. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  231. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  232. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  233. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  234. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  235. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  236. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  237. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  238. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  239. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  240. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  241. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  242. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  243. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  244. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  245. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  246. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  247. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  248. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  249. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  250. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  251. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  252. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  253. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  254. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  255. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  256. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  257. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  258. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  259. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  260. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  261. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  262. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  263. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  264. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  265. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  266. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  267. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  268. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  269. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  270. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  271. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  272. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  273. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  274. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  275. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  276. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  277. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  278. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  279. package/pi/package-skills/gsd-update/SKILL.md +54 -0
  280. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  281. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  282. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  283. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  284. package/scripts/build-hooks.js +4 -0
  285. package/scripts/ci-next-health.cjs +246 -23
  286. package/scripts/command-contract-helpers.cjs +1 -1
  287. package/scripts/docs-guard-registry.cjs +22 -7
  288. package/scripts/gen-features.cjs +14 -2
  289. package/scripts/gen-health-docs.cjs +13 -1
  290. package/scripts/gen-pi-package-skills.cjs +217 -0
  291. package/scripts/lib/drift-scan.cjs +27 -0
  292. package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
  293. package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
  294. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  295. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  296. package/scripts/lint-retired-runtime-name.cjs +5 -0
  297. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  298. package/scripts/mutation-matrix.cjs +23 -1
  299. package/scripts/npm-audit-baseline.cjs +41 -7
  300. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  301. package/scripts/select-docs-guards.cjs +1 -1
  302. package/scripts/self-test-report-policy.cjs +350 -0
  303. package/scripts/sync-next-version.cjs +38 -6
  304. package/skills/gsd-update/SKILL.md +5 -2
  305. package/vscode/package.json +1 -1
  306. package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
  307. package/scripts/diagnose-validate-commit-phases.cjs +0 -348
  308. package/scripts/xtrace-phase-timing.cjs +0 -205
@@ -58,6 +58,7 @@ exports.DOCS_PARITY_VIOLATION = exports.LANE_SLUG_RE = exports.PARITY_VIOLATION
58
58
  exports.mergeReviewerLanes = mergeReviewerLanes;
59
59
  exports.checkReviewerLaneParity = checkReviewerLaneParity;
60
60
  exports.checkReviewerDocsParity = checkReviewerDocsParity;
61
+ const text_lines_cjs_1 = require("./text-lines.cjs");
61
62
  /**
62
63
  * Argv placeholders (Phase 5b, #2799).
63
64
  *
@@ -134,6 +135,8 @@ exports.REVIEWER_LANES = Object.freeze([
134
135
  evidenceClass: 'source-grounded',
135
136
  requiresBinaries: [],
136
137
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.claude',
138
+ contextWindowTokens: 200000,
139
+ contextWindowConfigKey: 'review.context_window_tokens.claude',
137
140
  modelConfigKey: 'review.models.claude',
138
141
  effortConfigKey: 'review.effort.claude',
139
142
  defaultEffort: 'high',
@@ -165,6 +168,8 @@ exports.REVIEWER_LANES = Object.freeze([
165
168
  evidenceClass: 'source-grounded',
166
169
  requiresBinaries: [],
167
170
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.codex',
171
+ contextWindowTokens: 272000,
172
+ contextWindowConfigKey: 'review.context_window_tokens.codex',
168
173
  modelConfigKey: 'review.models.codex',
169
174
  effortConfigKey: 'review.effort.codex',
170
175
  defaultEffort: 'high',
@@ -193,6 +198,8 @@ exports.REVIEWER_LANES = Object.freeze([
193
198
  evidenceClass: 'diff-only',
194
199
  requiresBinaries: [],
195
200
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.coderabbit',
201
+ contextWindowTokens: null,
202
+ contextWindowConfigKey: null,
196
203
  // Accepts no model flag at all (review.md:367) — not merely "none configured".
197
204
  modelConfigKey: null,
198
205
  effortConfigKey: null,
@@ -223,6 +230,8 @@ exports.REVIEWER_LANES = Object.freeze([
223
230
  // stock Windows/Git-Bash (#2589) — is no longer a prerequisite for this lane.
224
231
  requiresBinaries: [],
225
232
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.opencode',
233
+ contextWindowTokens: null,
234
+ contextWindowConfigKey: 'review.context_window_tokens.opencode',
226
235
  modelConfigKey: 'review.models.opencode',
227
236
  effortConfigKey: 'review.effort.opencode',
228
237
  defaultEffort: 'high',
@@ -249,6 +258,8 @@ exports.REVIEWER_LANES = Object.freeze([
249
258
  evidenceClass: 'source-grounded',
250
259
  requiresBinaries: [],
251
260
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.qwen',
261
+ contextWindowTokens: 131072,
262
+ contextWindowConfigKey: 'review.context_window_tokens.qwen',
252
263
  modelConfigKey: null,
253
264
  effortConfigKey: null,
254
265
  defaultEffort: null,
@@ -277,6 +288,8 @@ exports.REVIEWER_LANES = Object.freeze([
277
288
  evidenceClass: 'source-grounded',
278
289
  requiresBinaries: [],
279
290
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.cursor',
291
+ contextWindowTokens: null,
292
+ contextWindowConfigKey: 'review.context_window_tokens.cursor',
280
293
  // #3653: cursor-agent exposes --model (204 selectable models); wired the same as codex.
281
294
  modelConfigKey: 'review.models.cursor',
282
295
  effortConfigKey: null,
@@ -313,6 +326,8 @@ exports.REVIEWER_LANES = Object.freeze([
313
326
  // Phase 5b: the handler reads the transcript with JSON.parse per line, not `jq`.
314
327
  requiresBinaries: [],
315
328
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.antigravity',
329
+ contextWindowTokens: null,
330
+ contextWindowConfigKey: 'review.context_window_tokens.antigravity',
316
331
  // NOT `review.models.antigravity` — the shipped key is `review.models.agy` (review.md:291) and
317
332
  // Phase 4 federated it under that name. This lane is why the key is declared, not derived.
318
333
  modelConfigKey: 'review.models.agy',
@@ -347,6 +362,8 @@ exports.REVIEWER_LANES = Object.freeze([
347
362
  // neither `jq` nor `curl` is a prerequisite any more (#2589's stated Windows hazard).
348
363
  requiresBinaries: [],
349
364
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.ollama',
365
+ contextWindowTokens: null,
366
+ contextWindowConfigKey: 'review.context_window_tokens.ollama',
350
367
  modelConfigKey: 'review.models.ollama',
351
368
  effortConfigKey: null,
352
369
  defaultEffort: null,
@@ -377,6 +394,8 @@ exports.REVIEWER_LANES = Object.freeze([
377
394
  evidenceClass: 'source-grounded',
378
395
  requiresBinaries: [],
379
396
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.lm_studio',
397
+ contextWindowTokens: null,
398
+ contextWindowConfigKey: 'review.context_window_tokens.lm_studio',
380
399
  modelConfigKey: 'review.models.lm_studio',
381
400
  effortConfigKey: null,
382
401
  defaultEffort: null,
@@ -407,6 +426,8 @@ exports.REVIEWER_LANES = Object.freeze([
407
426
  evidenceClass: 'source-grounded',
408
427
  requiresBinaries: [],
409
428
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.llama_cpp',
429
+ contextWindowTokens: null,
430
+ contextWindowConfigKey: 'review.context_window_tokens.llama_cpp',
410
431
  modelConfigKey: 'review.models.llama_cpp',
411
432
  effortConfigKey: null,
412
433
  defaultEffort: null,
@@ -455,6 +476,8 @@ exports.REVIEWER_LANES = Object.freeze([
455
476
  evidenceClass: 'source-grounded',
456
477
  requiresBinaries: [],
457
478
  promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.kimi-code',
479
+ contextWindowTokens: 262144,
480
+ contextWindowConfigKey: 'review.context_window_tokens.kimi-code',
458
481
  modelConfigKey: 'review.models.kimi-code',
459
482
  effortConfigKey: null,
460
483
  defaultEffort: null,
@@ -576,6 +599,7 @@ exports.PARITY_VIOLATION = Object.freeze({
576
599
  REGISTRY_LANE_UNDECLARED: 'registry_lane_undeclared',
577
600
  DESCRIPTOR_LANE_NOT_IN_REGISTRY: 'descriptor_lane_not_in_registry',
578
601
  BESPOKE_LEG_PRESENT: 'bespoke_leg_present',
602
+ AVAILABILITY_FALLBACK_MISMATCH: 'availability_fallback_mismatch',
579
603
  DUPLICATE_SLUG: 'duplicate_slug',
580
604
  DUPLICATE_FLAG: 'duplicate_flag',
581
605
  DUPLICATE_SECTION: 'duplicate_section',
@@ -607,6 +631,10 @@ const LEG_MARKER_RE = /<!--\s*reviewer-lane:\s*([a-z0-9_-]+)\s*-->/g;
607
631
  * design principle of this module.
608
632
  */
609
633
  exports.LANE_SLUG_RE = /^[a-z0-9][a-z0-9_-]*$/;
634
+ /** The workflow's availability call: `gsd_run review-lane availability ...` at command position. */
635
+ const AVAILABILITY_CALL_RE = /^\s*gsd_run review-lane availability(?:\s|$)/;
636
+ /** One `<slug>:missing` row in the printf fallback; slug is LANE_SLUG_RE's grammar capped at 32 chars. */
637
+ const AVAILABILITY_FALLBACK_ROW_RE = /(?:'|\\n)([a-z0-9][a-z0-9_-]{0,31}):missing/g;
610
638
  /** Bounds of the step a bespoke leg marker could appear inside. */
611
639
  const INVOKE_STEP_RE = /<step name="invoke_reviewers">([\s\S]*?)<\/step>/;
612
640
  function sliceStep(workflowText, re) {
@@ -627,8 +655,10 @@ function countOccurrences(haystack, re) {
627
655
  }
628
656
  /**
629
657
  * Bidirectional parity across three surfaces: the descriptor, the roster
630
- * (`KNOWN_REVIEWER_SLUGS`), and the generated capability **registry** — plus one anti-parity
631
- * assertion against the workflow.
658
+ * (`KNOWN_REVIEWER_SLUGS`), and the generated capability **registry** — plus two assertions
659
+ * against the workflow: the anti-parity check, and the availability-fallback check (the workflow
660
+ * makes the availability call, has a fallback row per lane, and the fallback slug set equals the
661
+ * openai-http lanes).
632
662
  *
633
663
  * **Re-pointed by Phase 5b (#2799).** Through Phase 5a this function also required a literal
634
664
  * `<!-- reviewer-lane: <slug> -->` per lane inside `invoke_reviewers` and a literal
@@ -671,6 +701,7 @@ function checkReviewerLaneParity(input) {
671
701
  const seenSlug = new Set();
672
702
  const seenFlag = new Set();
673
703
  const seenSection = new Set();
704
+ const httpSlugs = new Set();
674
705
  // The declared parameter type says `ReviewerLane[]`, but this function is a
675
706
  // trust boundary — narrow from `unknown` rather than believing the annotation.
676
707
  const rawLanes = Array.isArray(descriptor) ? descriptor : [];
@@ -689,6 +720,8 @@ function checkReviewerLaneParity(input) {
689
720
  if (seenSlug.has(slug))
690
721
  add(exports.PARITY_VIOLATION.DUPLICATE_SLUG, slug);
691
722
  seenSlug.add(slug);
723
+ if (lane.transport === 'openai-http')
724
+ httpSlugs.add(slug);
692
725
  const flags = Array.isArray(lane.flags) ? lane.flags : [];
693
726
  for (const flag of flags) {
694
727
  if (typeof flag !== 'string')
@@ -740,6 +773,28 @@ function checkReviewerLaneParity(input) {
740
773
  for (const slug of markerSlugs.keys()) {
741
774
  add(exports.PARITY_VIOLATION.BESPOKE_LEG_PRESENT, slug);
742
775
  }
776
+ // --- the availability call's `||` fallback must name exactly the openai-http lanes ---
777
+ //
778
+ // review.md prints these rows only when an older gsd-tools lacks `review-lane availability`, so
779
+ // they are a second hand-kept copy of the roster. Checked only when the call is present.
780
+ // Anchored to the command position so a comment or prose mention cannot shadow the real call;
781
+ // a missing call is itself a violation (otherwise deleting it would silently skip this check).
782
+ const availabilityLine = (0, text_lines_cjs_1.splitLines)(workflowText).find((line) => AVAILABILITY_CALL_RE.test(line));
783
+ if (availabilityLine === undefined) {
784
+ add(exports.PARITY_VIOLATION.AVAILABILITY_FALLBACK_MISMATCH, 'no-call');
785
+ }
786
+ else {
787
+ if (!availabilityLine.includes('review-lane availability ||')) {
788
+ add(exports.PARITY_VIOLATION.AVAILABILITY_FALLBACK_MISMATCH, 'no-fallback');
789
+ }
790
+ const fallback = new Set(Array.from(availabilityLine.matchAll(AVAILABILITY_FALLBACK_ROW_RE), (m) => m[1]));
791
+ for (const slug of fallback)
792
+ if (!httpSlugs.has(slug))
793
+ add(exports.PARITY_VIOLATION.AVAILABILITY_FALLBACK_MISMATCH, slug);
794
+ for (const slug of httpSlugs)
795
+ if (!fallback.has(slug))
796
+ add(exports.PARITY_VIOLATION.AVAILABILITY_FALLBACK_MISMATCH, slug);
797
+ }
743
798
  return { ok: violations.length === 0, violations };
744
799
  }
745
800
  /* ------------------------------------------------------------------ *
@@ -23,7 +23,7 @@
23
23
  * third-party overlay manifests (ADR-2782 D4).
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.LANE_UNAVAILABLE = void 0;
26
+ exports.CONTEXT_WINDOW_HEADROOM = exports.LANE_UNAVAILABLE = void 0;
27
27
  exports.configString = configString;
28
28
  exports.normalizeHost = normalizeHost;
29
29
  exports.resolveLaneEffort = resolveLaneEffort;
@@ -33,7 +33,10 @@ exports.isEmptyReview = isEmptyReview;
33
33
  exports.fileRefPrompt = fileRefPrompt;
34
34
  exports.applyPromptOverride = applyPromptOverride;
35
35
  exports.artifactPaths = artifactPaths;
36
- exports.resolveLaneBudget = resolveLaneBudget;
36
+ exports.resolveLaneCap = resolveLaneCap;
37
+ exports.checkPromptAgainstCap = checkPromptAgainstCap;
38
+ exports.refuseIfOverCap = refuseIfOverCap;
39
+ exports.renderPromptRefusalStub = renderPromptRefusalStub;
37
40
  exports.resolveLanePlan = resolveLanePlan;
38
41
  const review_lane_descriptor_cjs_1 = require("./review-lane-descriptor.cjs");
39
42
  /* ------------------------------------------------------------------ *
@@ -60,8 +63,12 @@ exports.LANE_UNAVAILABLE = Object.freeze({
60
63
  PROBE_TIMEOUT: 'probe_timeout',
61
64
  HOST_UNREACHABLE: 'host_unreachable',
62
65
  EGRESS_HOST_CHANGED: 'egress_host_changed',
63
- BUDGET_TOO_SMALL: 'budget_too_small',
64
- BUDGET_TOOL_FAILED: 'budget_tool_failed',
66
+ /**
67
+ * #4559: the prompt a lane would receive is larger than its prompt cap (an explicit cap, or the
68
+ * default derived from the lane's context window). The lane is REFUSED — never trimmed, because a
69
+ * silently truncated plan set is a review of something other than the plans.
70
+ */
71
+ PROMPT_EXCEEDS_CONTEXT: 'prompt_exceeds_context',
65
72
  });
66
73
  /** Every handler name the runner can dispatch. Mirrors `LaneHandler` (D6's closed enum). */
67
74
  const KNOWN_HANDLERS = new Set(['antigravity', 'openai-compatible', 'opencode']);
@@ -233,7 +240,8 @@ function fileRefPrompt(promptPath, repoRoot) {
233
240
  /**
234
241
  * Re-point a resolved plan at a different prompt file (`review-lane invoke --prompt-file`, #5049).
235
242
  *
236
- * The override carries a budget-trimmed prompt, so it has to land wherever THIS lane actually
243
+ * The override carries a caller-substituted prompt (a section subset, say; nothing trims a prompt
244
+ * to fit a lane since #4559), so it has to land wherever THIS lane actually
237
245
  * reads its prompt, and that differs by channel:
238
246
  * - `stdin`: the runner streams the file `plan.stdin` names.
239
247
  * - `argv-file-ref`: the prompt PATH is embedded in a sentence in argv (`fileRefPrompt`), already
@@ -279,25 +287,124 @@ function artifactPaths(runDir, slug) {
279
287
  };
280
288
  }
281
289
  /**
282
- * Per-lane prompt budget (#2797 semantics, preserved exactly).
290
+ * Fraction of a lane's declared context window a prompt may occupy (#4559). The remaining 35%
291
+ * absorbs the system prompt, the reply, and `estimateTokens` being a chars/4 estimate rather than
292
+ * a tokenizer (code tokenizes denser than prose).
293
+ */
294
+ exports.CONTEXT_WINDOW_HEADROOM = 0.65;
295
+ /**
296
+ * Per-lane prompt cap (#2797 semantics, extended by #4559).
283
297
  *
284
- * `-1` is the UNSET sentinel and falls back to the central `review.max_prompt_tokens`, because
285
- * `0` is a legitimate value meaning "do not trim this lane". Treating 0 as unset would silently
286
- * switch a user who deliberately disabled trimming onto the global budget.
298
+ * Precedence: per-lane `promptBudgetKey` → global `review.max_prompt_tokens` → the default derived
299
+ * from the lane's context window (`floor(window * CONTEXT_WINDOW_HEADROOM)`). `-1` is the UNSET
300
+ * sentinel for the per-lane key, because `0` is a legitimate value meaning "no cap for this lane".
301
+ * An explicit `0` at either explicit level also turns the derived default off.
302
+ *
303
+ * The window is the operator's `contextWindowConfigKey` value when that is a positive finite number
304
+ * (`0` disables the derived cap), else the lane's declared `contextWindowTokens`. Both are narrowed
305
+ * from `unknown` — overlay lanes and config values cross a trust boundary — and anything that is not
306
+ * a positive finite number narrows to "unset".
287
307
  *
288
308
  * Single source of truth: `gsd-core/bin/gsd-tools.cjs`'s `review-lane plan`/`invoke` and
289
- * `src/reviewer-step-dispatch.cts`'s `dispatchReviewerLanes` both resolve a lane's budget through
309
+ * `src/reviewer-step-dispatch.cts`'s `dispatchReviewerLanes` both resolve a lane's cap through
290
310
  * this function rather than each carrying their own copy (#4209 R3 — two verbatim copies drift).
291
311
  */
292
- function resolveLaneBudget(lane, configGet) {
293
- if (!lane.promptBudgetKey)
294
- return null;
295
- const per = configGet(lane.promptBudgetKey);
312
+ function resolveLaneCap(lane, configGet) {
313
+ // A spawn lane whose prompt channel is `none` (coderabbit) is fed no prompt, so there is nothing
314
+ // to cap: no key — explicit, global or derived — may ever refuse it (#4559, design row 18).
315
+ const laneRaw = lane;
316
+ if (laneRaw?.transport === 'spawn' && laneRaw.invoke?.promptChannel === 'none') {
317
+ return { cap: null, source: 'none', contextWindowTokens: null };
318
+ }
296
319
  const isNum = (v) => typeof v === 'number' && Number.isFinite(v);
297
- if (isNum(per) && per !== -1)
298
- return per;
299
- const global = configGet('review.max_prompt_tokens');
300
- return isNum(global) ? global : null;
320
+ const isPositive = (v) => isNum(v) && v > 0;
321
+ const raw = lane;
322
+ const budgetKey = typeof raw?.promptBudgetKey === 'string' && raw.promptBudgetKey ? raw.promptBudgetKey : null;
323
+ if (budgetKey) {
324
+ const per = configGet(budgetKey);
325
+ if (isNum(per) && per !== -1)
326
+ return { cap: per, source: 'lane', contextWindowTokens: null };
327
+ const global = configGet('review.max_prompt_tokens');
328
+ if (isNum(global))
329
+ return { cap: global, source: 'global', contextWindowTokens: null };
330
+ }
331
+ const windowKey = typeof raw?.contextWindowConfigKey === 'string' && raw.contextWindowConfigKey ? raw.contextWindowConfigKey : null;
332
+ const override = windowKey ? configGet(windowKey) : undefined;
333
+ if (override === 0)
334
+ return { cap: null, source: 'none', contextWindowTokens: null };
335
+ const windowTokens = isPositive(override)
336
+ ? override
337
+ : isPositive(raw?.contextWindowTokens)
338
+ ? raw.contextWindowTokens
339
+ : null;
340
+ if (windowTokens === null)
341
+ return { cap: null, source: 'none', contextWindowTokens: null };
342
+ return {
343
+ cap: Math.floor(windowTokens * exports.CONTEXT_WINDOW_HEADROOM),
344
+ source: 'context-window',
345
+ contextWindowTokens: windowTokens,
346
+ };
347
+ }
348
+ /**
349
+ * The ONE refuse-or-run decision (#4559), shared by `review-lane invoke` and `dispatchReviewerLanes`.
350
+ * Refuses only when a positive cap is STRICTLY exceeded: a prompt exactly at the cap runs, and a
351
+ * cap of `0` or `null` never refuses. `detail` names the estimate, the cap, where the cap came from
352
+ * and the key to change, so the refusal is actionable rather than a bare "too big".
353
+ */
354
+ function checkPromptAgainstCap(estimatedTokens, capInfo, lane) {
355
+ const { cap, source, contextWindowTokens } = capInfo;
356
+ if (cap === null || !(cap > 0) || !(estimatedTokens > cap))
357
+ return { ok: true };
358
+ const raw = lane;
359
+ const slug = typeof raw?.slug === 'string' ? raw.slug : '';
360
+ const budgetKey = typeof raw?.promptBudgetKey === 'string' && raw.promptBudgetKey ? raw.promptBudgetKey : null;
361
+ const windowKey = typeof raw?.contextWindowConfigKey === 'string' && raw.contextWindowConfigKey ? raw.contextWindowConfigKey : null;
362
+ // Remedy keys are the lane's DECLARED keys (an overlay lane's differ from the slug convention).
363
+ const remedy = source === 'lane'
364
+ ? `raise ${budgetKey ?? 'the lane prompt cap'} or set it to 0 to remove the cap`
365
+ : source === 'global'
366
+ ? `raise review.max_prompt_tokens${budgetKey ? ` or set ${budgetKey}` : ''}`
367
+ : windowKey
368
+ ? `raise ${windowKey} if this lane's model has a larger window, or set it to 0 to remove the cap`
369
+ : budgetKey
370
+ ? `this lane's context window is fixed by its declaration; set review.max_prompt_tokens or ${budgetKey} to override`
371
+ : `this lane's cap comes from its declared contextWindowTokens and the lane declares no key to override it`;
372
+ const windowNote = contextWindowTokens !== null ? `, ${contextWindowTokens}-token context window` : '';
373
+ return {
374
+ ok: false,
375
+ reason: exports.LANE_UNAVAILABLE.PROMPT_EXCEEDS_CONTEXT,
376
+ detail: `estimated ${estimatedTokens} tokens exceeds the ${cap}-token cap for lane '${slug}' ` +
377
+ `(source: ${source}${windowNote}); ${remedy}`,
378
+ };
379
+ }
380
+ /**
381
+ * Resolve the lane's cap and decide refuse-or-run in one pure step (#4559): `null` = run, otherwise
382
+ * the structured refusal. `review-lane invoke` and `dispatchReviewerLanes` both call this, so the
383
+ * two surfaces cannot disagree about whether, or how, a lane is refused.
384
+ */
385
+ function refuseIfOverCap(input) {
386
+ const capInfo = resolveLaneCap(input.lane, input.configGet);
387
+ const verdict = checkPromptAgainstCap(input.estimatedTokens, capInfo, input.lane);
388
+ if (verdict.ok)
389
+ return null;
390
+ return {
391
+ ok: false,
392
+ reason: verdict.reason,
393
+ detail: verdict.detail,
394
+ refusal: {
395
+ estimatedTokens: input.estimatedTokens,
396
+ cap: capInfo.cap,
397
+ capSource: capInfo.source,
398
+ contextWindowTokens: capInfo.contextWindowTokens,
399
+ },
400
+ };
401
+ }
402
+ /**
403
+ * The text written to a refused lane's review file (#4559). The marker
404
+ * `review did not run: prompt exceeds` is stable: `review.md`'s skip gate and coverage loop key on it.
405
+ */
406
+ function renderPromptRefusalStub(slug, detail) {
407
+ return `${slug} review did not run: prompt exceeds this lane's context cap (${detail})\n`;
301
408
  }
302
409
  /* ------------------------------------------------------------------ *
303
410
  * Resolution
@@ -32,6 +32,7 @@ exports.parseModelBanner = parseModelBanner;
32
32
  exports.parseTranscriptModel = parseTranscriptModel;
33
33
  exports.checkEgressHost = checkEgressHost;
34
34
  exports.probeLane = probeLane;
35
+ exports.probeHttpLanes = probeHttpLanes;
35
36
  exports.writeReviewOrStub = writeReviewOrStub;
36
37
  exports.emptyOutputDiagnosis = emptyOutputDiagnosis;
37
38
  exports.handleOpencodeOutput = handleOpencodeOutput;
@@ -318,14 +319,18 @@ async function probeLane(plan, deps) {
318
319
  // Only a STRING config value names a host. `String(unknown)` would turn an object into the
319
320
  // literal '[object Object]' and probe that as a URL — a nonsense destination reported as
320
321
  // "unreachable" rather than as the misconfiguration it is.
322
+ // For openai-http, `plan.host` is already resolved by `resolveLanePlan` (config → default,
323
+ // unset sentinels like "null" handled) — probe the destination the run would use.
321
324
  const configured = deps.configGet(probe.hostConfigKey);
322
- const base = (0, review_lane_invocation_cjs_1.normalizeHost)((typeof configured === 'string' ? configured : '') ||
323
- (plan.transport === 'openai-http' ? plan.host : ''));
325
+ const base = (0, review_lane_invocation_cjs_1.normalizeHost)((plan.transport === 'openai-http' ? plan.host : '') || (typeof configured === 'string' ? configured : ''));
324
326
  if (!base) {
325
327
  return { available: false, reason: review_lane_invocation_cjs_1.LANE_UNAVAILABLE.HOST_UNREACHABLE, detail: 'no host resolved' };
326
328
  }
327
- const r = await deps.httpJson(`${base}${probe.path}`, { method: 'GET', timeoutMs: probe.timeoutMs });
328
- return r.ok
329
+ // D7: reachable ⇒ available. Any HTTP answer (401 behind an auth proxy, 404, 3xx) proves a
330
+ // server is there; only a transport failure (status 0) does not. Redirects are not followed:
331
+ // a probe must not issue a request to a host the descriptor never named.
332
+ const r = await deps.httpJson(`${base}${probe.path}`, { method: 'GET', timeoutMs: probe.timeoutMs, probe: true });
333
+ return r.status > 0
329
334
  ? { available: true }
330
335
  : {
331
336
  available: false,
@@ -341,6 +346,25 @@ async function probeLane(plan, deps) {
341
346
  };
342
347
  }
343
348
  }
349
+ /**
350
+ * `<slug>:available|missing` rows for the openai-http lanes in `lanes` (all, or those in `selected`).
351
+ * Prerequisite binaries are not folded in: the caller reports `jq` separately. An unresolvable lane
352
+ * reads `missing` and its reason is returned in `warnings` rather than swallowed.
353
+ */
354
+ async function probeHttpLanes(lanes, selected, resolve, deps) {
355
+ // Probes run concurrently (each has its own timeout); Promise.all keeps descriptor order, and
356
+ // warnings are collected from the results afterwards so their order never depends on resolve timing.
357
+ const results = await Promise.all(lanes
358
+ .filter((l) => l.transport === 'openai-http' && (selected.length === 0 || selected.includes(l.slug)))
359
+ .map(async (lane) => {
360
+ const r = resolve(lane);
361
+ const up = r.ok && (await probeLane({ ...r.plan, requiresBinaries: [] }, deps)).available;
362
+ return { row: `${lane.slug}:${up ? 'available' : 'missing'}`, warning: r.ok ? null : `${lane.slug}: ${r.detail}` };
363
+ }));
364
+ const rows = results.map((x) => x.row);
365
+ const warnings = results.flatMap((x) => (x.warning === null ? [] : [x.warning]));
366
+ return { rows, warnings };
367
+ }
344
368
  /* ------------------------------------------------------------------ *
345
369
  * Empty-output policy (#2494 / #2605 / #2794)
346
370
  * ------------------------------------------------------------------ */
@@ -940,6 +964,11 @@ async function runOpenAiCompatible(plan, promptText, deps) {
940
964
  let model = plan.model;
941
965
  if (!model && plan.modelsUrl) {
942
966
  const listed = await deps.httpJson(plan.modelsUrl, { method: 'GET', timeoutMs: 2_000 });
967
+ // A redirect is a lane failure, not "discovery found nothing" (#5433): the host answered with
968
+ // somewhere else to go, so the configured URL is wrong and the completions request would be too.
969
+ if (listed.status >= 300 && listed.status < 400 && listed.error) {
970
+ return { review: '', rawBody: listed.error, model: recordedModel(plan.fallbackModel, exports.MODEL_SOURCE.REQUESTED) };
971
+ }
943
972
  if (listed.ok) {
944
973
  try {
945
974
  const parsed = JSON.parse(listed.body);
@@ -29,8 +29,9 @@
29
29
  *
30
30
  * The bounded source-review prompt built here is METADATA ONLY — repository root, canonical
31
31
  * file paths, review depth, base SHA, and four fixed prohibitions. It never embeds file
32
- * contents. If the assembled prompt exceeds a lane's resolved budget, that lane's dispatch
33
- * hard-fails before `invoke` runs for it — no silent truncation of the file list.
32
+ * contents. If the assembled prompt exceeds a lane's resolved prompt cap (an explicit cap or the
33
+ * default derived from its context window, #4559), that lane's dispatch hard-fails with reason
34
+ * `prompt_exceeds_context` before `invoke` runs for it — no silent truncation of the file list.
34
35
  */
35
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -125,9 +126,8 @@ function validatePaths(repoRoot, paths) {
125
126
  }
126
127
  return { ok: true };
127
128
  }
128
- // `resolveLaneBudget` (review-lane-invocation.cjs) resolves the number; `null` and a resolved
129
- // `0` both mean unbounded (#2797) — the caller's overflow check must test both `!== null` and
130
- // `!== 0`. See the call site below.
129
+ // `resolveLaneCap` (review-lane-invocation.cjs) resolves the cap; `null` and a resolved `0` both
130
+ // mean unbounded (#2797), and `checkPromptAgainstCap` owns that test. See the call site below.
131
131
  /**
132
132
  * One-line depth definition for an external reviewer lane, condensed from `<depth_levels>` in
133
133
  * `agents/gsd-code-reviewer.md` (#4209 review: a bare `quick`/`standard`/`deep` label means
@@ -296,13 +296,15 @@ async function dispatchReviewerLanes(input, deps) {
296
296
  anyFailed = true;
297
297
  continue;
298
298
  }
299
- const budget = (0, review_lane_invocation_cjs_1.resolveLaneBudget)(lane, configGet);
300
- if (budget !== null && budget !== 0 && estimatedTokens > budget) {
299
+ // #4559: refuse-not-trim, through the same check `review-lane invoke` uses.
300
+ const refused = (0, review_lane_invocation_cjs_1.refuseIfOverCap)({ lane, estimatedTokens, configGet });
301
+ if (refused) {
301
302
  results.push({
302
303
  slug,
303
304
  ok: false,
304
- reason: 'budget_exceeded',
305
- detail: `estimated ${estimatedTokens} tokens exceeds resolved budget ${budget} for lane '${slug}'`,
305
+ reason: refused.reason,
306
+ detail: refused.detail,
307
+ refusal: refused.refusal,
306
308
  });
307
309
  anyFailed = true;
308
310
  continue;