fix: parse annotated pre-exec file paths

Strip planner-style path annotations before pre-execution checks compare\ninputs and expected outputs. This keeps existing files, prior outputs,\nand ordering checks aligned even when task-plan entries include inline\ndescriptions.\n\nCloses #3742
This commit is contained in:
mastertyko 2026-04-08 01:25:24 +02:00
parent 2ff938596b
commit 12b6a01dae
2 changed files with 85 additions and 2 deletions

View file

@ -238,8 +238,7 @@ export async function checkPackageExistence(
export function normalizeFilePath(filePath: string): string {
if (!filePath) return filePath;
// Strip backtick wrapping from LLM-generated paths (#3649)
let normalized = filePath.replace(/`/g, "");
let normalized = extractPathFromAnnotation(filePath);
// Normalize path separators to forward slashes
normalized = normalized.replace(/\\/g, "/");
@ -260,6 +259,24 @@ export function normalizeFilePath(filePath: string): string {
return normalized;
}
function extractPathFromAnnotation(raw: string): string {
const trimmed = raw.trim();
if (!trimmed) return trimmed;
const backtickMatch = trimmed.match(/^`([^`]+)`(?:\s+[—–-]\s+.*)?$/);
if (backtickMatch) {
return backtickMatch[1].trim();
}
const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
if (annotatedMatch) {
return annotatedMatch[1].trim();
}
// Fall back to the original behavior for already-plain paths.
return trimmed.replace(/`/g, "");
}
/**
* Build a set of files that will be created by tasks up to (but not including) taskIndex.
* All paths are normalized for consistent comparison.

View file

@ -1083,11 +1083,77 @@ describe("checkTaskOrdering false positive regression (#3677)", () => {
const results = checkTaskOrdering(tasks, "/tmp");
assert.equal(results.length, 0, "Normalized task.files path should not trigger a false positive");
});
test("annotated inputs still trigger ordering violations against later plain outputs", () => {
const tasks = [
createTask({
id: "T01",
sequence: 0,
files: [],
inputs: ["`later.ts` — needed first"],
expected_output: [],
}),
createTask({
id: "T02",
sequence: 1,
files: [],
inputs: [],
expected_output: ["later.ts"],
}),
];
const results = checkTaskOrdering(tasks, "/tmp");
assert.equal(results.length, 1, "Annotated inputs should still match later plain expected_output entries");
assert.equal(results[0].target, "`later.ts` — needed first");
assert.ok(results[0].message.includes("sequence violation"));
});
});
// ─── checkFilePathConsistency additional edge cases ──────────────────────────
describe("checkFilePathConsistency additional edge cases", () => {
test("annotated inputs match files that already exist on disk", () => {
const tempDir = join(tmpdir(), `pre-exec-test-annotated-input-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
writeFileSync(join(tempDir, "existing.ts"), "// content");
try {
const tasks = [
createTask({
id: "T01",
files: [],
inputs: ["`existing.ts` — file already on disk"],
expected_output: [],
}),
];
const results = checkFilePathConsistency(tasks, tempDir);
assert.equal(results.length, 0, "Annotated inputs should resolve to the on-disk file path");
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
test("plain inputs match prior annotated expected outputs", () => {
const tasks = [
createTask({
id: "T01",
files: [],
inputs: [],
expected_output: ["`generated.ts` — created earlier"],
}),
createTask({
id: "T02",
files: [],
inputs: ["generated.ts"],
expected_output: [],
}),
];
const results = checkFilePathConsistency(tasks, "/tmp");
assert.equal(results.length, 0, "Prior annotated expected_output entries should satisfy later plain inputs");
});
test("inputs referencing glob-like patterns should not crash", () => {
// A glob pattern in inputs is unusual but should be handled gracefully.
// The file won't exist on disk, so it should produce a blocking result.