Skip to content
Closed
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.2 - Unreleased

- Reordered review prompts so shared instructions and the JSON contract precede feature-specific context, improving provider prompt-cache reuse across feature reviews.

## 0.7.1 - 2026-07-20

### Highlights
Expand Down
35 changes: 35 additions & 0 deletions src/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,41 @@ import { fixtureRoot, writeFixture } from "./test-helpers.js";
import type { FeatureRecord, FindingRecord, ProjectRecord } from "./types.js";

describe("review prompt provenance", () => {
it("keeps shared review instructions ahead of feature-specific context", async () => {
const root = await fixtureRoot("clawpatch-prompt-cache-prefix-");
await writeFixture(root, "src/index.ts", "export const value = 1;\n");
await writeFixture(root, "src/second.ts", "export const second = 2;\n");
const firstFeature = feature();
const secondFeature: FeatureRecord = {
...feature(),
featureId: "feat_second",
title: "Second feature",
summary: "A different semantic review unit",
ownedFiles: [{ path: "src/second.ts", reason: "primary" }],
contextFiles: [],
tests: [],
};

const [first, second] = await Promise.all([
buildReviewPromptBundle(root, project(root), firstFeature, defaultConfig()),
buildReviewPromptBundle(root, project(root), secondFeature, defaultConfig()),
]);
const firstFeatureIndex = first.prompt.indexOf("Feature:\n");
const secondFeatureIndex = second.prompt.indexOf("Feature:\n");
const firstPrefix = first.prompt.slice(0, firstFeatureIndex);
const secondPrefix = second.prompt.slice(0, secondFeatureIndex);

expect(firstFeatureIndex).toBeGreaterThan(0);
expect(secondFeatureIndex).toBeGreaterThan(0);
expect(firstPrefix).toBe(secondPrefix);
expect(firstPrefix).toContain("Review categories:");
expect(firstPrefix).toContain("JSON shape:");
expect(first.prompt.indexOf("JSON shape:")).toBeLessThan(firstFeatureIndex);
expect(first.prompt.indexOf("Valid evidence paths are exactly:")).toBeGreaterThan(
firstFeatureIndex,
);
});

it("records included, omitted, and truncated review prompt context", async () => {
const root = await fixtureRoot("clawpatch-prompt-provenance-");
await writeFixture(root, "src/index.ts", "export const value = 1;\n");
Expand Down
31 changes: 16 additions & 15 deletions src/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,7 @@ ${customPrompt.trim()}

Return strict JSON only. No markdown fences.

Project:
${JSON.stringify({ name: project.name, detected: project.detected }, null, 2)}

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

${customBlock}Review categories:
Review categories:
- correctness bugs
- security issues
- race/concurrency bugs
Expand All @@ -223,7 +217,7 @@ Shell and workflow review:
- Focus this rule on captured or parsed values, not human-facing logging fallbacks like some_command || echo "failed".
- Recommend separating the primary command capture from fallback assignment, for example if ! status="$(cmd)"; then status="fallback"; fi.

${reviewModeInstructions(mode)}${cudaBlock}
${reviewModeInstructions(mode)}

${languageGuidance}

Expand All @@ -235,18 +229,13 @@ issues: when the same bug pattern appears in multiple owned files, emit one find
with multiple evidence refs instead of separate one-off findings.

Avoid speculative low-evidence findings. Evidence must point at included files.
Valid evidence paths are exactly:
${validEvidencePaths.map((path) => `- ${path}`).join("\n")}
Feature metadata paths are not valid evidence unless listed above.
Feature metadata paths are not valid evidence unless listed in the review input below.
When providing evidence line ranges, use the line-number gutter in the Files section.
Do not inspect files beyond the shown excerpts for evidence. If an excerpt is truncated,
only cite lines that appear in the Files section.
Set evidence.quote to null; line ranges are enough for validation.

Prompt context:
${JSON.stringify(promptContext, null, 2)}

JSON shape:
${customBlock}JSON shape:
{
"findings": [
{
Expand All @@ -266,6 +255,18 @@ JSON shape:
"inspected": {"files":["string"],"symbols":["string"],"notes":["string"]}
}

Project:
${JSON.stringify({ name: project.name, detected: project.detected }, null, 2)}

Feature:
${JSON.stringify(reviewFeatureView(feature), null, 2)}
${cudaBlock}
Valid evidence paths are exactly:
${validEvidencePaths.map((path) => `- ${path}`).join("\n")}

Prompt context:
${JSON.stringify(promptContext, null, 2)}

Files:
${fileBlocks.join("\n\n")}`;
const promptBytes = Buffer.byteLength(prompt, "utf8");
Expand Down