Skip to content

Commit 1ffaf19

Browse files
fix(prompt): bound revalidation metadata (#148)
* fix: compact revalidation prompt metadata * fix(prompt): hard-cap revalidation metadata * test(prompt): make metadata assertion platform-neutral --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent bfe68d8 commit 1ffaf19

3 files changed

Lines changed: 110 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## 0.7.1 - Unreleased
44

5+
- 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.
6+
57
## 0.7.0 - 2026-06-15
68

79
- Removed the direct MiniMax HTTP provider and its transport dependency; provider integrations are now explicitly limited to coding harnesses and agent CLIs.

src/prompt.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
22
import {
33
REVIEW_PROMPT_FILE_CHAR_LIMIT,
44
buildFixPrompt,
5+
buildRevalidatePrompt,
56
buildReviewPromptBundle,
67
} from "./prompt.js";
78
import { defaultConfig } from "./config.js";
@@ -106,6 +107,63 @@ describe("review prompt provenance", () => {
106107
);
107108
});
108109

110+
it("compacts revalidation feature metadata before provider submission", async () => {
111+
const root = await fixtureRoot("clawpatch-revalidate-prompt-budget-");
112+
await writeFixture(root, "src/index.ts", "export const value = 1;\n");
113+
const largeFeature: FeatureRecord = {
114+
...feature(),
115+
ownedFiles: [{ path: "src/index.ts", reason: "primary" }],
116+
contextFiles: Array.from({ length: 2_500 }, (_, index) => ({
117+
path: `docs/context-${index}.md`,
118+
reason: `context ${"x".repeat(300)}`,
119+
})),
120+
tests: Array.from({ length: 200 }, (_, index) => ({
121+
path: `tests/context-${index}.test.ts`,
122+
command: null,
123+
})),
124+
findingIds: Array.from({ length: 200 }, (_, index) => `fnd_large_${index}`),
125+
patchAttemptIds: Array.from({ length: 200 }, (_, index) => `patch_large_${index}`),
126+
analysisHistory: Array.from({ length: 40 }, (_, index) => ({
127+
runId: `run_large_${index}`,
128+
kind: "review",
129+
summary: `large analysis ${"x".repeat(1_000)}`,
130+
provider: "codex",
131+
model: null,
132+
reasoningEffort: null,
133+
createdAt: new Date(2026, 0, index + 1).toISOString(),
134+
})),
135+
};
136+
const largeFinding: FindingRecord = {
137+
...finding("src/index.ts"),
138+
history: Array.from({ length: 40 }, (_, index) => ({
139+
runId: `run_history_${index}`,
140+
kind: "revalidate",
141+
status: "open",
142+
note: null,
143+
reasoning: `large history ${"x".repeat(1_000)}`,
144+
commands: [],
145+
createdAt: new Date(2026, 0, index + 1).toISOString(),
146+
})),
147+
};
148+
const config = defaultConfig();
149+
config.review.maxOwnedFiles = 10_000;
150+
config.review.maxContextFiles = 10_000;
151+
152+
const prompt = await buildRevalidatePrompt(root, largeFinding, largeFeature, [], config);
153+
154+
expect(prompt.length).toBeLessThan(1_048_576);
155+
expect(prompt).toContain('"omittedContextFiles": 2450');
156+
expect(prompt).toContain('"omittedTests": 150');
157+
expect(prompt).toContain('"omittedFindingIds": 150');
158+
expect(prompt).toContain('"omittedPatchAttemptIds": 150');
159+
expect(prompt).toContain('"omittedAnalysisHistory": 37');
160+
expect(prompt).toContain('"omittedHistory": 35');
161+
expect(prompt).toContain("--- src/index.ts");
162+
expect(prompt).not.toContain('"path": "docs/context-2499.md"');
163+
expect(prompt).not.toContain("large analysis 0");
164+
expect(prompt).not.toContain("large history 0");
165+
});
166+
109167
it("does not list duplicate-skipped included files as omitted", async () => {
110168
const root = await fixtureRoot("clawpatch-prompt-duplicate-context-");
111169
await writeFixture(root, "src/index.ts", "export const value = 1;\n");

src/prompt.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type ReviewMode = "default" | "deslopify";
1414

1515
export const REVIEW_PROMPT_FILE_CHAR_LIMIT = 24_000;
1616
const REVALIDATE_FILE_CONTEXT_CHAR_LIMIT = 120_000;
17+
const REVALIDATE_METADATA_LIST_LIMIT = 50;
1718

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

@@ -432,10 +433,10 @@ Return strict JSON only:
432433
{"outcome":"fixed|open|false-positive|uncertain","reasoning":"string","commands":["string"]}
433434
434435
Finding:
435-
${JSON.stringify(finding, null, 2)}
436+
${JSON.stringify(revalidationFindingEvidence(finding), null, 2)}
436437
437438
Feature:
438-
${JSON.stringify(feature, null, 2)}
439+
${JSON.stringify(revalidationFeatureEvidence(feature, config), null, 2)}
439440
440441
Linked patch attempts:
441442
${JSON.stringify(
@@ -453,6 +454,53 @@ Relevant current files:
453454
${fileBlocks.join("\n\n")}`;
454455
}
455456

457+
function revalidationFindingEvidence(finding: FindingRecord): object {
458+
return {
459+
...finding,
460+
history: finding.history.slice(-5),
461+
omittedHistory: Math.max(0, finding.history.length - 5),
462+
};
463+
}
464+
465+
function revalidationFeatureEvidence(feature: FeatureRecord, config: ClawpatchConfig): object {
466+
const ownedLimit = Math.min(config.review.maxOwnedFiles, REVALIDATE_METADATA_LIST_LIMIT);
467+
const contextLimit = Math.min(config.review.maxContextFiles, REVALIDATE_METADATA_LIST_LIMIT);
468+
const testLimit = Math.min(config.review.maxContextFiles, REVALIDATE_METADATA_LIST_LIMIT);
469+
return {
470+
schemaVersion: feature.schemaVersion,
471+
featureId: feature.featureId,
472+
title: feature.title,
473+
summary: feature.summary,
474+
kind: feature.kind,
475+
source: feature.source,
476+
confidence: feature.confidence,
477+
entrypoints: feature.entrypoints.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
478+
omittedEntrypoints: Math.max(0, feature.entrypoints.length - REVALIDATE_METADATA_LIST_LIMIT),
479+
ownedFiles: feature.ownedFiles.slice(0, ownedLimit),
480+
omittedOwnedFiles: Math.max(0, feature.ownedFiles.length - ownedLimit),
481+
contextFiles: feature.contextFiles.slice(0, contextLimit),
482+
omittedContextFiles: Math.max(0, feature.contextFiles.length - contextLimit),
483+
tests: feature.tests.slice(0, testLimit),
484+
omittedTests: Math.max(0, feature.tests.length - testLimit),
485+
tags: feature.tags.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
486+
omittedTags: Math.max(0, feature.tags.length - REVALIDATE_METADATA_LIST_LIMIT),
487+
trustBoundaries: feature.trustBoundaries,
488+
status: feature.status,
489+
lock: feature.lock,
490+
findingIds: feature.findingIds.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
491+
omittedFindingIds: Math.max(0, feature.findingIds.length - REVALIDATE_METADATA_LIST_LIMIT),
492+
patchAttemptIds: feature.patchAttemptIds.slice(0, REVALIDATE_METADATA_LIST_LIMIT),
493+
omittedPatchAttemptIds: Math.max(
494+
0,
495+
feature.patchAttemptIds.length - REVALIDATE_METADATA_LIST_LIMIT,
496+
),
497+
analysisHistory: feature.analysisHistory.slice(-3),
498+
omittedAnalysisHistory: Math.max(0, feature.analysisHistory.length - 3),
499+
createdAt: feature.createdAt,
500+
updatedAt: feature.updatedAt,
501+
};
502+
}
503+
456504
function revalidationPatchEvidence(
457505
patch: PatchAttempt,
458506
expectedValidationCommands: readonly string[],

0 commit comments

Comments
 (0)