Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 0.7.1 - Unreleased

- Fixed revalidation prompts to compact historical and feature metadata and hard-cap metadata lists even when configured file limits are high, preventing provider input overflows, thanks @pai-scaffolde.

## 0.7.0 - 2026-06-15

- Removed the direct MiniMax HTTP provider and its transport dependency; provider integrations are now explicitly limited to coding harnesses and agent CLIs.
Expand Down
58 changes: 58 additions & 0 deletions src/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
REVIEW_PROMPT_FILE_CHAR_LIMIT,
buildFixPrompt,
buildRevalidatePrompt,
buildReviewPromptBundle,
} from "./prompt.js";
import { defaultConfig } from "./config.js";
Expand Down Expand Up @@ -106,6 +107,63 @@ describe("review prompt provenance", () => {
);
});

it("compacts revalidation feature metadata before provider submission", async () => {
const root = await fixtureRoot("clawpatch-revalidate-prompt-budget-");
await writeFixture(root, "src/index.ts", "export const value = 1;\n");
const largeFeature: FeatureRecord = {
...feature(),
ownedFiles: [{ path: "src/index.ts", reason: "primary" }],
contextFiles: Array.from({ length: 2_500 }, (_, index) => ({
path: `docs/context-${index}.md`,
reason: `context ${"x".repeat(300)}`,
})),
tests: Array.from({ length: 200 }, (_, index) => ({
path: `tests/context-${index}.test.ts`,
command: null,
})),
findingIds: Array.from({ length: 200 }, (_, index) => `fnd_large_${index}`),
patchAttemptIds: Array.from({ length: 200 }, (_, index) => `patch_large_${index}`),
analysisHistory: Array.from({ length: 40 }, (_, index) => ({
runId: `run_large_${index}`,
kind: "review",
summary: `large analysis ${"x".repeat(1_000)}`,
provider: "codex",
model: null,
reasoningEffort: null,
createdAt: new Date(2026, 0, index + 1).toISOString(),
})),
};
const largeFinding: FindingRecord = {
...finding("src/index.ts"),
history: Array.from({ length: 40 }, (_, index) => ({
runId: `run_history_${index}`,
kind: "revalidate",
status: "open",
note: null,
reasoning: `large history ${"x".repeat(1_000)}`,
commands: [],
createdAt: new Date(2026, 0, index + 1).toISOString(),
})),
};
const config = defaultConfig();
config.review.maxOwnedFiles = 10_000;
config.review.maxContextFiles = 10_000;

const prompt = await buildRevalidatePrompt(root, largeFinding, largeFeature, [], config);

expect(prompt.length).toBeLessThan(1_048_576);
expect(prompt).toContain('"omittedContextFiles": 2450');
expect(prompt).toContain('"omittedTests": 150');
expect(prompt).toContain('"omittedFindingIds": 150');
expect(prompt).toContain('"omittedPatchAttemptIds": 150');
expect(prompt).toContain('"omittedAnalysisHistory": 37');
expect(prompt).toContain('"omittedHistory": 35');
expect(prompt).toContain("--- src/index.ts");
expect(prompt).not.toContain('"path": "docs/context-2499.md"');
expect(prompt).not.toContain("large analysis 0");
expect(prompt).not.toContain("large history 0");
});

it("does not list duplicate-skipped included files as omitted", async () => {
const root = await fixtureRoot("clawpatch-prompt-duplicate-context-");
await writeFixture(root, "src/index.ts", "export const value = 1;\n");
Expand Down
52 changes: 50 additions & 2 deletions src/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type ReviewMode = "default" | "deslopify";

export const REVIEW_PROMPT_FILE_CHAR_LIMIT = 24_000;
const REVALIDATE_FILE_CONTEXT_CHAR_LIMIT = 120_000;
const REVALIDATE_METADATA_LIST_LIMIT = 50;

export type ReviewPromptFileRole = "owned" | "context" | "test";

Expand Down Expand Up @@ -432,10 +433,10 @@ Return strict JSON only:
{"outcome":"fixed|open|false-positive|uncertain","reasoning":"string","commands":["string"]}

Finding:
${JSON.stringify(finding, null, 2)}
${JSON.stringify(revalidationFindingEvidence(finding), null, 2)}

Feature:
${JSON.stringify(feature, null, 2)}
${JSON.stringify(revalidationFeatureEvidence(feature, config), null, 2)}

Linked patch attempts:
${JSON.stringify(
Expand All @@ -453,6 +454,53 @@ Relevant current files:
${fileBlocks.join("\n\n")}`;
}

function revalidationFindingEvidence(finding: FindingRecord): object {
return {
...finding,
history: finding.history.slice(-5),
omittedHistory: Math.max(0, finding.history.length - 5),
};
}

function revalidationFeatureEvidence(feature: FeatureRecord, config: ClawpatchConfig): object {
const ownedLimit = Math.min(config.review.maxOwnedFiles, REVALIDATE_METADATA_LIST_LIMIT);
const contextLimit = Math.min(config.review.maxContextFiles, REVALIDATE_METADATA_LIST_LIMIT);
const testLimit = Math.min(config.review.maxContextFiles, REVALIDATE_METADATA_LIST_LIMIT);
return {
schemaVersion: feature.schemaVersion,
featureId: feature.featureId,
title: feature.title,
summary: feature.summary,
kind: feature.kind,
source: feature.source,
confidence: feature.confidence,
entrypoints: feature.entrypoints.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
omittedEntrypoints: Math.max(0, feature.entrypoints.length - REVALIDATE_METADATA_LIST_LIMIT),
ownedFiles: feature.ownedFiles.slice(0, ownedLimit),
omittedOwnedFiles: Math.max(0, feature.ownedFiles.length - ownedLimit),
contextFiles: feature.contextFiles.slice(0, contextLimit),
omittedContextFiles: Math.max(0, feature.contextFiles.length - contextLimit),
tests: feature.tests.slice(0, testLimit),
omittedTests: Math.max(0, feature.tests.length - testLimit),
tags: feature.tags.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
omittedTags: Math.max(0, feature.tags.length - REVALIDATE_METADATA_LIST_LIMIT),
trustBoundaries: feature.trustBoundaries,
status: feature.status,
lock: feature.lock,
findingIds: feature.findingIds.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
omittedFindingIds: Math.max(0, feature.findingIds.length - REVALIDATE_METADATA_LIST_LIMIT),
patchAttemptIds: feature.patchAttemptIds.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
omittedPatchAttemptIds: Math.max(
0,
feature.patchAttemptIds.length - REVALIDATE_METADATA_LIST_LIMIT,
),
analysisHistory: feature.analysisHistory.slice(-3),
omittedAnalysisHistory: Math.max(0, feature.analysisHistory.length - 3),
createdAt: feature.createdAt,
updatedAt: feature.updatedAt,
};
}

function revalidationPatchEvidence(
patch: PatchAttempt,
expectedValidationCommands: readonly string[],
Expand Down