supipowers 1.5.3 → 2.0.1
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 +131 -42
- 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 +18 -8
- 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 +135 -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 +268 -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 +221 -109
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +114 -13
- 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 +1 -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 +1399 -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
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
|
}
|
package/src/review/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import type { TSchema } from "@sinclair/typebox";
|
|
3
2
|
import { Value } from "@sinclair/typebox/value";
|
|
4
3
|
import type {
|
|
5
4
|
ConfiguredReviewAgent,
|
|
@@ -132,6 +131,7 @@ export const ReviewAgentConfigSchema = Type.Object(
|
|
|
132
131
|
Type.Literal("xhigh"),
|
|
133
132
|
Type.Null(),
|
|
134
133
|
])),
|
|
134
|
+
peerCoordination: Type.Optional(Type.Boolean()),
|
|
135
135
|
},
|
|
136
136
|
{ additionalProperties: false },
|
|
137
137
|
);
|
|
@@ -222,25 +222,6 @@ export const ReviewSessionSchema = Type.Object(
|
|
|
222
222
|
{ additionalProperties: false },
|
|
223
223
|
);
|
|
224
224
|
|
|
225
|
-
export interface ReviewValidationError {
|
|
226
|
-
path: string;
|
|
227
|
-
message: string;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function normalizeErrorPath(path: string): string {
|
|
231
|
-
return path.replace(/^\//, "").replace(/\//g, ".") || "(root)";
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export function collectReviewValidationErrors(schema: TSchema, data: unknown): ReviewValidationError[] {
|
|
235
|
-
return [...Value.Errors(schema, data)].map((error) => ({
|
|
236
|
-
path: normalizeErrorPath(error.path),
|
|
237
|
-
message: error.message,
|
|
238
|
-
}));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export function formatReviewValidationErrors(errors: ReviewValidationError[]): string[] {
|
|
242
|
-
return errors.map((error) => `${error.path}: ${error.message}`);
|
|
243
|
-
}
|
|
244
225
|
|
|
245
226
|
export function isReviewScopeFile(value: unknown): value is ReviewScopeFile {
|
|
246
227
|
return Value.Check(ReviewScopeFileSchema, value);
|
package/src/review/validator.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import reviewOutputSchema from "./prompts/review-output-schema.md" with { type: "text" };
|
|
2
1
|
import validationReviewPrompt from "./prompts/validation-review.md" with { type: "text" };
|
|
3
2
|
import type { GateExecutionContext, ReviewFinding, 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 interface ReviewValidationInput {
|
|
8
12
|
cwd: string;
|
|
@@ -14,6 +18,7 @@ export interface ReviewValidationInput {
|
|
|
14
18
|
timeoutMs?: number;
|
|
15
19
|
validatorName?: string;
|
|
16
20
|
now?: () => Date;
|
|
21
|
+
reliability?: ReliabilityReporter;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface ReviewValidationResult {
|
|
@@ -80,12 +85,12 @@ export function buildValidationPrompt(
|
|
|
80
85
|
validatorName: string,
|
|
81
86
|
validatedAt: string,
|
|
82
87
|
): string {
|
|
83
|
-
return
|
|
88
|
+
return renderTemplate(validationReviewPrompt, {
|
|
84
89
|
scope,
|
|
85
90
|
findingsJson: JSON.stringify(findings, null, 2),
|
|
86
91
|
validatorName,
|
|
87
92
|
validatedAt,
|
|
88
|
-
outputSchema:
|
|
93
|
+
outputSchema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
89
94
|
});
|
|
90
95
|
}
|
|
91
96
|
|
|
@@ -107,7 +112,7 @@ export async function validateReviewFindings(input: ReviewValidationInput): Prom
|
|
|
107
112
|
const result = await runWithOutputValidation(input.createAgentSession, {
|
|
108
113
|
cwd: input.cwd,
|
|
109
114
|
prompt: buildValidationPrompt(input.scope, input.findings, validatorName, validatedAt),
|
|
110
|
-
schema:
|
|
115
|
+
schema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
111
116
|
parse(raw) {
|
|
112
117
|
const output = parseReviewOutput(raw);
|
|
113
118
|
return {
|
|
@@ -118,6 +123,7 @@ export async function validateReviewFindings(input: ReviewValidationInput): Prom
|
|
|
118
123
|
model: input.model,
|
|
119
124
|
thinkingLevel: input.thinkingLevel ?? null,
|
|
120
125
|
timeoutMs: input.timeoutMs ?? 120_000,
|
|
126
|
+
reliability: input.reliability,
|
|
121
127
|
});
|
|
122
128
|
|
|
123
129
|
if (result.status === "blocked") {
|
|
@@ -2,15 +2,17 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { FixPrSessionLedger } from "../fix-pr/types.js";
|
|
4
4
|
import type { PlatformPaths } from "../platform/types.js";
|
|
5
|
+
import type { WorkspaceTarget } from "../types.js";
|
|
6
|
+
import { getProjectTargetStatePath } from "../workspace/state-paths.js";
|
|
5
7
|
|
|
6
8
|
const SESSIONS_DIR = "fix-pr-sessions";
|
|
7
9
|
|
|
8
|
-
function getBaseDir(paths: PlatformPaths,
|
|
9
|
-
return paths
|
|
10
|
+
function getBaseDir(paths: PlatformPaths, target: WorkspaceTarget): string {
|
|
11
|
+
return getProjectTargetStatePath(paths, target, SESSIONS_DIR);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export function getSessionDir(paths: PlatformPaths,
|
|
13
|
-
return path.join(getBaseDir(paths,
|
|
14
|
+
export function getSessionDir(paths: PlatformPaths, target: WorkspaceTarget, sessionId: string): string {
|
|
15
|
+
return path.join(getBaseDir(paths, target), sessionId);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function generateFixPrSessionId(): string {
|
|
@@ -21,14 +23,14 @@ export function generateFixPrSessionId(): string {
|
|
|
21
23
|
return `fpr-${date}-${time}-${rand}`;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
export function createFixPrSession(paths: PlatformPaths,
|
|
25
|
-
const sessionDir = getSessionDir(paths,
|
|
26
|
+
export function createFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, ledger: FixPrSessionLedger): void {
|
|
27
|
+
const sessionDir = getSessionDir(paths, target, ledger.id);
|
|
26
28
|
fs.mkdirSync(path.join(sessionDir, "snapshots"), { recursive: true });
|
|
27
29
|
fs.writeFileSync(path.join(sessionDir, "ledger.json"), JSON.stringify(ledger, null, 2));
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
export function loadFixPrSession(paths: PlatformPaths,
|
|
31
|
-
const ledgerPath = path.join(getSessionDir(paths,
|
|
32
|
+
export function loadFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, sessionId: string): FixPrSessionLedger | null {
|
|
33
|
+
const ledgerPath = path.join(getSessionDir(paths, target, sessionId), "ledger.json");
|
|
32
34
|
if (!fs.existsSync(ledgerPath)) return null;
|
|
33
35
|
try {
|
|
34
36
|
return JSON.parse(fs.readFileSync(ledgerPath, "utf-8")) as FixPrSessionLedger;
|
|
@@ -37,14 +39,19 @@ export function loadFixPrSession(paths: PlatformPaths, cwd: string, sessionId: s
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
export function updateFixPrSession(paths: PlatformPaths,
|
|
41
|
-
const ledgerPath = path.join(getSessionDir(paths,
|
|
42
|
+
export function updateFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, ledger: FixPrSessionLedger): void {
|
|
43
|
+
const ledgerPath = path.join(getSessionDir(paths, target, ledger.id), "ledger.json");
|
|
42
44
|
ledger.updatedAt = new Date().toISOString();
|
|
43
45
|
fs.writeFileSync(ledgerPath, JSON.stringify(ledger, null, 2));
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
export function findActiveFixPrSession(
|
|
47
|
-
|
|
48
|
+
export function findActiveFixPrSession(
|
|
49
|
+
paths: PlatformPaths,
|
|
50
|
+
target: WorkspaceTarget,
|
|
51
|
+
repo: string,
|
|
52
|
+
prNumber: number,
|
|
53
|
+
): FixPrSessionLedger | null {
|
|
54
|
+
const baseDir = getBaseDir(paths, target);
|
|
48
55
|
if (!fs.existsSync(baseDir)) return null;
|
|
49
56
|
|
|
50
57
|
const dirs = fs.readdirSync(baseDir)
|
|
@@ -53,8 +60,8 @@ export function findActiveFixPrSession(paths: PlatformPaths, cwd: string): FixPr
|
|
|
53
60
|
.reverse();
|
|
54
61
|
|
|
55
62
|
for (const dir of dirs) {
|
|
56
|
-
const ledger = loadFixPrSession(paths,
|
|
57
|
-
if (ledger && ledger.status === "running") return ledger;
|
|
63
|
+
const ledger = loadFixPrSession(paths, target, dir);
|
|
64
|
+
if (ledger && ledger.status === "running" && ledger.repo === repo && ledger.prNumber === prNumber) return ledger;
|
|
58
65
|
}
|
|
59
66
|
return null;
|
|
60
67
|
}
|