feat(headless,auto): surface self-feedback queue at autonomous-loop idle
Two thin slices toward sf-mp4rxkwb-l4baga: 1. Help text. The triage and reflect commands have shipped over the last few commits but neither was discoverable via `sf headless help`. Add both to the command list + add five usage examples covering the piping and --run patterns. 2. Bail-time queue notifier. When the autonomous loop is about to break for "no-active-milestone" or "milestone-complete" while open self-feedback entries still exist, surface the queue with a clear pointer to `sf headless triage --list` / `--run`. Best-effort wrapper that never throws — the proper fix (triage as a real unit type with begin/dispatch/checkpoint/complete lifecycle) is the larger remaining slice of the parent entry; this just makes the queue VISIBLE at the exact moment operators historically lost track of it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
34521814cc
commit
001740680b
2 changed files with 38 additions and 0 deletions
|
|
@ -225,6 +225,7 @@ const SUBCOMMAND_HELP: Record<string, string> = {
|
||||||
" query Machine snapshot: JSON state + next dispatch + costs (no LLM)",
|
" query Machine snapshot: JSON state + next dispatch + costs (no LLM)",
|
||||||
" usage Live LLM-provider usage snapshot (today: gemini-cli tier + per-model quota)",
|
" usage Live LLM-provider usage snapshot (today: gemini-cli tier + per-model quota)",
|
||||||
" reflect Assemble reflection corpus + render prompt for cross-corpus pattern analysis (--json for raw, --run to dispatch to gemini-cli, --model <id> to override)",
|
" reflect Assemble reflection corpus + render prompt for cross-corpus pattern analysis (--json for raw, --run to dispatch to gemini-cli, --model <id> to override)",
|
||||||
|
" triage Render canonical self-feedback triage prompt for piping into a model (--list for digest, --json for structured, --max N to cap, --run to dispatch + write decisions to .sf/triage/decisions/, --model <id> to override)",
|
||||||
"",
|
"",
|
||||||
"new-milestone flags:",
|
"new-milestone flags:",
|
||||||
" --context <path> Path to spec/PRD file (use '-' for stdin)",
|
" --context <path> Path to spec/PRD file (use '-' for stdin)",
|
||||||
|
|
@ -255,6 +256,11 @@ const SUBCOMMAND_HELP: Record<string, string> = {
|
||||||
" sf headless query Instant machine JSON state snapshot",
|
" sf headless query Instant machine JSON state snapshot",
|
||||||
" sf headless status uok UOK gate health table (last 24h)",
|
" sf headless status uok UOK gate health table (last 24h)",
|
||||||
" sf headless status uok --json UOK gate health as JSON",
|
" sf headless status uok --json UOK gate health as JSON",
|
||||||
|
" sf headless triage --list Self-feedback queue digest (impact↓ effort↑ ts↑)",
|
||||||
|
" sf headless triage | sf-some-model Pipe triage prompt to any model",
|
||||||
|
" sf headless triage --run Dispatch triage to default model + write decisions",
|
||||||
|
" sf headless reflect Render reflection prompt for piping",
|
||||||
|
" sf headless reflect --run Dispatch reflection + write report",
|
||||||
"",
|
"",
|
||||||
"Exit codes: 0 = success, 1 = error/timeout, 10 = blocked, 11 = cancelled",
|
"Exit codes: 0 = success, 1 = error/timeout, 10 = blocked, 11 = cancelled",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ import {
|
||||||
rollbackToCheckpoint,
|
rollbackToCheckpoint,
|
||||||
} from "../safety/git-checkpoint.js";
|
} from "../safety/git-checkpoint.js";
|
||||||
import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
||||||
|
import { selectInlineFixCandidates } from "../self-feedback-drain.js";
|
||||||
import { recordSelfFeedback } from "../self-feedback.js";
|
import { recordSelfFeedback } from "../self-feedback.js";
|
||||||
import {
|
import {
|
||||||
checkpointWal,
|
checkpointWal,
|
||||||
|
|
@ -123,6 +124,35 @@ import {
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import { closeoutAndStop, generateMilestoneReport, maybeFireProductAudit, shouldRunPlanningFlowGate } from "./phases-helpers.js";
|
import { closeoutAndStop, generateMilestoneReport, maybeFireProductAudit, shouldRunPlanningFlowGate } from "./phases-helpers.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Surface the open self-feedback queue to the operator at idle-bail time.
|
||||||
|
*
|
||||||
|
* Why here (sf-mp4rxkwb-l4baga partial): the autonomous loop has historically
|
||||||
|
* exited silently on "no-active-milestone" / "milestone-complete" while the
|
||||||
|
* self-feedback queue still had unresolved entries. Operators didn't know
|
||||||
|
* the queue was waiting, so triage never ran. This helper makes the queue
|
||||||
|
* visible at the exact moment the loop is about to bail — the proper fix
|
||||||
|
* (triage as a real unit type) is the larger remaining slice of the parent
|
||||||
|
* entry.
|
||||||
|
*
|
||||||
|
* Best-effort: never throws, never blocks. If candidate selection fails or
|
||||||
|
* the basePath isn't a forge repo, this is a no-op.
|
||||||
|
*/
|
||||||
|
function surfaceSelfFeedbackQueueOnIdle(ctx, basePath, exitReason) {
|
||||||
|
try {
|
||||||
|
const candidates = selectInlineFixCandidates(basePath);
|
||||||
|
if (!Array.isArray(candidates) || candidates.length === 0) return;
|
||||||
|
const n = candidates.length;
|
||||||
|
const noun = n === 1 ? "entry" : "entries";
|
||||||
|
ctx.ui.notify(
|
||||||
|
`Idle (${exitReason}) but ${n} self-feedback ${noun} still open. Run \`sf headless triage --list\` to scan, or \`sf headless triage --run\` to dispatch the canonical triage prompt.`,
|
||||||
|
"warning",
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// Best-effort — never block the loop's bail path on a queue probe.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── runPreDispatch ───────────────────────────────────────────────────────────
|
// ─── runPreDispatch ───────────────────────────────────────────────────────────
|
||||||
/**
|
/**
|
||||||
* Phase 1: Pre-dispatch — resource guard, health gate, state derivation,
|
* Phase 1: Pre-dispatch — resource guard, health gate, state derivation,
|
||||||
|
|
@ -616,6 +646,7 @@ export async function runPreDispatch(ic, loopState) {
|
||||||
`No active milestone — ${incomplete.length} incomplete (${ids}), see diagnostic above`,
|
`No active milestone — ${incomplete.length} incomplete (${ids}), see diagnostic above`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
surfaceSelfFeedbackQueueOnIdle(ctx, s.basePath, "no-active-milestone");
|
||||||
debugLog("autoLoop", { phase: "exit", reason: "no-active-milestone" });
|
debugLog("autoLoop", { phase: "exit", reason: "no-active-milestone" });
|
||||||
deps.emitJournalEvent({
|
deps.emitJournalEvent({
|
||||||
ts: new Date().toISOString(),
|
ts: new Date().toISOString(),
|
||||||
|
|
@ -709,6 +740,7 @@ export async function runPreDispatch(ic, loopState) {
|
||||||
);
|
);
|
||||||
deps.logCmuxEvent(prefs, `Milestone ${mid} complete.`, "success");
|
deps.logCmuxEvent(prefs, `Milestone ${mid} complete.`, "success");
|
||||||
await closeoutAndStop(ctx, pi, s, deps, `Milestone ${mid} complete`);
|
await closeoutAndStop(ctx, pi, s, deps, `Milestone ${mid} complete`);
|
||||||
|
surfaceSelfFeedbackQueueOnIdle(ctx, s.basePath, "milestone-complete");
|
||||||
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
||||||
deps.emitJournalEvent({
|
deps.emitJournalEvent({
|
||||||
ts: new Date().toISOString(),
|
ts: new Date().toISOString(),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue