fix(verify): ignore stale broad cargo preferences

This commit is contained in:
Mikael Hugo 2026-05-15 07:06:17 +02:00
parent d1ca3d035c
commit c5850c8039
2 changed files with 87 additions and 8 deletions

View file

@ -26,7 +26,7 @@ test("discoverCommands_when_preferences_reference_missing_repo_dirs_falls_back_t
const root = makeProject();
writeFileSync(join(root, "pyproject.toml"), "[project]\nname = 'demo'\n");
const staleCommand =
"bash -c 'set -e; for d in \"scanner\" \"supervisor\"; do (cd \"$d\" && cargo check); done'";
'bash -c \'set -e; for d in "scanner" "supervisor"; do (cd "$d" && cargo check); done\'';
const result = discoverCommands({
cwd: root,
@ -44,7 +44,7 @@ test("discoverCommands_when_for_loop_dirs_exist_keeps_preference_commands", () =
mkdirSync(join(root, "scanner"));
mkdirSync(join(root, "supervisor"));
const command =
"bash -c 'set -e; for d in \"scanner\" \"supervisor\"; do (cd \"$d\" && cargo check); done'";
'bash -c \'set -e; for d in "scanner" "supervisor"; do (cd "$d" && cargo check); done\'';
const result = discoverCommands({
cwd: root,
@ -57,13 +57,56 @@ test("discoverCommands_when_for_loop_dirs_exist_keeps_preference_commands", () =
});
});
test("discoverCommands_when_broad_cargo_preferences_conflict_with_task_plan_uses_task_plan", () => {
const root = makeProject();
mkdirSync(join(root, "scanner"));
mkdirSync(join(root, "supervisor"));
writeFileSync(join(root, "pyproject.toml"), "[project]\nname = 'demo'\n");
const command =
'bash -c \'set -e; for d in "scanner" "supervisor"; do (cd "$d" && cargo check); done\'';
const result = discoverCommands({
cwd: root,
preferenceCommands: [command, "uv run pytest -x"],
taskPlanVerify: "make pytest",
});
assert.deepEqual(result, {
commands: ["make pytest"],
source: "task-plan",
});
});
test("discoverCommands_when_broad_cargo_preferences_conflict_with_makefile_uses_python_project", () => {
const root = makeProject();
mkdirSync(join(root, "scanner"));
mkdirSync(join(root, "supervisor"));
writeFileSync(join(root, "pyproject.toml"), "[project]\nname = 'demo'\n");
writeFileSync(
join(root, "Makefile"),
"verify-python:\n\tpython -m pytest -q\n",
);
const command =
'bash -c \'set -e; for d in "scanner" "supervisor"; do (cd "$d" && cargo check); done\'';
const result = discoverCommands({
cwd: root,
preferenceCommands: [command, "uv run pytest -x"],
});
assert.deepEqual(result, {
commands: ["python -m pytest -q"],
source: "python-project",
});
});
test("hasMissingProjectPathReferences_detects_absent_for_loop_cd_targets", () => {
const root = makeProject();
assert.equal(
hasMissingProjectPathReferences(
root,
"bash -c 'for d in \"scanner\"; do (cd \"$d\" && cargo test); done'",
'bash -c \'for d in "scanner"; do (cd "$d" && cargo test); done\'',
),
true,
);

View file

@ -22,7 +22,9 @@ function truncate(value, maxBytes) {
const PACKAGE_SCRIPT_KEYS = ["typecheck", "lint", "test"];
function extractForLoopCdDirs(command) {
const match = command.match(/for\s+d\s+in\s+(.+?);\s*do\s*\(\s*cd\s+["']?\$d["']?/s);
const match = command.match(
/for\s+d\s+in\s+(.+?);\s*do\s*\(\s*cd\s+["']?\$d["']?/s,
);
if (!match) return null;
const segment = match[1];
const dirs = [];
@ -51,10 +53,42 @@ export function hasMissingProjectPathReferences(cwd, command) {
return dirs.some((dir) => !existsSync(join(cwd, dir)));
}
function normalizeConfiguredCommands(cwd, commands) {
function isBroadMultiProjectCargoCommand(command) {
const dirs = extractForLoopCdDirs(command);
if (!dirs || dirs.length < 2) return false;
return /\bcargo\s+(fmt|check|test|clippy)\b/.test(command);
}
function hasMakefileVerifyTarget(cwd) {
const makefilePath = join(cwd, "Makefile");
if (!existsSync(makefilePath)) return false;
try {
return /^verify[-\w]*:/m.test(readFileSync(makefilePath, "utf-8"));
} catch {
return false;
}
}
function hasProjectLocalVerificationSignal(cwd, taskPlanVerify) {
if (taskPlanVerify?.trim()) return true;
if (hasMakefileVerifyTarget(cwd)) return true;
return (
existsSync(join(cwd, "package.json")) ||
existsSync(join(cwd, "pyproject.toml"))
);
}
function looksLikeStalePreferenceSet(cwd, commands, taskPlanVerify) {
if (!hasProjectLocalVerificationSignal(cwd, taskPlanVerify)) return false;
return commands.some((cmd) => isBroadMultiProjectCargoCommand(cmd));
}
function normalizeConfiguredCommands(cwd, commands, taskPlanVerify = "") {
const filtered = (commands ?? []).map((c) => c.trim()).filter(Boolean);
if (filtered.length === 0) return { commands: [], stale: false };
const stale = filtered.some((cmd) => hasMissingProjectPathReferences(cwd, cmd));
const stale =
filtered.some((cmd) => hasMissingProjectPathReferences(cwd, cmd)) ||
looksLikeStalePreferenceSet(cwd, filtered, taskPlanVerify);
return { commands: stale ? [] : filtered, stale };
}
@ -102,6 +136,7 @@ export function discoverCommands(options) {
const configured = normalizeConfiguredCommands(
options.cwd,
options.preferenceCommands,
options.taskPlanVerify,
);
if (configured.commands.length > 0) {
return { commands: configured.commands, source: "preference" };
@ -114,8 +149,9 @@ export function discoverCommands(options) {
.map((c) => c.trim())
.filter(Boolean);
const configured = normalizeConfiguredCommands(options.cwd, rawCommands);
const commands = configured.commands
.filter((c) => sanitizeCommand(c) !== null);
const commands = configured.commands.filter(
(c) => sanitizeCommand(c) !== null,
);
if (commands.length > 0) {
return { commands, source: "task-plan" };
}