From e172347169cda82f615eaea7b0211a6403484148 Mon Sep 17 00:00:00 2001 From: PAI Date: Tue, 30 Jun 2026 13:11:33 -0700 Subject: [PATCH 1/3] fix: compact revalidation prompt metadata --- src/prompt.test.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++++ src/prompt.ts | 52 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/prompt.test.ts b/src/prompt.test.ts index 94ad672..b8f73f4 100644 --- a/src/prompt.test.ts +++ b/src/prompt.test.ts @@ -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"; @@ -106,6 +107,66 @@ 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 prompt = await buildRevalidatePrompt( + root, + largeFinding, + largeFeature, + [], + defaultConfig(), + ); + + expect(prompt.length).toBeLessThan(1_048_576); + expect(prompt).toContain('"omittedContextFiles": 2476'); + expect(prompt).toContain('"omittedTests": 176'); + 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("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"); diff --git a/src/prompt.ts b/src/prompt.ts index 32797df..bb8878f 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -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"; @@ -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( @@ -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 = config.review.maxOwnedFiles; + const contextLimit = config.review.maxContextFiles; + const testLimit = config.review.maxContextFiles; + 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[], From b9895f5302632bfe0700d3456087b59c586961b0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 1 Jul 2026 07:15:51 +0100 Subject: [PATCH 2/3] fix(prompt): hard-cap revalidation metadata --- CHANGELOG.md | 2 ++ src/prompt.test.ts | 15 ++++++--------- src/prompt.ts | 6 +++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d76db..f3d314f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/prompt.test.ts b/src/prompt.test.ts index b8f73f4..d7b65da 100644 --- a/src/prompt.test.ts +++ b/src/prompt.test.ts @@ -145,18 +145,15 @@ describe("review prompt provenance", () => { 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, - [], - defaultConfig(), - ); + const prompt = await buildRevalidatePrompt(root, largeFinding, largeFeature, [], config); expect(prompt.length).toBeLessThan(1_048_576); - expect(prompt).toContain('"omittedContextFiles": 2476'); - expect(prompt).toContain('"omittedTests": 176'); + 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'); diff --git a/src/prompt.ts b/src/prompt.ts index bb8878f..9878942 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -463,9 +463,9 @@ function revalidationFindingEvidence(finding: FindingRecord): object { } function revalidationFeatureEvidence(feature: FeatureRecord, config: ClawpatchConfig): object { - const ownedLimit = config.review.maxOwnedFiles; - const contextLimit = config.review.maxContextFiles; - const testLimit = config.review.maxContextFiles; + 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, From 9698057bc69693de521f7e55e506b08ff7fd0d77 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 1 Jul 2026 07:22:40 +0100 Subject: [PATCH 3/3] test(prompt): make metadata assertion platform-neutral --- src/prompt.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prompt.test.ts b/src/prompt.test.ts index d7b65da..45b2859 100644 --- a/src/prompt.test.ts +++ b/src/prompt.test.ts @@ -159,7 +159,7 @@ describe("review prompt provenance", () => { expect(prompt).toContain('"omittedAnalysisHistory": 37'); expect(prompt).toContain('"omittedHistory": 35'); expect(prompt).toContain("--- src/index.ts"); - expect(prompt).not.toContain("docs/context-2499.md"); + expect(prompt).not.toContain('"path": "docs/context-2499.md"'); expect(prompt).not.toContain("large analysis 0"); expect(prompt).not.toContain("large history 0"); });