supipowers 1.5.2 → 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.
- package/README.md +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +149 -45
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +19 -9
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +129 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +264 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +298 -127
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +115 -14
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +11 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1401 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
|
46
|
-
|
|
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
|
|
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 (
|
|
113
|
-
return buildBlockedResult(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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:
|
|
125
|
-
summary:
|
|
126
|
-
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
|
-
|
|
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
|
|
30
|
-
|
|
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
|
-
):
|
|
72
|
+
): TargetDetectedCommand | null {
|
|
33
73
|
for (const scriptName of options.scriptNames) {
|
|
34
|
-
const command = normalizeCommand(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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 ||
|
|
116
|
-
throw new Error(`${options.id} gate requires an enabled config with
|
|
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
|
|
120
|
-
|
|
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: "
|
|
128
|
-
summary: `${options.label}
|
|
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
|
-
|
|
132
|
-
|
|
292
|
+
target: context.target.relativeDir,
|
|
293
|
+
reason: "no-matching-runs",
|
|
133
294
|
},
|
|
134
295
|
};
|
|
135
296
|
}
|
|
136
297
|
|
|
137
|
-
const
|
|
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: "
|
|
141
|
-
summary: `${options.label}
|
|
142
|
-
issues: [
|
|
342
|
+
status: "passed",
|
|
343
|
+
summary: `${options.label} passed.`,
|
|
344
|
+
issues: [],
|
|
143
345
|
metadata: {
|
|
144
|
-
|
|
145
|
-
|
|
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 {
|
|
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
|
+
}
|
package/src/quality/runner.ts
CHANGED
|
@@ -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
|
|
65
|
-
if (
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (trimmed.length === 0) {
|
|
66
72
|
continue;
|
|
67
73
|
}
|
|
68
|
-
seen.add(
|
|
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
|
-
|
|
131
|
+
repoRoot: string,
|
|
132
|
+
workspaceTargets: WorkspaceTarget[],
|
|
133
|
+
target: WorkspaceTarget,
|
|
95
134
|
): Promise<ReviewScope> {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
110
|
-
|
|
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 =
|
|
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(
|
|
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,
|