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,9 +1,13 @@
|
|
|
1
1
|
import agentReviewWrapperPrompt from "./prompts/agent-review-wrapper.md" with { type: "text" };
|
|
2
2
|
import outputInstructionsPrompt from "./prompts/output-instructions.md" with { type: "text" };
|
|
3
|
-
import reviewOutputSchema from "./prompts/review-output-schema.md" with { type: "text" };
|
|
4
3
|
import type { ConfiguredReviewAgent, GateExecutionContext, ReviewOutput, ReviewScope } from "../types.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { runWithOutputValidation, type ReliabilityReporter } from "../ai/structured-output.js";
|
|
5
|
+
import { renderSchemaText } from "../ai/schema-text.js";
|
|
6
|
+
import { explainReviewOutputFailure, parseReviewOutput } from "./output.js";
|
|
7
|
+
import { renderTemplate } from "../ai/template.js";
|
|
8
|
+
import { ReviewOutputSchema } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const REVIEW_OUTPUT_SCHEMA_TEXT = renderSchemaText(ReviewOutputSchema);
|
|
7
11
|
|
|
8
12
|
export interface MultiAgentReviewInput {
|
|
9
13
|
cwd: string;
|
|
@@ -13,8 +17,11 @@ export interface MultiAgentReviewInput {
|
|
|
13
17
|
model?: string;
|
|
14
18
|
thinkingLevel?: string | null;
|
|
15
19
|
timeoutMs?: number;
|
|
20
|
+
/** Tool ids active in the host runtime. Used to gate `peerCoordination` on `irc`. */
|
|
21
|
+
activeTools?: string[];
|
|
16
22
|
onAgentStart?: (agent: ConfiguredReviewAgent) => void;
|
|
17
23
|
onAgentComplete?: (result: MultiAgentAgentResult) => void;
|
|
24
|
+
reliability?: ReliabilityReporter;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export interface MultiAgentAgentResult {
|
|
@@ -30,8 +37,8 @@ export interface MultiAgentReviewResult {
|
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
function renderOutputInstructions(): string {
|
|
33
|
-
return
|
|
34
|
-
outputSchema:
|
|
40
|
+
return renderTemplate(outputInstructionsPrompt, {
|
|
41
|
+
outputSchema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
35
42
|
});
|
|
36
43
|
}
|
|
37
44
|
|
|
@@ -41,18 +48,95 @@ export function buildConfiguredAgentPrompt(agent: ConfiguredReviewAgent, scope:
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
const outputInstructions = renderOutputInstructions();
|
|
44
|
-
const agentPrompt =
|
|
45
|
-
|
|
46
|
-
{ outputInstructions },
|
|
47
|
-
);
|
|
51
|
+
const agentPrompt = renderTemplate(agent.prompt.replaceAll("{output_instructions}", "{{outputInstructions}}"),
|
|
52
|
+
{ outputInstructions },);
|
|
48
53
|
|
|
49
|
-
return
|
|
54
|
+
return renderTemplate(agentReviewWrapperPrompt, {
|
|
50
55
|
agent,
|
|
51
56
|
agentPrompt,
|
|
52
57
|
scope,
|
|
53
58
|
});
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
interface PeerCoordinationIdentity {
|
|
62
|
+
agent: ConfiguredReviewAgent;
|
|
63
|
+
id: string;
|
|
64
|
+
displayName: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const PEER_COORDINATION_AGENT_ID_PREFIX = "supi-review";
|
|
68
|
+
|
|
69
|
+
function normalizePeerCoordinationIdSegment(name: string, fallbackIndex: number): string {
|
|
70
|
+
const normalized = name
|
|
71
|
+
.trim()
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
74
|
+
.replace(/^-+|-+$/g, "");
|
|
75
|
+
return normalized || `agent-${fallbackIndex + 1}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizePeerCoordinationDisplayName(name: string, fallbackIndex: number): string {
|
|
79
|
+
const normalized = name.trim().replace(/\s+/g, " ");
|
|
80
|
+
return normalized || `review-agent-${fallbackIndex + 1}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildPeerCoordinationIdentities(
|
|
84
|
+
agents: ConfiguredReviewAgent[],
|
|
85
|
+
): PeerCoordinationIdentity[] {
|
|
86
|
+
const countsByBaseId = new Map<string, number>();
|
|
87
|
+
return agents.map((agent, index) => {
|
|
88
|
+
const segment = normalizePeerCoordinationIdSegment(agent.name, index);
|
|
89
|
+
const baseId = `${PEER_COORDINATION_AGENT_ID_PREFIX}-${segment}`;
|
|
90
|
+
const previousCount = countsByBaseId.get(baseId) ?? 0;
|
|
91
|
+
countsByBaseId.set(baseId, previousCount + 1);
|
|
92
|
+
return {
|
|
93
|
+
agent,
|
|
94
|
+
id: previousCount === 0 ? baseId : `${baseId}-${previousCount + 1}`,
|
|
95
|
+
displayName: normalizePeerCoordinationDisplayName(agent.name, index),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build the IRC peer-coordination prompt block for one agent.
|
|
102
|
+
*
|
|
103
|
+
* Returns `null` when peer coordination is disabled, the host runtime does not
|
|
104
|
+
* expose the `irc` tool, or the agent has no peers (single-agent run, or only
|
|
105
|
+
* itself opted in).
|
|
106
|
+
*/
|
|
107
|
+
export function buildPeerCoordinationPromptBlock(
|
|
108
|
+
agent: ConfiguredReviewAgent,
|
|
109
|
+
peers: ConfiguredReviewAgent[],
|
|
110
|
+
activeTools: string[],
|
|
111
|
+
identities: PeerCoordinationIdentity[] = buildPeerCoordinationIdentities(peers),
|
|
112
|
+
): string | null {
|
|
113
|
+
if (agent.peerCoordination !== true) return null;
|
|
114
|
+
if (!activeTools.includes("irc")) return null;
|
|
115
|
+
|
|
116
|
+
const self = identities.find((identity) => identity.agent === agent);
|
|
117
|
+
if (!self) return null;
|
|
118
|
+
|
|
119
|
+
const otherPeers = identities.filter(
|
|
120
|
+
(identity) => identity.agent.peerCoordination === true && identity.agent !== agent,
|
|
121
|
+
);
|
|
122
|
+
if (otherPeers.length === 0) return null;
|
|
123
|
+
|
|
124
|
+
return [
|
|
125
|
+
"## IRC peer coordination",
|
|
126
|
+
"",
|
|
127
|
+
`You are running as \`${self.id}\` in a multi-agent review. Other reviewers also opted into peer coordination:`,
|
|
128
|
+
...otherPeers.map((peer) => `- \`${peer.id}\` — ${peer.displayName}`),
|
|
129
|
+
"",
|
|
130
|
+
"Use the OMP `irc` tool when continuing alone is wasteful or wrong:",
|
|
131
|
+
"- Send a DM with `irc({ op: \"send\", to: \"<peer-id>\", message: \"...\" })` when you spot work a peer is already filing, when you need their finding for context, or when you would otherwise duplicate analysis.",
|
|
132
|
+
"- Reply in plain prose. Do **NOT** send JSON status payloads. Do **NOT** quote the message you are replying to.",
|
|
133
|
+
"- One DM is one round-trip. Do **NOT** follow up with \"did you get my message?\".",
|
|
134
|
+
"- Use exactly the peer ids listed above. Do **NOT** invent ids from agent names, and do **NOT** broadcast unless you genuinely need every peer.",
|
|
135
|
+
"- If a peer has already filed an equivalent finding, do **NOT** file a duplicate; defer to theirs.",
|
|
136
|
+
"",
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
56
140
|
function aggregateAgentOutputs(results: MultiAgentAgentResult[]): ReviewOutput {
|
|
57
141
|
const findings = results.flatMap((result) =>
|
|
58
142
|
result.output.findings.map((finding) => ({
|
|
@@ -73,13 +157,25 @@ function aggregateAgentOutputs(results: MultiAgentAgentResult[]): ReviewOutput {
|
|
|
73
157
|
async function runConfiguredAgent(
|
|
74
158
|
input: Omit<MultiAgentReviewInput, "agents">,
|
|
75
159
|
agent: ConfiguredReviewAgent,
|
|
160
|
+
peers: ConfiguredReviewAgent[],
|
|
161
|
+
peerIdentities: PeerCoordinationIdentity[],
|
|
76
162
|
): Promise<MultiAgentAgentResult> {
|
|
77
163
|
input.onAgentStart?.(agent);
|
|
78
164
|
|
|
165
|
+
const identity = peerIdentities.find((peer) => peer.agent === agent);
|
|
166
|
+
const basePrompt = buildConfiguredAgentPrompt(agent, input.scope);
|
|
167
|
+
const peerBlock = buildPeerCoordinationPromptBlock(
|
|
168
|
+
agent,
|
|
169
|
+
peers,
|
|
170
|
+
input.activeTools ?? [],
|
|
171
|
+
peerIdentities,
|
|
172
|
+
);
|
|
173
|
+
const prompt = peerBlock ? `${peerBlock}\n${basePrompt}` : basePrompt;
|
|
174
|
+
|
|
79
175
|
const result = await runWithOutputValidation(input.createAgentSession, {
|
|
80
176
|
cwd: input.cwd,
|
|
81
|
-
prompt
|
|
82
|
-
schema:
|
|
177
|
+
prompt,
|
|
178
|
+
schema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
83
179
|
parse(raw) {
|
|
84
180
|
const output = parseReviewOutput(raw);
|
|
85
181
|
return {
|
|
@@ -88,8 +184,12 @@ async function runConfiguredAgent(
|
|
|
88
184
|
};
|
|
89
185
|
},
|
|
90
186
|
model: agent.model ?? input.model,
|
|
91
|
-
thinkingLevel: input.thinkingLevel ?? null,
|
|
187
|
+
thinkingLevel: agent.thinkingLevel ?? input.thinkingLevel ?? null,
|
|
188
|
+
...(peerBlock && identity
|
|
189
|
+
? { agentId: identity.id, agentDisplayName: identity.displayName }
|
|
190
|
+
: {}),
|
|
92
191
|
timeoutMs: input.timeoutMs ?? 120_000,
|
|
192
|
+
reliability: input.reliability,
|
|
93
193
|
});
|
|
94
194
|
|
|
95
195
|
if (result.status === "blocked") {
|
|
@@ -124,8 +224,9 @@ async function runConfiguredAgent(
|
|
|
124
224
|
}
|
|
125
225
|
|
|
126
226
|
export async function runMultiAgentReview(input: MultiAgentReviewInput): Promise<MultiAgentReviewResult> {
|
|
227
|
+
const peerIdentities = buildPeerCoordinationIdentities(input.agents);
|
|
127
228
|
const results = await Promise.all(
|
|
128
|
-
input.agents.map((agent) => runConfiguredAgent(input, agent)),
|
|
229
|
+
input.agents.map((agent) => runConfiguredAgent(input, agent, input.agents, peerIdentities)),
|
|
129
230
|
);
|
|
130
231
|
|
|
131
232
|
return {
|
package/src/review/output.ts
CHANGED
|
@@ -1,80 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
export interface StructuredParseResult<T> {
|
|
15
|
-
output: T | null;
|
|
16
|
-
error: string | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface OutputValidationRunOptions<T> {
|
|
20
|
-
cwd: string;
|
|
21
|
-
prompt: string;
|
|
22
|
-
schema: string;
|
|
23
|
-
parse: (raw: string) => StructuredParseResult<T>;
|
|
24
|
-
model?: string;
|
|
25
|
-
thinkingLevel?: string | null;
|
|
26
|
-
timeoutMs?: number;
|
|
27
|
-
maxAttempts?: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type OutputValidationRunResult<T> =
|
|
31
|
-
| {
|
|
32
|
-
status: "ok";
|
|
33
|
-
output: T;
|
|
34
|
-
rawOutput: string;
|
|
35
|
-
attempts: number;
|
|
36
|
-
}
|
|
37
|
-
| {
|
|
38
|
-
status: "blocked";
|
|
39
|
-
error: string;
|
|
40
|
-
rawOutputs: string[];
|
|
41
|
-
attempts: number;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function truncateForPrompt(text: string, maxLength = 1200): string {
|
|
45
|
-
const normalized = text.trim();
|
|
46
|
-
if (normalized.length <= maxLength) {
|
|
47
|
-
return normalized;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return `${normalized.slice(0, maxLength - 1)}…`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function parseStructuredOutput<T>(raw: string, schema: TSchema): StructuredParseResult<T> {
|
|
54
|
-
let parsed: unknown;
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
parsed = JSON.parse(stripMarkdownCodeFence(raw));
|
|
58
|
-
} catch (error) {
|
|
59
|
-
return {
|
|
60
|
-
output: null,
|
|
61
|
-
error: error instanceof Error ? `Invalid JSON: ${error.message}` : "Invalid JSON.",
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!Value.Check(schema, parsed)) {
|
|
66
|
-
const errors = formatReviewValidationErrors(collectReviewValidationErrors(schema, parsed));
|
|
67
|
-
return {
|
|
68
|
-
output: null,
|
|
69
|
-
error: errors.length > 0 ? errors.join("; ") : "Output does not match the required schema.",
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
output: parsed as T,
|
|
75
|
-
error: null,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
1
|
+
// src/review/output.ts
|
|
2
|
+
//
|
|
3
|
+
// Review-specific thin wrappers over the shared structured-output foundation.
|
|
4
|
+
// Everything generic (parseStructuredOutput<T>, runWithOutputValidation<T>,
|
|
5
|
+
// StructuredOutputResult<T>, validation-error helpers) lives in
|
|
6
|
+
// src/ai/structured-output.ts. This module exists only so review callers can
|
|
7
|
+
// parse a raw model response into ReviewOutput without rewriting the schema
|
|
8
|
+
// reference at every site.
|
|
9
|
+
|
|
10
|
+
import { parseStructuredOutput } from "../ai/structured-output.js";
|
|
11
|
+
import type { ReviewOutput } from "../types.js";
|
|
12
|
+
import { ReviewOutputSchema } from "./types.js";
|
|
78
13
|
|
|
79
14
|
export function parseReviewOutput(raw: string): ReviewOutput | null {
|
|
80
15
|
return parseStructuredOutput<ReviewOutput>(raw, ReviewOutputSchema).output;
|
|
@@ -83,65 +18,3 @@ export function parseReviewOutput(raw: string): ReviewOutput | null {
|
|
|
83
18
|
export function explainReviewOutputFailure(raw: string): string | null {
|
|
84
19
|
return parseStructuredOutput<ReviewOutput>(raw, ReviewOutputSchema).error;
|
|
85
20
|
}
|
|
86
|
-
|
|
87
|
-
export async function runWithOutputValidation<T>(
|
|
88
|
-
createAgentSession: GateExecutionContext["createAgentSession"],
|
|
89
|
-
options: OutputValidationRunOptions<T>,
|
|
90
|
-
): Promise<OutputValidationRunResult<T>> {
|
|
91
|
-
const maxAttempts = Math.max(1, options.maxAttempts ?? 3);
|
|
92
|
-
const rawOutputs: string[] = [];
|
|
93
|
-
let prompt = options.prompt;
|
|
94
|
-
|
|
95
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
96
|
-
const result = await runStructuredAgentSession(createAgentSession, {
|
|
97
|
-
cwd: options.cwd,
|
|
98
|
-
prompt,
|
|
99
|
-
model: options.model,
|
|
100
|
-
thinkingLevel: options.thinkingLevel ?? null,
|
|
101
|
-
timeoutMs: options.timeoutMs,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (result.status !== "ok") {
|
|
105
|
-
return {
|
|
106
|
-
status: "blocked",
|
|
107
|
-
error: result.error,
|
|
108
|
-
rawOutputs,
|
|
109
|
-
attempts: attempt,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
rawOutputs.push(result.finalText);
|
|
114
|
-
const parsed = options.parse(result.finalText);
|
|
115
|
-
if (parsed.output) {
|
|
116
|
-
return {
|
|
117
|
-
status: "ok",
|
|
118
|
-
output: parsed.output,
|
|
119
|
-
rawOutput: result.finalText,
|
|
120
|
-
attempts: attempt,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (attempt === maxAttempts) {
|
|
125
|
-
return {
|
|
126
|
-
status: "blocked",
|
|
127
|
-
error: parsed.error ?? "Agent output was invalid.",
|
|
128
|
-
rawOutputs,
|
|
129
|
-
attempts: attempt,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
prompt = renderReviewTemplate(invalidOutputRetryPrompt, {
|
|
134
|
-
prompt: options.prompt,
|
|
135
|
-
error: parsed.error ?? "Agent output was invalid.",
|
|
136
|
-
previousOutput: truncateForPrompt(result.finalText),
|
|
137
|
-
schema: options.schema,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
status: "blocked",
|
|
143
|
-
error: "Output validation exhausted without producing a valid result.",
|
|
144
|
-
rawOutputs,
|
|
145
|
-
attempts: maxAttempts,
|
|
146
|
-
};
|
|
147
|
-
}
|
package/src/review/runner.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import reviewOutputSchema from "./prompts/review-output-schema.md" with { type: "text" };
|
|
2
1
|
import singleReviewPrompt from "./prompts/single-review.md" with { type: "text" };
|
|
3
2
|
import type { GateExecutionContext, ReviewLevel, ReviewOutput, ReviewScope } from "../types.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { runWithOutputValidation, type ReliabilityReporter } from "../ai/structured-output.js";
|
|
4
|
+
import { renderSchemaText } from "../ai/schema-text.js";
|
|
5
|
+
import { explainReviewOutputFailure, parseReviewOutput } from "./output.js";
|
|
6
|
+
import { renderTemplate } from "../ai/template.js";
|
|
7
|
+
import { ReviewOutputSchema } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const REVIEW_OUTPUT_SCHEMA_TEXT = renderSchemaText(ReviewOutputSchema);
|
|
6
10
|
|
|
7
11
|
export type SingleReviewLevel = Extract<ReviewLevel, "quick" | "deep">;
|
|
8
12
|
|
|
@@ -14,6 +18,7 @@ export interface SingleReviewRunnerInput {
|
|
|
14
18
|
model?: string;
|
|
15
19
|
thinkingLevel?: string | null;
|
|
16
20
|
timeoutMs?: number;
|
|
21
|
+
reliability?: ReliabilityReporter;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface SingleReviewRunResult {
|
|
@@ -70,12 +75,12 @@ export function normalizeReviewOutput(output: ReviewOutput): ReviewOutput {
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
export function buildSingleReviewPrompt(scope: ReviewScope, level: SingleReviewLevel): string {
|
|
73
|
-
return
|
|
78
|
+
return renderTemplate(singleReviewPrompt, {
|
|
74
79
|
level,
|
|
75
80
|
scope,
|
|
76
81
|
isQuick: level === "quick",
|
|
77
82
|
isDeep: level === "deep",
|
|
78
|
-
outputSchema:
|
|
83
|
+
outputSchema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
79
84
|
});
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -83,7 +88,7 @@ export async function runSingleReview(input: SingleReviewRunnerInput): Promise<S
|
|
|
83
88
|
const result = await runWithOutputValidation(input.createAgentSession, {
|
|
84
89
|
cwd: input.cwd,
|
|
85
90
|
prompt: buildSingleReviewPrompt(input.scope, input.level),
|
|
86
|
-
schema:
|
|
91
|
+
schema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
87
92
|
parse(raw) {
|
|
88
93
|
const output = parseReviewOutput(raw);
|
|
89
94
|
return {
|
|
@@ -94,6 +99,7 @@ export async function runSingleReview(input: SingleReviewRunnerInput): Promise<S
|
|
|
94
99
|
model: input.model,
|
|
95
100
|
thinkingLevel: input.thinkingLevel ?? null,
|
|
96
101
|
timeoutMs: input.timeoutMs ?? 120_000,
|
|
102
|
+
reliability: input.reliability,
|
|
97
103
|
});
|
|
98
104
|
|
|
99
105
|
if (result.status === "blocked") {
|
package/src/review/scope.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Platform, PlatformContext } from "../platform/types.js";
|
|
2
|
-
import type { ReviewScope, ReviewScopeFile, ReviewScopeStats } from "../types.js";
|
|
2
|
+
import type { ReviewScope, ReviewScopeFile, ReviewScopeStats, WorkspaceTarget } from "../types.js";
|
|
3
|
+
import { filterGitLogOnelineToWorkspaceTarget } from "../workspace/git-scope.js";
|
|
4
|
+
import { findWorkspaceTargetForPath } from "../workspace/path-mapping.js";
|
|
3
5
|
|
|
4
6
|
interface ExcludedReviewScopeFile {
|
|
5
7
|
path: string;
|
|
@@ -14,6 +16,11 @@ export interface ParsedReviewDiff {
|
|
|
14
16
|
stats: ReviewScopeStats;
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
export interface ReviewWorkspaceSelection {
|
|
20
|
+
target: WorkspaceTarget;
|
|
21
|
+
targets: WorkspaceTarget[];
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
export const EXCLUDED_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [
|
|
18
25
|
{ pattern: /\.lock$/, reason: "lock file" },
|
|
19
26
|
{ pattern: /-lock\.(json|yaml|yml)$/, reason: "lock file" },
|
|
@@ -200,6 +207,51 @@ function ensureReviewableScope(scope: ReviewScope, message: string): ReviewScope
|
|
|
200
207
|
return scope;
|
|
201
208
|
}
|
|
202
209
|
|
|
210
|
+
function buildTargetLabel(selection: ReviewWorkspaceSelection): string {
|
|
211
|
+
return `${selection.target.name} (${selection.target.relativeDir})`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function appendTargetContext(description: string, selection?: ReviewWorkspaceSelection | null): string {
|
|
215
|
+
return selection ? `${description} for ${buildTargetLabel(selection)}` : description;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildEmptyScopeMessage(baseMessage: string, selection?: ReviewWorkspaceSelection | null): string {
|
|
219
|
+
return selection
|
|
220
|
+
? `${baseMessage} for ${buildTargetLabel(selection)}.`
|
|
221
|
+
: `${baseMessage}.`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function ensureTargetScopeHasDiff(
|
|
225
|
+
diff: string,
|
|
226
|
+
emptyScopeMessage: string,
|
|
227
|
+
selection?: ReviewWorkspaceSelection | null,
|
|
228
|
+
): void {
|
|
229
|
+
if (selection && !diff.trim()) {
|
|
230
|
+
throw new Error(emptyScopeMessage);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function filterDiffToWorkspaceTarget(diffOutput: string, selection?: ReviewWorkspaceSelection | null): string {
|
|
235
|
+
if (!selection || !diffOutput.trim()) {
|
|
236
|
+
return diffOutput;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return diffOutput
|
|
240
|
+
.split(/^diff --git /m)
|
|
241
|
+
.filter(Boolean)
|
|
242
|
+
.map((chunk) => `diff --git ${chunk}`)
|
|
243
|
+
.filter((chunk) => {
|
|
244
|
+
const headerMatch = chunk.match(/^diff --git a\/(.+?) b\/(.+)/m);
|
|
245
|
+
const repoRelativePath = headerMatch?.[2]?.trim();
|
|
246
|
+
if (!repoRelativePath) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return findWorkspaceTargetForPath(selection.targets, repoRelativePath)?.id === selection.target.id;
|
|
251
|
+
})
|
|
252
|
+
.join("\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
203
255
|
export async function listReviewBaseBranches(platform: Pick<Platform, "exec">, cwd: string): Promise<string[]> {
|
|
204
256
|
const output = await execGit(platform, cwd, ["branch", "--all", "--format=%(refname:short)"]);
|
|
205
257
|
return [...new Set(
|
|
@@ -220,9 +272,18 @@ export async function listRecentReviewCommits(
|
|
|
220
272
|
platform: Pick<Platform, "exec">,
|
|
221
273
|
cwd: string,
|
|
222
274
|
count = 20,
|
|
275
|
+
selection?: ReviewWorkspaceSelection | null,
|
|
223
276
|
): Promise<string[]> {
|
|
224
|
-
|
|
225
|
-
|
|
277
|
+
if (!selection) {
|
|
278
|
+
const output = await execGit(platform, cwd, ["log", "--oneline", `-${count}`]);
|
|
279
|
+
return output
|
|
280
|
+
.split("\n")
|
|
281
|
+
.map((line) => line.trim())
|
|
282
|
+
.filter((line) => line.length > 0);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const output = await execGit(platform, cwd, ["log", `-${count}`, "--format=%H%x1f%s%x1e", "--name-only"]);
|
|
286
|
+
return filterGitLogOnelineToWorkspaceTarget(output, selection.targets, selection.target)
|
|
226
287
|
.split("\n")
|
|
227
288
|
.map((line) => line.trim())
|
|
228
289
|
.filter((line) => line.length > 0);
|
|
@@ -233,63 +294,120 @@ export async function loadPullRequestScope(
|
|
|
233
294
|
cwd: string,
|
|
234
295
|
baseBranch: string,
|
|
235
296
|
currentBranch?: string,
|
|
297
|
+
selection?: ReviewWorkspaceSelection | null,
|
|
236
298
|
): Promise<ReviewScope> {
|
|
237
299
|
const branch = currentBranch ?? await getCurrentReviewBranch(platform, cwd);
|
|
238
|
-
const
|
|
300
|
+
const rawDiff = await execGit(platform, cwd, ["diff", "--no-ext-diff", "--binary", `${baseBranch}...${branch}`]);
|
|
301
|
+
const diff = filterDiffToWorkspaceTarget(rawDiff, selection);
|
|
302
|
+
ensureTargetScopeHasDiff(
|
|
303
|
+
diff,
|
|
304
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering PR-style changes", selection),
|
|
305
|
+
selection,
|
|
306
|
+
);
|
|
239
307
|
const scope = createScope(
|
|
240
308
|
"pull-request",
|
|
241
|
-
`Reviewing changes between ${baseBranch} and ${branch}`,
|
|
309
|
+
appendTargetContext(`Reviewing changes between ${baseBranch} and ${branch}`, selection),
|
|
242
310
|
diff,
|
|
243
311
|
{ baseBranch },
|
|
244
312
|
);
|
|
245
|
-
return ensureReviewableScope(
|
|
313
|
+
return ensureReviewableScope(
|
|
314
|
+
scope,
|
|
315
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering PR-style changes", selection),
|
|
316
|
+
);
|
|
246
317
|
}
|
|
247
318
|
|
|
248
|
-
export async function loadUncommittedScope(
|
|
319
|
+
export async function loadUncommittedScope(
|
|
320
|
+
platform: Pick<Platform, "exec">,
|
|
321
|
+
cwd: string,
|
|
322
|
+
selection?: ReviewWorkspaceSelection | null,
|
|
323
|
+
): Promise<ReviewScope> {
|
|
249
324
|
const [unstaged, staged, untracked] = await Promise.all([
|
|
250
325
|
execGit(platform, cwd, ["diff", "--no-ext-diff", "--binary"]),
|
|
251
326
|
execGit(platform, cwd, ["diff", "--cached", "--no-ext-diff", "--binary"]),
|
|
252
327
|
buildUntrackedDiff(platform, cwd),
|
|
253
328
|
]);
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
|
|
329
|
+
const rawDiff = [unstaged, staged, untracked].filter((chunk) => chunk.trim().length > 0).join("\n");
|
|
330
|
+
const diff = filterDiffToWorkspaceTarget(rawDiff, selection);
|
|
331
|
+
ensureTargetScopeHasDiff(
|
|
332
|
+
diff,
|
|
333
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering uncommitted changes", selection),
|
|
334
|
+
selection,
|
|
335
|
+
);
|
|
336
|
+
const scope = createScope(
|
|
337
|
+
"uncommitted",
|
|
338
|
+
appendTargetContext("Reviewing uncommitted changes", selection),
|
|
339
|
+
diff,
|
|
340
|
+
);
|
|
341
|
+
return ensureReviewableScope(
|
|
342
|
+
scope,
|
|
343
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering uncommitted changes", selection),
|
|
344
|
+
);
|
|
257
345
|
}
|
|
258
346
|
|
|
259
347
|
export async function loadCommitScope(
|
|
260
348
|
platform: Pick<Platform, "exec">,
|
|
261
349
|
cwd: string,
|
|
262
350
|
commit: string,
|
|
351
|
+
selection?: ReviewWorkspaceSelection | null,
|
|
263
352
|
): Promise<ReviewScope> {
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
|
|
353
|
+
const rawDiff = await execGit(platform, cwd, ["show", "--format=", "--no-ext-diff", "--binary", commit]);
|
|
354
|
+
const diff = filterDiffToWorkspaceTarget(rawDiff, selection);
|
|
355
|
+
ensureTargetScopeHasDiff(
|
|
356
|
+
diff,
|
|
357
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering commit changes", selection),
|
|
358
|
+
selection,
|
|
359
|
+
);
|
|
360
|
+
const scope = createScope(
|
|
361
|
+
"commit",
|
|
362
|
+
appendTargetContext(`Reviewing commit ${commit}`, selection),
|
|
363
|
+
diff,
|
|
364
|
+
{ commit },
|
|
365
|
+
);
|
|
366
|
+
return ensureReviewableScope(
|
|
367
|
+
scope,
|
|
368
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering commit changes", selection),
|
|
369
|
+
);
|
|
267
370
|
}
|
|
268
371
|
|
|
269
372
|
export async function loadCustomReviewScope(
|
|
270
373
|
platform: Pick<Platform, "exec">,
|
|
271
374
|
cwd: string,
|
|
272
375
|
instructions: string,
|
|
376
|
+
selection?: ReviewWorkspaceSelection | null,
|
|
273
377
|
): Promise<ReviewScope> {
|
|
274
|
-
let
|
|
378
|
+
let rawDiff = "";
|
|
275
379
|
|
|
276
380
|
try {
|
|
277
|
-
|
|
381
|
+
rawDiff = await execGit(platform, cwd, ["diff", "--no-ext-diff", "--binary", "HEAD"]);
|
|
278
382
|
} catch {
|
|
279
|
-
|
|
383
|
+
rawDiff = "";
|
|
280
384
|
}
|
|
281
385
|
|
|
282
|
-
|
|
386
|
+
const diff = filterDiffToWorkspaceTarget(rawDiff, selection);
|
|
387
|
+
ensureTargetScopeHasDiff(
|
|
388
|
+
diff,
|
|
389
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering custom review changes", selection),
|
|
390
|
+
selection,
|
|
391
|
+
);
|
|
392
|
+
const scope = createScope(
|
|
283
393
|
"custom",
|
|
284
|
-
`Custom review: ${instructions.slice(0, 60)}`,
|
|
394
|
+
appendTargetContext(`Custom review: ${instructions.slice(0, 60)}`, selection),
|
|
285
395
|
diff,
|
|
286
396
|
{ customInstructions: instructions },
|
|
287
397
|
);
|
|
398
|
+
|
|
399
|
+
return selection
|
|
400
|
+
? ensureReviewableScope(
|
|
401
|
+
scope,
|
|
402
|
+
buildEmptyScopeMessage("No reviewable files remain after filtering custom review changes", selection),
|
|
403
|
+
)
|
|
404
|
+
: scope;
|
|
288
405
|
}
|
|
289
406
|
|
|
290
407
|
export async function selectReviewScope(
|
|
291
408
|
platform: Pick<Platform, "exec">,
|
|
292
409
|
ctx: Pick<PlatformContext, "cwd" | "ui">,
|
|
410
|
+
selection?: ReviewWorkspaceSelection | null,
|
|
293
411
|
): Promise<ReviewScope | null> {
|
|
294
412
|
const choice = await ctx.ui.select(
|
|
295
413
|
"What should /supi:review inspect?",
|
|
@@ -317,17 +435,19 @@ export async function selectReviewScope(
|
|
|
317
435
|
if (!selected) {
|
|
318
436
|
return null;
|
|
319
437
|
}
|
|
320
|
-
return loadPullRequestScope(platform, ctx.cwd, selected);
|
|
438
|
+
return loadPullRequestScope(platform, ctx.cwd, selected, undefined, selection);
|
|
321
439
|
}
|
|
322
440
|
|
|
323
441
|
if (choice.startsWith("Uncommitted changes")) {
|
|
324
|
-
return loadUncommittedScope(platform, ctx.cwd);
|
|
442
|
+
return loadUncommittedScope(platform, ctx.cwd, selection);
|
|
325
443
|
}
|
|
326
444
|
|
|
327
445
|
if (choice.startsWith("Specific commit")) {
|
|
328
|
-
const commits = await listRecentReviewCommits(platform, ctx.cwd);
|
|
446
|
+
const commits = await listRecentReviewCommits(platform, ctx.cwd, 20, selection);
|
|
329
447
|
if (commits.length === 0) {
|
|
330
|
-
throw new Error(
|
|
448
|
+
throw new Error(selection
|
|
449
|
+
? `No commits found for ${buildTargetLabel(selection)}.`
|
|
450
|
+
: "No commits found.");
|
|
331
451
|
}
|
|
332
452
|
const selected = await ctx.ui.select("Commit to review", commits, {
|
|
333
453
|
helpText: "Select a recent commit · Esc to cancel",
|
|
@@ -339,7 +459,7 @@ export async function selectReviewScope(
|
|
|
339
459
|
if (!commit) {
|
|
340
460
|
throw new Error("Could not determine the selected commit hash.");
|
|
341
461
|
}
|
|
342
|
-
return loadCommitScope(platform, ctx.cwd, commit);
|
|
462
|
+
return loadCommitScope(platform, ctx.cwd, commit, selection);
|
|
343
463
|
}
|
|
344
464
|
|
|
345
465
|
const instructions = await ctx.ui.input("Custom review focus", {
|
|
@@ -349,5 +469,5 @@ export async function selectReviewScope(
|
|
|
349
469
|
if (!instructions?.trim()) {
|
|
350
470
|
return null;
|
|
351
471
|
}
|
|
352
|
-
return loadCustomReviewScope(platform, ctx.cwd, instructions.trim());
|
|
472
|
+
return loadCustomReviewScope(platform, ctx.cwd, instructions.trim(), selection);
|
|
353
473
|
}
|