feat: plumb /sf autonomous full + add docstrings on the auto-command path
`/sf autonomous full` (or `--full`) plumbs through to AutoSession.fullAutonomy, to be consumed at milestone-complete to skip the human-review pause and auto-merge + chain to the next milestone. Git revert is the safety net (see ADR-019/021 conversation on autonomy and reversibility). Plumbing path: - commands/handlers/auto.ts: parses `full` / `--full` modifier, threads fullAutonomy through launchAuto options - commands/catalog.ts: completion entries for `full` and `--full` - auto.ts: startAuto and startAutoDetached accept fullAutonomy in options; startAuto pins it on the session up-front so resume paths preserve it - auto/session.ts: AutoSession.fullAutonomy field with full docstring Behavior change is staged: the milestone-complete consumer that auto-merges and chains is intentionally not in this commit (parallel session is active in auto-post-unit.ts and auto/loop.ts; will land in a follow-up). Also adds JSDoc to the functions on the touched path: - handleAutoCommand (full command-family doc) - launchAuto (headless vs detached routing) - startAutoDetached (fire-and-forget rationale, why it diverges from startAuto) - AutoSession.fullAutonomy (full inline doc) Typecheck clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
356d1d1f99
commit
ed85252fc5
4 changed files with 77 additions and 1 deletions
|
|
@ -307,6 +307,23 @@ function normalizeSessionFilePath(raw: unknown): string | null {
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire-and-forget wrapper around {@link startAuto} for the interactive shell.
|
||||||
|
*
|
||||||
|
* The interactive REPL cannot block on the long-running auto loop, so the
|
||||||
|
* command handler calls this synchronously: the loop runs in the background,
|
||||||
|
* UI events fire through `ctx.ui.notify`, and any startup failure surfaces as
|
||||||
|
* an error notification rather than an unhandled rejection.
|
||||||
|
*
|
||||||
|
* The headless code path uses {@link startAuto} directly because `sf headless`
|
||||||
|
* needs to await loop completion to set its exit code.
|
||||||
|
*
|
||||||
|
* @param ctx Extension command context (for notify, status, widgets)
|
||||||
|
* @param pi Extension API (for engine calls and sessions)
|
||||||
|
* @param base Project root path
|
||||||
|
* @param verboseMode Verbose execution output
|
||||||
|
* @param options Optional run modifiers — see {@link startAuto}
|
||||||
|
*/
|
||||||
export function startAutoDetached(
|
export function startAutoDetached(
|
||||||
ctx: ExtensionCommandContext,
|
ctx: ExtensionCommandContext,
|
||||||
pi: ExtensionAPI,
|
pi: ExtensionAPI,
|
||||||
|
|
@ -316,6 +333,11 @@ export function startAutoDetached(
|
||||||
step?: boolean;
|
step?: boolean;
|
||||||
interrupted?: InterruptedSessionAssessment;
|
interrupted?: InterruptedSessionAssessment;
|
||||||
milestoneLock?: string | null;
|
milestoneLock?: string | null;
|
||||||
|
/**
|
||||||
|
* Full-autonomy mode: auto-merge milestone branches and chain to the
|
||||||
|
* next milestone without pausing for human review. See `/sf autonomous full`.
|
||||||
|
*/
|
||||||
|
fullAutonomy?: boolean;
|
||||||
},
|
},
|
||||||
): void {
|
): void {
|
||||||
void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
|
void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
|
||||||
|
|
@ -1467,6 +1489,11 @@ export async function startAuto(
|
||||||
step?: boolean;
|
step?: boolean;
|
||||||
interrupted?: InterruptedSessionAssessment;
|
interrupted?: InterruptedSessionAssessment;
|
||||||
milestoneLock?: string | null;
|
milestoneLock?: string | null;
|
||||||
|
/**
|
||||||
|
* Full-autonomy mode: auto-merge milestone branches and chain to the
|
||||||
|
* next milestone without pausing for human review. See `/sf autonomous full`.
|
||||||
|
*/
|
||||||
|
fullAutonomy?: boolean;
|
||||||
},
|
},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (s.active) {
|
if (s.active) {
|
||||||
|
|
@ -1485,6 +1512,11 @@ export async function startAuto(
|
||||||
|
|
||||||
const requestedStepMode = options?.step ?? false;
|
const requestedStepMode = options?.step ?? false;
|
||||||
const interruptedAssessment = options?.interrupted ?? null;
|
const interruptedAssessment = options?.interrupted ?? null;
|
||||||
|
// Pin full-autonomy on the session up-front. The branches below that set
|
||||||
|
// stepMode never override fullAutonomy — it carries through resume paths,
|
||||||
|
// fresh starts, and crash recovery so the milestone-complete code path can
|
||||||
|
// consult it without re-reading command-line options.
|
||||||
|
s.fullAutonomy = options?.fullAutonomy === true;
|
||||||
if (options?.milestoneLock !== undefined) {
|
if (options?.milestoneLock !== undefined) {
|
||||||
s.sessionMilestoneLock = options.milestoneLock ?? null;
|
s.sessionMilestoneLock = options.milestoneLock ?? null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,13 @@ export class AutoSession {
|
||||||
active = false;
|
active = false;
|
||||||
paused = false;
|
paused = false;
|
||||||
stepMode = false;
|
stepMode = false;
|
||||||
|
/**
|
||||||
|
* Full-autonomy mode: auto-merge milestone branches and chain to the next
|
||||||
|
* milestone without pausing for human review. Set from the `/sf autonomous full`
|
||||||
|
* command line. Consumed at milestone-complete to skip the review pause and
|
||||||
|
* auto-trigger merge + next-milestone dispatch. Git revert is the safety net.
|
||||||
|
*/
|
||||||
|
fullAutonomy = false;
|
||||||
verbose = false;
|
verbose = false;
|
||||||
activeEngineId: string | null = null;
|
activeEngineId: string | null = null;
|
||||||
activeRunDir: string | null = null;
|
activeRunDir: string | null = null;
|
||||||
|
|
|
||||||
|
|
@ -163,10 +163,14 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly SfCommandDefinition[] = [
|
||||||
|
|
||||||
const NESTED_COMPLETIONS: CompletionMap = {
|
const NESTED_COMPLETIONS: CompletionMap = {
|
||||||
autonomous: [
|
autonomous: [
|
||||||
|
{ cmd: "full", desc: "Auto-merge milestones; chain end-to-end without review" },
|
||||||
|
{ cmd: "--full", desc: "Auto-merge milestones; chain end-to-end without review" },
|
||||||
{ cmd: "--verbose", desc: "Show detailed execution output" },
|
{ cmd: "--verbose", desc: "Show detailed execution output" },
|
||||||
{ cmd: "--debug", desc: "Enable debug logging" },
|
{ cmd: "--debug", desc: "Enable debug logging" },
|
||||||
],
|
],
|
||||||
auto: [
|
auto: [
|
||||||
|
{ cmd: "full", desc: "Auto-merge milestones; chain end-to-end without review" },
|
||||||
|
{ cmd: "--full", desc: "Auto-merge milestones; chain end-to-end without review" },
|
||||||
{ cmd: "--verbose", desc: "Show detailed execution output" },
|
{ cmd: "--verbose", desc: "Show detailed execution output" },
|
||||||
{ cmd: "--debug", desc: "Enable debug logging" },
|
{ cmd: "--debug", desc: "Enable debug logging" },
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,24 @@ export function parseMilestoneTarget(input: string): {
|
||||||
return { milestoneId: match[1], rest };
|
return { milestoneId: match[1], rest };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch entry point for the auto-mode command family.
|
||||||
|
*
|
||||||
|
* Handles `/sf auto`, `/sf autonomous`, `/sf next`, `/sf stop`, `/sf pause`, and
|
||||||
|
* their flag variants. Returns `true` when the command was recognised and
|
||||||
|
* routed (caller stops searching), `false` when the command isn't auto-related.
|
||||||
|
*
|
||||||
|
* Recognised flags on autonomous/auto:
|
||||||
|
* - `full` or `--full` — full-autonomy mode (auto-merge + chain milestones)
|
||||||
|
* - `--verbose` — verbose execution output
|
||||||
|
* - `--debug` — enable debug logging via SF_DEBUG
|
||||||
|
* - `M001` (positional) — milestone target lock (only run that milestone)
|
||||||
|
* - `--yolo=<file>` — yolo seed; bootstraps a fresh milestone from a brief
|
||||||
|
*
|
||||||
|
* The handler validates milestone targets exist, gates remote sessions, then
|
||||||
|
* dispatches via `launchAuto` (which routes between headless and detached
|
||||||
|
* spawn paths).
|
||||||
|
*/
|
||||||
export async function handleAutoCommand(
|
export async function handleAutoCommand(
|
||||||
trimmed: string,
|
trimmed: string,
|
||||||
ctx: ExtensionCommandContext,
|
ctx: ExtensionCommandContext,
|
||||||
|
|
@ -71,11 +89,20 @@ export async function handleAutoCommand(
|
||||||
trimmed === "autonomous" ||
|
trimmed === "autonomous" ||
|
||||||
trimmed.startsWith("autonomous ");
|
trimmed.startsWith("autonomous ");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route an auto-mode launch through either the headless (in-process) or
|
||||||
|
* detached (spawned subprocess) entry point depending on `SF_HEADLESS`.
|
||||||
|
*
|
||||||
|
* Headless mode runs the auto loop in the current process (used by CI,
|
||||||
|
* tests, and `sf headless`); detached mode forks a long-running child so
|
||||||
|
* the interactive shell stays responsive while auto-mode runs.
|
||||||
|
*/
|
||||||
const launchAuto = async (
|
const launchAuto = async (
|
||||||
verboseMode: boolean,
|
verboseMode: boolean,
|
||||||
options?: {
|
options?: {
|
||||||
step?: boolean;
|
step?: boolean;
|
||||||
milestoneLock?: string | null;
|
milestoneLock?: string | null;
|
||||||
|
fullAutonomy?: boolean;
|
||||||
},
|
},
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (process.env.SF_HEADLESS === "1") {
|
if (process.env.SF_HEADLESS === "1") {
|
||||||
|
|
@ -123,6 +150,11 @@ export async function handleAutoCommand(
|
||||||
parseMilestoneTarget(afterYolo);
|
parseMilestoneTarget(afterYolo);
|
||||||
const verboseMode = afterMilestone.includes("--verbose");
|
const verboseMode = afterMilestone.includes("--verbose");
|
||||||
const debugMode = afterMilestone.includes("--debug");
|
const debugMode = afterMilestone.includes("--debug");
|
||||||
|
// `/sf autonomous full` (or `--full`): full-autonomy mode — auto-merges
|
||||||
|
// milestone branches and chains to the next milestone without pausing
|
||||||
|
// for human review. Git revert is the safety net.
|
||||||
|
const fullAutonomy =
|
||||||
|
/\bfull\b/.test(afterMilestone) || afterMilestone.includes("--full");
|
||||||
if (debugMode) enableDebug(projectRoot());
|
if (debugMode) enableDebug(projectRoot());
|
||||||
if (!(await guardRemoteSession(ctx, pi))) return true;
|
if (!(await guardRemoteSession(ctx, pi))) return true;
|
||||||
|
|
||||||
|
|
@ -159,9 +191,10 @@ export async function handleAutoCommand(
|
||||||
} else if (milestoneId) {
|
} else if (milestoneId) {
|
||||||
await launchAuto(verboseMode, {
|
await launchAuto(verboseMode, {
|
||||||
milestoneLock: milestoneId,
|
milestoneLock: milestoneId,
|
||||||
|
fullAutonomy,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await launchAuto(verboseMode);
|
await launchAuto(verboseMode, fullAutonomy ? { fullAutonomy } : undefined);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue