fix(headless-triage): --run takes precedence over --json/--list

Discovered via dogfood: \`sf headless triage --run --json\` short-
circuited to the candidate-list JSON before reaching the dispatch
path, so the run never happened.

--run is the action; --json/--list describe output format. Restructure
so --run always dispatches; --json then controls whether the run
result is JSON vs human text. Without --run, --json/--list still emit
the candidate digest as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-14 08:29:11 +02:00
parent 65c1914b1f
commit a6c36a4b6b

View file

@ -156,41 +156,49 @@ export async function handleTriage(
return { exitCode: 0 };
}
if (options.json) {
process.stdout.write(
`${JSON.stringify({
ok: true,
count: candidates.length,
candidates: candidates.map((c) => ({
id: c.id,
kind: c.kind,
severity: c.severity,
summary: c.summary,
ts: c.ts,
impact: c.impactScore ?? null,
effort: c.effortEstimate ?? null,
})),
})}\n`,
);
return { exitCode: 0 };
}
if (options.list) {
process.stdout.write(
`${candidates.length} candidate${candidates.length === 1 ? "" : "s"} (priority: impact↓ effort↑ ts↑)\n\n`,
);
for (const c of candidates) {
const impact = c.impactScore != null ? `i${c.impactScore}` : "i?";
const effort = c.effortEstimate != null ? `e${c.effortEstimate}` : "e?";
// --run takes precedence over --json/--list because they describe the
// OUTPUT FORMAT, not the action. With --run, --json controls whether
// the run-result is JSON vs. human text. Without --run, --json emits
// the candidate digest as JSON (the inspect path).
if (!options.run) {
if (options.json) {
process.stdout.write(
` [${c.severity}] ${impact} ${effort} ${c.id} ${c.kind}\n`,
`${JSON.stringify({
ok: true,
count: candidates.length,
candidates: candidates.map((c) => ({
id: c.id,
kind: c.kind,
severity: c.severity,
summary: c.summary,
ts: c.ts,
impact: c.impactScore ?? null,
effort: c.effortEstimate ?? null,
})),
})}\n`,
);
process.stdout.write(` ${c.summary}\n`);
return { exitCode: 0 };
}
if (options.list) {
process.stdout.write(
`${candidates.length} candidate${candidates.length === 1 ? "" : "s"} (priority: impact↓ effort↑ ts↑)\n\n`,
);
for (const c of candidates) {
const impact = c.impactScore != null ? `i${c.impactScore}` : "i?";
const effort =
c.effortEstimate != null ? `e${c.effortEstimate}` : "e?";
process.stdout.write(
` [${c.severity}] ${impact} ${effort} ${c.id} ${c.kind}\n`,
);
process.stdout.write(` ${c.summary}\n`);
}
return { exitCode: 0 };
}
return { exitCode: 0 };
}
// Default: emit the canonical triage prompt for piping into a model.
// Render the canonical triage prompt (used by both the default
// pipe-to-model output and the --run dispatch path below).
let prompt: string;
try {
prompt = drainModule.buildInlineFixPrompt(candidates);