feat(cli): sf --maintain drains self-feedback triage queue too

Extends the --maintain command (catalog refresh + quota refresh +
coverage audit) to also drain the self-feedback triage queue with
max=10 candidates per invocation. Combined with the daemon's 6h
maintenance timer that spawns `sf --maintain` in every configured
repo, this gives unattended cross-repo triage:

  Repo                       What gets triaged
  ────────────────────────── ─────────────────────────────────
  ~/code/singularity-forge   SF's own backlog (prompt-never-sent,
                             architecture defects, the 3
                             enhancement entries from today)
  ~/code/dr-repo             dr-repo's backlog (M005 flow
                             failures, agent friction, etc.)
  ~/code/centralcloud/*      whatever each subproject accrues

Both --maintain and `headless autonomous` use process.cwd() so they
target the right repo automatically. Interactive mode (plain `sf`)
deliberately does NOT auto-triage — that would spawn subagents while
the user is working in the same session, risking lock contention.

Triage failures stay non-fatal: catalog/quota/coverage work still
completes even if triage subagent dispatch hits the prompt-never-sent
bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-16 19:04:03 +02:00
parent 5a57549591
commit f7cd01df0a

View file

@ -812,9 +812,30 @@ if (cliFlags.maintain) {
const prefs = (loadEffectiveSFPreferences()?.preferences ?? {}) as Record<string, unknown>;
const coverage = computeBenchmarkCoverage(prefs);
writeBenchmarkCoverage(coverage);
// Self-feedback triage drain. The daemon's 6h maintenance timer fires
// `sf --maintain` in every allowed repo (set via daemon config), so
// triage runs unattended across the whole project portfolio. Bounded
// at max=10 per invocation to keep each run finite. Items beyond the
// bound flush on subsequent maintenance ticks.
let triageCount = 0;
try {
const { handleTriage } = await import("./headless-triage.js");
const triageResult = await handleTriage(process.cwd(), {
apply: true,
json: true, // suppress chatty stdout in the maintenance path
max: 10,
});
triageCount = triageResult.exitCode === 0 ? 10 : 0;
} catch (err) {
process.stderr.write(
`[sf --maintain] triage drain failed (non-fatal): ${
err instanceof Error ? err.message : String(err)
}\n`,
);
}
const ms = Date.now() - startedAt;
process.stdout.write(
`[sf --maintain] catalog + quota refresh + coverage audit done in ${ms}ms — coverage ${coverage.summary.coveredCount}/${coverage.summary.total} (${coverage.uncovered.length} uncovered)\n`,
`[sf --maintain] catalog + quota refresh + coverage audit + triage drain done in ${ms}ms — coverage ${coverage.summary.coveredCount}/${coverage.summary.total} (${coverage.uncovered.length} uncovered), triage attempted up to ${triageCount} candidates\n`,
);
} catch (err) {
process.stderr.write(