supipowers 1.5.3 → 2.0.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 (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +131 -42
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +18 -8
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +129 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +264 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +221 -109
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +114 -13
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +1 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1399 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -1,5 +1,6 @@
1
- import { stripMarkdownCodeFence } from "../../text.js";
2
- import { runStructuredAgentSession } from "../ai-session.js";
1
+ import { parseStructuredOutput, runWithOutputValidation, type ReliabilityReporter } from "../../ai/structured-output.js";
2
+ import { renderSchemaText } from "../../ai/schema-text.js";
3
+ import { AiReviewOutputSchema, type AiReviewOutput } from "../contracts.js";
3
4
  import type {
4
5
  GateExecutionContext,
5
6
  GateIssue,
@@ -15,11 +16,7 @@ export interface AiReviewResult {
15
16
  metadata?: Record<string, unknown>;
16
17
  }
17
18
 
18
- interface AiReviewPayload {
19
- summary: string;
20
- issues: GateIssue[];
21
- recommendedStatus: AiReviewResult["status"];
22
- }
19
+ const AI_REVIEW_SCHEMA_TEXT = renderSchemaText(AiReviewOutputSchema);
23
20
 
24
21
  export function buildAiReviewPrompt(
25
22
  scopeFiles: string[],
@@ -42,8 +39,8 @@ export function buildAiReviewPrompt(
42
39
  "Files in scope:",
43
40
  files,
44
41
  "",
45
- "Return JSON only with this exact shape:",
46
- '{"summary":"string","issues":[{"severity":"error|warning|info","message":"string","file":"optional string","line":"optional number","detail":"optional string"}],"recommendedStatus":"passed|failed|blocked"}',
42
+ "Return JSON only matching this schema:",
43
+ AI_REVIEW_SCHEMA_TEXT,
47
44
  "",
48
45
  "Rules:",
49
46
  "- recommendedStatus must be 'failed' when you found actionable issues.",
@@ -52,42 +49,6 @@ export function buildAiReviewPrompt(
52
49
  ].join("\n");
53
50
  }
54
51
 
55
- function isGateIssue(value: unknown): value is GateIssue {
56
- return (
57
- typeof value === "object" &&
58
- value !== null &&
59
- (value as GateIssue).severity !== undefined &&
60
- ["error", "warning", "info"].includes((value as GateIssue).severity) &&
61
- typeof (value as GateIssue).message === "string" &&
62
- ((value as GateIssue).file === undefined || typeof (value as GateIssue).file === "string") &&
63
- ((value as GateIssue).line === undefined || typeof (value as GateIssue).line === "number") &&
64
- ((value as GateIssue).detail === undefined || typeof (value as GateIssue).detail === "string")
65
- );
66
- }
67
-
68
- function parseAiReviewPayload(raw: string): AiReviewPayload | null {
69
- try {
70
- const parsed = JSON.parse(stripMarkdownCodeFence(raw)) as Record<string, unknown>;
71
-
72
- if (
73
- typeof parsed.summary !== "string" ||
74
- !Array.isArray(parsed.issues) ||
75
- !parsed.issues.every(isGateIssue) ||
76
- !["passed", "failed", "blocked"].includes(String(parsed.recommendedStatus))
77
- ) {
78
- return null;
79
- }
80
-
81
- return {
82
- summary: parsed.summary,
83
- issues: parsed.issues,
84
- recommendedStatus: parsed.recommendedStatus as AiReviewPayload["recommendedStatus"],
85
- };
86
- } catch {
87
- return null;
88
- }
89
- }
90
-
91
52
  function buildBlockedResult(summary: string, metadata?: Record<string, unknown>): AiReviewResult {
92
53
  return {
93
54
  status: "blocked",
@@ -100,30 +61,31 @@ function buildBlockedResult(summary: string, metadata?: Record<string, unknown>)
100
61
  export async function runAiReview(
101
62
  context: Pick<GateExecutionContext, "cwd" | "scopeFiles" | "fileScope" | "createAgentSession" | "reviewModel">,
102
63
  depth: AiReviewDepth,
64
+ reliability?: ReliabilityReporter,
103
65
  ): Promise<AiReviewResult> {
104
- const sessionResult = await runStructuredAgentSession(context.createAgentSession, {
66
+ const result = await runWithOutputValidation<AiReviewOutput>(context.createAgentSession, {
105
67
  cwd: context.cwd,
106
68
  prompt: buildAiReviewPrompt(context.scopeFiles, context.fileScope, depth),
69
+ schema: AI_REVIEW_SCHEMA_TEXT,
70
+ parse: (raw) => parseStructuredOutput<AiReviewOutput>(raw, AiReviewOutputSchema),
107
71
  model: context.reviewModel?.model,
108
72
  thinkingLevel: context.reviewModel?.thinkingLevel ?? null,
109
73
  timeoutMs: 120_000,
74
+ reliability,
110
75
  });
111
76
 
112
- if (sessionResult.status !== "ok") {
113
- return buildBlockedResult(sessionResult.error);
114
- }
115
-
116
- const parsed = parseAiReviewPayload(sessionResult.finalText);
117
- if (!parsed) {
118
- return buildBlockedResult("AI review returned invalid JSON.", {
119
- rawOutput: sessionResult.finalText,
77
+ if (result.status === "blocked") {
78
+ return buildBlockedResult(result.error, {
79
+ depth,
80
+ attempts: result.attempts,
81
+ ...(result.rawOutputs.length > 0 ? { rawOutputs: result.rawOutputs } : {}),
120
82
  });
121
83
  }
122
84
 
123
85
  return {
124
- status: parsed.recommendedStatus,
125
- summary: parsed.summary,
126
- issues: parsed.issues,
127
- metadata: { depth },
86
+ status: result.output.recommendedStatus,
87
+ summary: result.output.summary,
88
+ issues: result.output.issues as GateIssue[],
89
+ metadata: { depth, attempts: result.attempts },
128
90
  };
129
91
  }
@@ -1,15 +1,38 @@
1
1
  import type {
2
2
  CommandGateConfig,
3
3
  CommandGateId,
4
+ CommandGateRun,
4
5
  GateDefinition,
5
6
  GateIssue,
6
7
  ProjectFacts,
8
+ ProjectFactsTarget,
9
+ WorkspaceTarget,
7
10
  } from "../../types.js";
8
11
  import { GATE_CONFIG_SCHEMAS } from "../registry.js";
9
12
 
10
- interface DetectedCommand {
13
+ const ARTIFACT_REF_RE = /artifact:\/\/[a-zA-Z0-9_-]+/g;
14
+
15
+ function extractArtifactRefs(...streams: string[]): string[] {
16
+ const seen = new Set<string>();
17
+ for (const stream of streams) {
18
+ if (!stream) continue;
19
+ const matches = stream.match(ARTIFACT_REF_RE);
20
+ if (!matches) continue;
21
+ for (const ref of matches) seen.add(ref);
22
+ }
23
+ return [...seen];
24
+ }
25
+
26
+ interface TargetDetectedCommand {
27
+ target: ProjectFactsTarget;
11
28
  command: string;
12
29
  confidence: "high" | "medium";
30
+ source: string;
31
+ }
32
+
33
+ interface DetectedCommandPlan {
34
+ runs: CommandGateRun[];
35
+ confidence: "high" | "medium";
13
36
  reason: string;
14
37
  }
15
38
 
@@ -26,64 +49,198 @@ function normalizeCommand(command: string | undefined): string | null {
26
49
  return trimmed ? trimmed : null;
27
50
  }
28
51
 
29
- function detectScriptByName(
30
- projectFacts: ProjectFacts,
52
+ function formatTargetLocation(target: Pick<ProjectFactsTarget, "kind" | "relativeDir">): string {
53
+ return target.kind === "root" ? "root" : target.relativeDir;
54
+ }
55
+
56
+ function describeRunSelector(target: CommandGateRun["target"]): string {
57
+ switch (target.scope) {
58
+ case "all-targets":
59
+ return "all targets";
60
+ case "root":
61
+ return "root target";
62
+ case "all-workspaces":
63
+ return "all workspace targets";
64
+ case "workspace":
65
+ return `workspace ${target.relativeDir}`;
66
+ }
67
+ }
68
+
69
+ function detectCommandInTarget(
70
+ target: ProjectFactsTarget,
31
71
  options: CommandGateOptions<CommandGateId>,
32
- ): DetectedCommand | null {
72
+ ): TargetDetectedCommand | null {
33
73
  for (const scriptName of options.scriptNames) {
34
- const command = normalizeCommand(projectFacts.packageScripts[scriptName]);
74
+ const command = normalizeCommand(target.packageScripts[scriptName]);
35
75
  if (!command) {
36
76
  continue;
37
77
  }
38
78
 
39
- // When a safety filter is defined, apply it even for exact name matches.
40
- // A repo with `"lint": "eslint . --fix"` must not be auto-configured.
41
79
  if (options.matchScript && !options.matchScript(scriptName, command)) {
42
80
  continue;
43
81
  }
44
82
 
45
83
  return {
84
+ target,
46
85
  command,
47
86
  confidence: "high",
48
- reason: `Detected package.json ${scriptName} script.`,
87
+ source: `package.json ${scriptName} script`,
49
88
  };
50
89
  }
51
90
 
52
- return null;
53
- }
54
-
55
- function detectScriptByHeuristic(
56
- projectFacts: ProjectFacts,
57
- options: CommandGateOptions<CommandGateId>,
58
- ): DetectedCommand | null {
59
91
  if (!options.matchScript) {
60
92
  return null;
61
93
  }
62
94
 
63
- for (const [scriptName, rawCommand] of Object.entries(projectFacts.packageScripts)) {
95
+ for (const [scriptName, rawCommand] of Object.entries(target.packageScripts)) {
64
96
  const command = normalizeCommand(rawCommand);
65
97
  if (!command || !options.matchScript(scriptName, command)) {
66
98
  continue;
67
99
  }
68
100
 
69
101
  return {
102
+ target,
70
103
  command,
71
104
  confidence: "medium",
72
- reason: `Detected package.json ${scriptName} script by command heuristic.`,
105
+ source: `package.json ${scriptName} script by command heuristic`,
73
106
  };
74
107
  }
75
108
 
76
109
  return null;
77
110
  }
78
111
 
112
+ function detectCommands(projectFacts: ProjectFacts, options: CommandGateOptions<CommandGateId>): TargetDetectedCommand[] {
113
+ return projectFacts.targets
114
+ .map((target) => detectCommandInTarget(target, options))
115
+ .filter((match): match is TargetDetectedCommand => match !== null);
116
+ }
117
+
118
+ function buildDetectedRuns(matches: TargetDetectedCommand[]): CommandGateRun[] {
119
+ const uniqueCommands = [...new Set(matches.map((match) => match.command))];
120
+ if (uniqueCommands.length === 1) {
121
+ return [{ command: uniqueCommands[0]!, target: { scope: "all-targets" } }];
122
+ }
123
+
124
+ const rootMatch = matches.find((match) => match.target.kind === "root");
125
+ const workspaceMatches = matches.filter((match) => match.target.kind === "workspace");
126
+ const runs: CommandGateRun[] = [];
127
+
128
+ if (rootMatch) {
129
+ runs.push({ command: rootMatch.command, target: { scope: "root" } });
130
+ }
131
+
132
+ if (workspaceMatches.length > 0) {
133
+ const workspaceCommands = [...new Set(workspaceMatches.map((match) => match.command))];
134
+ if (workspaceCommands.length === 1) {
135
+ runs.push({ command: workspaceCommands[0]!, target: { scope: "all-workspaces" } });
136
+ } else {
137
+ runs.push(
138
+ ...workspaceMatches.map((match) => ({
139
+ command: match.command,
140
+ target: { scope: "workspace", relativeDir: match.target.relativeDir } as const,
141
+ })),
142
+ );
143
+ }
144
+ }
145
+
146
+ return runs;
147
+ }
148
+
149
+ function describeDetectedPlan(
150
+ projectFacts: ProjectFacts,
151
+ options: CommandGateOptions<CommandGateId>,
152
+ matches: TargetDetectedCommand[],
153
+ runs: CommandGateRun[],
154
+ ): string {
155
+ if (projectFacts.targets.length === 1) {
156
+ return `Detected ${matches[0]!.source}.`;
157
+ }
158
+
159
+ if (runs.length === 1 && runs[0]?.target.scope === "all-targets") {
160
+ return `Detected ${options.label.toLowerCase()} command shared across all targets via per-target scripts.`;
161
+ }
162
+
163
+ if (
164
+ runs.length === 2
165
+ && runs.some((run) => run.target.scope === "root")
166
+ && runs.some((run) => run.target.scope === "all-workspaces")
167
+ ) {
168
+ return `Detected ${options.label.toLowerCase()} commands covering the root target and all workspace targets via per-target scripts.`;
169
+ }
170
+
171
+ if (runs.length === 1 && runs[0]?.target.scope === "all-workspaces") {
172
+ return `Detected ${options.label.toLowerCase()} command shared across all workspace targets via per-target scripts.`;
173
+ }
174
+
175
+ return `Detected ${options.label.toLowerCase()} commands covering every target via per-target scripts.`;
176
+ }
177
+
178
+ function detectCompleteCommandPlan(
179
+ projectFacts: ProjectFacts,
180
+ options: CommandGateOptions<CommandGateId>,
181
+ ): DetectedCommandPlan | null {
182
+ const matches = detectCommands(projectFacts, options);
183
+ if (matches.length !== projectFacts.targets.length) {
184
+ return null;
185
+ }
186
+
187
+ const runs = buildDetectedRuns(matches);
188
+ return {
189
+ runs,
190
+ confidence: matches.every((match) => match.confidence === "high") ? "high" : "medium",
191
+ reason: describeDetectedPlan(projectFacts, options, matches, runs),
192
+ };
193
+ }
194
+
195
+ function describeIncompleteCoverage(
196
+ projectFacts: ProjectFacts,
197
+ options: CommandGateOptions<CommandGateId>,
198
+ ): string | null {
199
+ if (projectFacts.targets.length <= 1) {
200
+ return null;
201
+ }
202
+
203
+ const matches = detectCommands(projectFacts, options);
204
+ if (matches.length === 0 || matches.length === projectFacts.targets.length) {
205
+ return null;
206
+ }
207
+
208
+ const targetLocations = matches.map((match) => formatTargetLocation(match.target));
209
+ const rootMatched = matches.some((match) => match.target.kind === "root");
210
+
211
+ if (!rootMatched && matches.every((match) => match.target.kind === "workspace")) {
212
+ return `Detected ${options.label.toLowerCase()} commands in workspace targets only (${targetLocations.join(", ")}), not in the root target. /supi:checks All also runs the root target, so this gate was not auto-configured.`;
213
+ }
214
+
215
+ return `Detected ${options.label.toLowerCase()} commands only in some targets (${targetLocations.join(", ")}). /supi:checks All runs every target, so this gate was not auto-configured.`;
216
+ }
217
+
218
+ function runMatchesTarget(run: CommandGateRun, target: WorkspaceTarget): boolean {
219
+ switch (run.target.scope) {
220
+ case "all-targets":
221
+ return true;
222
+ case "root":
223
+ return target.kind === "root";
224
+ case "all-workspaces":
225
+ return target.kind === "workspace";
226
+ case "workspace":
227
+ return target.kind === "workspace" && target.relativeDir === run.target.relativeDir;
228
+ }
229
+ }
230
+
79
231
  function createFailureDetail(label: string, exitCode: number, stdout: string, stderr: string): string {
80
232
  return stderr.trim() || stdout.trim() || `${label} command exited with code ${exitCode}.`;
81
233
  }
82
234
 
83
- function createFailureIssue(label: string, detail: string): GateIssue {
235
+ function createFailureIssue(
236
+ label: string,
237
+ detail: string,
238
+ target: WorkspaceTarget,
239
+ run: CommandGateRun,
240
+ ): GateIssue {
84
241
  return {
85
242
  severity: "error",
86
- message: `${label} command failed.`,
243
+ message: `${label} command failed for ${formatTargetLocation(target)} (${describeRunSelector(run.target)}).`,
87
244
  detail,
88
245
  };
89
246
  }
@@ -96,53 +253,99 @@ export function createCommandGate<TGateId extends CommandGateId>(
96
253
  description: options.description,
97
254
  configSchema: GATE_CONFIG_SCHEMAS[options.id],
98
255
  detect(projectFacts) {
99
- const detected =
100
- detectScriptByName(projectFacts, options) ?? detectScriptByHeuristic(projectFacts, options);
101
- if (!detected) {
256
+ const detectedPlan = detectCompleteCommandPlan(projectFacts, options);
257
+ if (detectedPlan) {
258
+ return {
259
+ suggestedConfig: {
260
+ enabled: true,
261
+ runs: detectedPlan.runs,
262
+ },
263
+ confidence: detectedPlan.confidence,
264
+ reason: detectedPlan.reason,
265
+ };
266
+ }
267
+
268
+ const incompleteCoverage = describeIncompleteCoverage(projectFacts, options);
269
+ if (!incompleteCoverage) {
102
270
  return null;
103
271
  }
104
272
 
105
273
  return {
106
- suggestedConfig: {
107
- enabled: true,
108
- command: detected.command,
109
- },
110
- confidence: detected.confidence,
111
- reason: detected.reason,
274
+ suggestedConfig: null,
275
+ confidence: "medium",
276
+ reason: incompleteCoverage,
112
277
  };
113
278
  },
114
279
  async run(context, config) {
115
- if (config.enabled !== true || typeof config.command !== "string") {
116
- throw new Error(`${options.id} gate requires an enabled config with a command.`);
280
+ if (config.enabled !== true || !Array.isArray(config.runs)) {
281
+ throw new Error(`${options.id} gate requires an enabled config with runs.`);
117
282
  }
118
283
 
119
- const result = await context.execShell(config.command, {
120
- cwd: context.cwd,
121
- timeout: 120_000,
122
- });
123
-
124
- if (result.code === 0) {
284
+ const matchingRuns = config.runs.filter((run) => runMatchesTarget(run, context.target));
285
+ if (matchingRuns.length === 0) {
125
286
  return {
126
287
  gate: options.id,
127
- status: "passed",
128
- summary: `${options.label} passed.`,
288
+ status: "skipped",
289
+ summary: `${options.label} skipped for ${formatTargetLocation(context.target)} — no configured run matches this target.`,
129
290
  issues: [],
130
291
  metadata: {
131
- command: config.command,
132
- exitCode: result.code,
292
+ target: context.target.relativeDir,
293
+ reason: "no-matching-runs",
133
294
  },
134
295
  };
135
296
  }
136
297
 
137
- const detail = createFailureDetail(options.label, result.code, result.stdout, result.stderr);
298
+ const executedRuns: Array<{
299
+ command: string;
300
+ target: CommandGateRun["target"];
301
+ exitCode: number;
302
+ artifactRefs?: string[];
303
+ }> = [];
304
+ for (const run of matchingRuns) {
305
+ const result = await context.execShell(run.command, {
306
+ cwd: context.cwd,
307
+ timeout: 120_000,
308
+ });
309
+ const artifactRefs = extractArtifactRefs(result.stdout, result.stderr);
310
+ executedRuns.push({
311
+ command: run.command,
312
+ target: run.target,
313
+ exitCode: result.code,
314
+ ...(artifactRefs.length > 0 ? { artifactRefs } : {}),
315
+ });
316
+
317
+ if (result.code !== 0) {
318
+ const detail = createFailureDetail(options.label, result.code, result.stdout, result.stderr);
319
+ const failedRefs = extractArtifactRefs(
320
+ ...executedRuns.flatMap((r) => r.artifactRefs ?? []),
321
+ );
322
+ return {
323
+ gate: options.id,
324
+ status: "failed",
325
+ summary: `${options.label} failed for ${formatTargetLocation(context.target)}.`,
326
+ issues: [createFailureIssue(options.label, detail, context.target, run)],
327
+ metadata: {
328
+ target: context.target.relativeDir,
329
+ runs: executedRuns,
330
+ failedCommand: run.command,
331
+ ...(failedRefs.length > 0 ? { artifactRefs: failedRefs } : {}),
332
+ },
333
+ };
334
+ }
335
+ }
336
+
337
+ const passedRefs = extractArtifactRefs(
338
+ ...executedRuns.flatMap((r) => r.artifactRefs ?? []),
339
+ );
138
340
  return {
139
341
  gate: options.id,
140
- status: "failed",
141
- summary: `${options.label} failed.`,
142
- issues: [createFailureIssue(options.label, detail)],
342
+ status: "passed",
343
+ summary: `${options.label} passed.`,
344
+ issues: [],
143
345
  metadata: {
144
- command: config.command,
145
- exitCode: result.code,
346
+ target: context.target.relativeDir,
347
+ runs: executedRuns,
348
+ ...(passedRefs.length > 0 ? { artifactRefs: passedRefs } : {}),
146
349
  },
147
350
  };
148
351
  },
@@ -1,5 +1,5 @@
1
- import type { QualityGatesConfig, ProjectFacts } from "../types.js";
2
- import { CANONICAL_GATE_ORDER, type GateRegistry } from "./registry.js";
1
+ import type { ProjectFacts, QualityGatesConfig } from "../types.js";
2
+ import { CANONICAL_GATE_ORDER, GATE_DISPLAY_NAMES, type GateRegistry } from "./registry.js";
3
3
  import { lspDiagnosticsGate } from "./gates/lsp-diagnostics.js";
4
4
  import { lintGate } from "./gates/lint.js";
5
5
  import { typecheckGate } from "./gates/typecheck.js";
@@ -31,3 +31,19 @@ export function detectReviewGates(projectFacts: ProjectFacts): QualityGatesConfi
31
31
 
32
32
  return gates;
33
33
  }
34
+
35
+ export function collectReviewGateNotes(projectFacts: ProjectFacts): string[] {
36
+ const notes: string[] = [];
37
+
38
+ for (const gateId of CANONICAL_GATE_ORDER) {
39
+ const definition = REVIEW_GATE_REGISTRY[gateId];
40
+ const detection = definition?.detect(projectFacts);
41
+ if (!detection?.reason || !detection.reason.includes("target")) {
42
+ continue;
43
+ }
44
+
45
+ notes.push(`${GATE_DISPLAY_NAMES[gateId]}: ${detection.reason}`);
46
+ }
47
+
48
+ return notes;
49
+ }
@@ -1,4 +1,5 @@
1
1
  // src/quality/runner.ts
2
+ import path from "node:path";
2
3
  import type { ExecOptions, ExecResult, Platform } from "../platform/types.js";
3
4
  import type {
4
5
  GateDefinition,
@@ -10,9 +11,12 @@ import type {
10
11
  QualityGatesConfig,
11
12
  ResolvedModel,
12
13
  ReviewReport,
14
+ WorkspaceTarget,
13
15
  } from "../types.js";
14
- import { CANONICAL_GATE_ORDER, type GateRegistry } from "./registry.js";
15
16
  import { collectLspDiagnostics } from "../lsp/bridge.js";
17
+ import { filterPathsForWorkspaceTarget, normalizeRepoPath } from "../workspace/path-mapping.js";
18
+ import { createExecShell } from "../utils/shell.js";
19
+ import { CANONICAL_GATE_ORDER, type GateRegistry } from "./registry.js";
16
20
 
17
21
  interface ReviewScope {
18
22
  changedFiles: string[];
@@ -39,6 +43,8 @@ export type ReviewRunEvent =
39
43
  export interface RunQualityGatesInput {
40
44
  platform: Pick<Platform, "exec" | "getActiveTools" | "createAgentSession">;
41
45
  cwd: string;
46
+ target: WorkspaceTarget;
47
+ workspaceTargets: WorkspaceTarget[];
42
48
  gates: QualityGatesConfig;
43
49
  filters: GateFilters;
44
50
  reviewModel: ResolvedModel;
@@ -61,11 +67,11 @@ function normalizeFileList(...chunks: string[]): string[] {
61
67
 
62
68
  for (const chunk of chunks) {
63
69
  for (const line of chunk.split("\n")) {
64
- const file = line.trim();
65
- if (file.length === 0) {
70
+ const trimmed = line.trim();
71
+ if (trimmed.length === 0) {
66
72
  continue;
67
73
  }
68
- seen.add(file);
74
+ seen.add(normalizeRepoPath(trimmed));
69
75
  }
70
76
  }
71
77
 
@@ -89,14 +95,48 @@ async function safeExec(
89
95
  }
90
96
  }
91
97
 
98
+ export async function discoverChangedRepoFiles(
99
+ exec: Platform["exec"],
100
+ repoRoot: string,
101
+ ): Promise<string[]> {
102
+ const head = await safeExec(exec, "git", ["diff", "--name-only", "HEAD"], { cwd: repoRoot });
103
+ const cached = await safeExec(exec, "git", ["diff", "--name-only", "--cached"], { cwd: repoRoot });
104
+ const untracked = await safeExec(exec, "git", ["ls-files", "--others", "--exclude-standard"], {
105
+ cwd: repoRoot,
106
+ });
107
+
108
+ return normalizeFileList(head.stdout, cached.stdout, untracked.stdout);
109
+ }
110
+
111
+ async function discoverTrackedRepoFiles(
112
+ exec: Platform["exec"],
113
+ repoRoot: string,
114
+ ): Promise<string[]> {
115
+ const tracked = await safeExec(exec, "git", ["ls-files"], { cwd: repoRoot });
116
+ return normalizeFileList(tracked.stdout);
117
+ }
118
+
119
+ function mapRepoPathsToTargetPaths(
120
+ workspaceTargets: WorkspaceTarget[],
121
+ target: WorkspaceTarget,
122
+ repoRelativePaths: string[],
123
+ ): string[] {
124
+ return filterPathsForWorkspaceTarget(workspaceTargets, target, repoRelativePaths).map((repoRelativePath) =>
125
+ normalizeRepoPath(path.relative(target.packageDir, path.join(target.repoRoot, repoRelativePath))),
126
+ );
127
+ }
128
+
92
129
  export async function discoverReviewScope(
93
130
  exec: Platform["exec"],
94
- cwd: string,
131
+ repoRoot: string,
132
+ workspaceTargets: WorkspaceTarget[],
133
+ target: WorkspaceTarget,
95
134
  ): Promise<ReviewScope> {
96
- const head = await safeExec(exec, "git", ["diff", "--name-only", "HEAD"], { cwd });
97
- const cached = await safeExec(exec, "git", ["diff", "--name-only", "--cached"], { cwd });
98
- const untracked = await safeExec(exec, "git", ["ls-files", "--others", "--exclude-standard"], { cwd });
99
- const changedFiles = normalizeFileList(head.stdout, cached.stdout, untracked.stdout);
135
+ const changedFiles = mapRepoPathsToTargetPaths(
136
+ workspaceTargets,
137
+ target,
138
+ await discoverChangedRepoFiles(exec, repoRoot),
139
+ );
100
140
 
101
141
  if (changedFiles.length > 0) {
102
142
  return {
@@ -106,8 +146,11 @@ export async function discoverReviewScope(
106
146
  };
107
147
  }
108
148
 
109
- const tracked = await safeExec(exec, "git", ["ls-files"], { cwd });
110
- const scopeFiles = normalizeFileList(tracked.stdout);
149
+ const scopeFiles = mapRepoPathsToTargetPaths(
150
+ workspaceTargets,
151
+ target,
152
+ await discoverTrackedRepoFiles(exec, repoRoot),
153
+ );
111
154
 
112
155
  return {
113
156
  changedFiles: [],
@@ -129,21 +172,13 @@ function selectConfiguredGates(gates: QualityGatesConfig, filters: GateFilters):
129
172
  return enabledGates;
130
173
  }
131
174
 
132
- function createExecShell(exec: Platform["exec"]): GateExecutionContext["execShell"] {
133
- return async (command: string, opts?: ExecOptions): Promise<ExecResult> => {
134
- if (process.platform === "win32") {
135
- return exec("cmd", ["/d", "/s", "/c", command], opts);
136
- }
137
-
138
- return exec("sh", ["-lc", command], opts);
139
- };
140
- }
141
175
 
142
176
  function createGateExecutionContext(
143
177
  input: RunQualityGatesInput,
144
178
  scope: ReviewScope,
145
179
  ): GateExecutionContext {
146
- const exec = input.platform.exec.bind(input.platform);
180
+ const exec: GateExecutionContext["exec"] = (cmd, args, opts) =>
181
+ input.platform.exec(cmd, args, { cwd: input.cwd, ...opts });
147
182
  const createAgentSession = input.platform.createAgentSession.bind(input.platform);
148
183
  const activeTools = input.platform.getActiveTools();
149
184
  const reviewModel = {
@@ -156,6 +191,7 @@ function createGateExecutionContext(
156
191
  changedFiles: scope.changedFiles,
157
192
  scopeFiles: scope.scopeFiles,
158
193
  fileScope: scope.fileScope,
194
+ target: input.target,
159
195
  exec,
160
196
  execShell: createExecShell(exec),
161
197
  getLspDiagnostics:
@@ -224,7 +260,12 @@ export function computeOverallStatus(summary: GateSummary): ReviewReport["overal
224
260
 
225
261
  export async function runQualityGates(input: RunQualityGatesInput): Promise<ReviewReport> {
226
262
  const selectedGates = selectConfiguredGates(input.gates, input.filters);
227
- const scope = await discoverReviewScope(input.platform.exec.bind(input.platform), input.cwd);
263
+ const scope = await discoverReviewScope(
264
+ input.platform.exec.bind(input.platform),
265
+ input.target.repoRoot,
266
+ input.workspaceTargets,
267
+ input.target,
268
+ );
228
269
  input.onEvent?.({
229
270
  type: "scope-discovered",
230
271
  changedFiles: scope.changedFiles.length,