singularity-forge/src/help-text.ts
Mikael Hugo 62b19d7ba4 feat(reflection): wire LLM dispatch (sf headless reflect --run)
Phase 1B of the reflection layer: complete the operator-driven loop by
adding actual LLM dispatch. Phase 1A (commit e161a59e2) shipped the
corpus assembler + prompt template + the prompt-emit operator surface.
This commit wires the dispatch end so `sf headless reflect --run`
produces a real report on disk without manual model piping.

Why shell-out to the gemini CLI and not SF's provider abstraction:
reflection is a single-prompt one-shot inference. Going through SF's
full agent dispatch would require a session, model registry, tool
registration, recovery shell — overkill for "render this prompt,
capture text." The gemini CLI handles auth (~/.gemini/oauth_creds.json),
Code Assist project discovery, and protocol drift on its behalf.
Subprocess cost is paid once per reflection (rare).

Implementation:

- reflection.js: runGeminiReflection(prompt, options) spawns
  `gemini --yolo --model <model> -p "<directive>"` and pipes the giant
  rendered template via stdin (gemini -p reads stdin and appends).
  Returns { ok, content, cleanFinish, exitCode, error, stderr }; never
  throws. Defaults to gemini-3-pro-preview (0% used on AI Ultra,
  strongest agentic model with quota). 8-minute timeout.

  cleanFinish detected by REFLECTION_COMPLETE terminator (emitted by
  the prompt template's output contract) — operator gets a warning when
  the report is truncated.

- headless-reflect.ts: --run flag triggers dispatch + report write
  via writeReflectionReport. --model overrides the default. Errors
  surface as JSON or text per --json. Successful runs emit the report
  path on stdout; failures emit error + truncated stderr.

- help-text.ts: documents --run and --model flags.

- Tests (4 new, 13 total): use a fake `gemini` binary on PATH to
  exercise the spawn path without real OAuth/network — covers
  ok+cleanFinish, non-zero exit, hang/timeout, missing-terminator.

All 1538 SF extension tests pass; typecheck clean.

Phase 2 follow-up (still gated on sf-mp4rxkwb-l4baga
triage-not-a-first-class-unit-type landing): reflection-pass becomes a
real autonomous-loop unit type, milestone-close auto-triggers it, the
report's `Recommended new self-feedback entries` section gets parsed
and the entries auto-filed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 04:33:16 +02:00

353 lines
15 KiB
TypeScript

const SUBCOMMAND_HELP: Record<string, string> = {
config: [
"Usage: sf config",
"",
"Re-run the interactive setup wizard to configure:",
" - LLM provider (Anthropic, OpenAI, Google, OpenRouter, Ollama, LM Studio, etc.)",
" - Web search provider (Brave, Tavily, built-in)",
" - Remote questions (Discord, Slack, Telegram)",
" - Tool API keys (Context7, Jina, Groq)",
"",
"All steps are skippable and can be changed later with /login or /search-provider.",
"",
"For detailed provider setup instructions (OpenRouter, Ollama, LM Studio, vLLM,",
"and other OpenAI-compatible endpoints), see docs/providers.md.",
].join("\n"),
update: [
"Usage: sf update",
"",
"Update SF to the latest version.",
"",
"Equivalent to: npm install -g singularity-forge@latest",
].join("\n"),
sessions: [
"Usage: sf sessions",
" sf sessions --all",
"",
"List saved sessions and interactively pick one to resume. Shows date,",
"message count, and a preview of the first message for each session.",
"",
"Sessions are stored per-directory by default. Use --all to list sessions",
"across all projects.",
"",
" sf sessions List sessions for the current directory",
" sf sessions --all List sessions across all projects",
"",
"Compare with --continue (-c) which always resumes the most recent session.",
].join("\n"),
install: [
"Usage: sf install <source> [-l, --local]",
"",
"Install a package/extension source and run post-install validation (dependency checks, setup).",
"",
"Examples:",
" sf install npm:@foo/bar",
" sf install git:github.com/user/repo",
" sf install https://github.com/user/repo",
" sf install ./local/path",
].join("\n"),
remove: [
"Usage: sf remove <source> [-l, --local]",
"",
"Remove an installed package source and its settings entry.",
].join("\n"),
list: [
"Usage: sf list",
"",
"List installed package sources from user and project settings.",
].join("\n"),
logs: [
"Usage: sf logs tail|follow [--source notif|session|activity|audit] [--severity level]",
"",
"Follow a merged live stream from .sf/notifications.jsonl, the latest",
"headless session JSONL, the latest .sf/activity JSONL, and .sf/audit-log.jsonl.",
"",
"Examples:",
" sf logs tail",
" sf logs follow --source audit",
" sf logs tail --severity error",
].join("\n"),
status: [
"Usage: sf status --live [--watch]",
"",
"Show a printable aggregate project view: current milestone, slice, task,",
"phase, next dispatch, recent events, session cost, and current model.",
"",
"Examples:",
" sf status --live",
" sf status --live --watch",
" sf dash --watch",
].join("\n"),
dash: ["Usage: sf dash [--watch]", "", "Alias for sf status --live."].join(
"\n",
),
stats: [
"Usage: sf stats models [--unit-type <type>] [--since <duration>]",
"",
"Summarize Bayesian model outcome rows from .sf/sf.db.",
"",
"Examples:",
" sf stats models --since 7d",
" sf stats models --unit-type execute-task --since 24h",
].join("\n"),
worktree: [
"Usage: sf worktree <command> [args]",
"",
"Manage isolated git worktrees for parallel work streams.",
"",
"Commands:",
" list List worktrees with status (files changed, commits, dirty)",
" merge [name] Squash-merge a worktree into main and clean up",
" clean Remove all worktrees that have been merged or are empty",
" remove <name> Remove a worktree (--force to remove with unmerged changes)",
"",
"The -w flag creates/resumes worktrees for interactive sessions:",
" sf -w Auto-name a new worktree, or resume the only active one",
" sf -w my-feature Create or resume a named worktree",
"",
"Lifecycle:",
" 1. sf -w Create worktree, start session inside it",
" 2. (work normally) All changes happen on the worktree branch",
" 3. Ctrl+C Exit — dirty work is auto-committed",
" 4. sf -w Resume where you left off",
" 5. sf worktree merge Squash-merge into main when done",
"",
"Examples:",
" sf -w Start in a new auto-named worktree",
' sf -w auth-refactor Create/resume "auth-refactor" worktree',
" sf worktree list See all worktrees and their status",
" sf worktree merge auth-refactor Merge and clean up",
" sf worktree clean Remove all merged/empty worktrees",
" sf worktree remove old-branch Remove a specific worktree",
" sf worktree remove old-branch --force Remove even with unmerged changes",
].join("\n"),
graph: [
"Usage: sf graph <subcommand> [options]",
"",
"Manage the SF project knowledge graph. Reads .sf/ artifacts and builds",
"a queryable graph of milestones, slices, tasks, rules, patterns, and lessons.",
"",
"Subcommands:",
" build Parse .sf/ artifacts (STATE.md, milestone ROADMAPs, slice PLANs,",
" KNOWLEDGE.md) and write .sf/graphs/graph.json atomically.",
" query Search graph nodes by term (BFS from seed matches, budget-trimmed).",
" Returns matching nodes and reachable edges within the token budget.",
" status Show whether graph.json exists, its age, node/edge counts, and",
" whether it is stale (built more than 24 hours ago).",
" diff Compare current graph.json with .last-build-snapshot.json.",
" Returns added, removed, and changed nodes and edges.",
"",
"Examples:",
" sf graph build Build the graph from .sf/ artifacts",
" sf graph status Check graph age and node/edge counts",
' sf graph query auth Find nodes related to "auth"',
" sf graph diff Show changes since last snapshot",
].join("\n"),
plan: [
"Usage: sf plan <command>",
"",
"Manage SF milestone planning artifacts and human docs exports.",
"",
"Commands:",
" promote <source> Copy a file from .sf/ to docs/plans/, docs/adr/, or docs/specs/",
" list List milestone and slice files in .sf/",
" diff Compare .sf/ state with promoted docs/ artifacts",
" specs <cmd> Generate, diff, or check docs/specs exports from .sf state",
"",
"See docs/plans/README.md, docs/adr/README.md, and docs/specs/README.md for conventions.",
].join("\n"),
schedule: [
"Usage: sf schedule <command> [args]",
"",
"Manage time-bound reminders and deferred work items.",
"Entries are stored as versioned append-only JSONL in .sf/schedule.jsonl (project)",
"or ~/.sf/schedule.jsonl (global). No daemon required — items surface on pull.",
"",
"Commands:",
" add --in <duration> [--kind <kind>] [--scope <scope>] <title>",
" --at <ISO-date> Schedule by absolute date instead",
" list [--due] [--all] [--json] [--scope <scope>]",
" done <id> Mark a scheduled item as done",
" cancel <id> Cancel a scheduled item",
" snooze <id> --by <duration> Postpone by relative time",
" run <id> Execute a scheduled item (show reminder or run command)",
"",
"Duration format: <number><unit> where unit is w(weeks), d(days), h(hours), m(minutes).",
" e.g. 30m, 4h, 2d, 1w",
"",
"Scope: project (default, stored in .sf/schedule.jsonl) or global (~/.sf/schedule.jsonl).",
"",
"Kinds: reminder, milestone_check, review_due, review, audit, recurring, command",
"",
"Examples:",
' sf schedule add --in 2w "Review feature adoption metrics"',
' sf schedule add --at 2026-06-01T09:00:00Z --kind audit "Audit ADR-007"',
" sf schedule list --due",
" sf schedule snooze 01ARZ3ND --by 1d",
" sf schedule done 01ARZ3ND",
].join("\n"),
headless: [
"Usage: sf headless [flags] [command] [args...]",
"",
"Machine surface for direct SF commands. Runs the same SF flow without rendering the TUI.",
"",
"Flags:",
" --timeout N Overall timeout in ms (default: 300000)",
" --json JSONL event stream to stdout (alias for --output-format stream-json)",
" --output-format <fmt> Output format: text (default), json (structured result), stream-json (JSONL events)",
" --bare Minimal context: skip CLAUDE.md, AGENTS.md, user settings, user skills",
" --resume <id> Resume a prior headless session by ID",
" --model ID Override model",
" --supervised Forward interactive UI requests to orchestrator via stdout/stdin",
" --response-timeout N Timeout (ms) for orchestrator response (default: 30000)",
" --answers <path> Pre-supply answers and secrets (JSON file)",
" --events <types> Filter JSONL output to specific event types (comma-separated)",
"",
"Commands:",
" autonomous Run all queued product units continuously",
" next Run one unit",
" status Show progress dashboard",
" new-milestone Create a milestone from a specification document",
" query Machine snapshot: JSON state + next dispatch + costs (no LLM)",
" 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)",
"",
"new-milestone flags:",
" --context <path> Path to spec/PRD file (use '-' for stdin)",
" --context-text <txt> Inline specification text",
" --autonomous Start autonomous mode after milestone creation",
" --verbose Show tool calls in progress output",
"",
"Output formats:",
" text Human-readable progress on stderr (default)",
" json Collect events silently, emit one structured result on stdout at exit",
" stream-json Stream JSONL events to stdout in real time (same as --json)",
"",
"Examples:",
" sf headless Show this help",
" sf headless autonomous Run autonomous mode through the machine surface",
" sf headless next Run one unit",
" sf headless --output-format json autonomous Structured JSON result on stdout",
" sf headless --json status Machine-readable JSONL stream",
" sf headless --timeout 60000 autonomous Run autonomous with 1-minute timeout",
" sf headless --bare autonomous Minimal context (CI/ecosystem use)",
" sf headless --resume abc123 autonomous Resume a prior session",
" sf headless new-milestone --context spec.md Create milestone from file",
" cat spec.md | sf headless new-milestone --context - From stdin",
" sf headless new-milestone --context spec.md --autonomous Create + run autonomously",
" sf headless --supervised autonomous Supervised orchestrator mode",
" sf headless --answers answers.json autonomous With pre-supplied answers",
" sf headless --events agent_end,extension_ui_request autonomous Filtered event stream",
" sf headless query Instant machine JSON state snapshot",
" sf headless status uok UOK gate health table (last 24h)",
" sf headless status uok --json UOK gate health as JSON",
"",
"Exit codes: 0 = success, 1 = error/timeout, 10 = blocked, 11 = cancelled",
].join("\n"),
};
// Alias: `sf wt --help` → same as `sf worktree --help`
SUBCOMMAND_HELP["wt"] = SUBCOMMAND_HELP["worktree"];
export function printHelp(version: string): void {
process.stdout.write(`SF v${version} — Singularity Forge\n\n`);
process.stdout.write("Usage: sf [options] [message...]\n\n");
process.stdout.write("Options:\n");
process.stdout.write(
" --mode <text|json|rpc> Session I/O mode: text/json print format or RPC protocol\n",
);
process.stdout.write(" --print, -p Single-shot print mode\n");
process.stdout.write(
" --continue, -c Resume the most recent session\n",
);
process.stdout.write(
" --worktree, -w [name] Start in an isolated worktree (auto-named if omitted)\n",
);
process.stdout.write(
" --model <id> Override model (e.g. provider/model-id)\n",
);
process.stdout.write(
" --no-session Disable session persistence\n",
);
process.stdout.write(
" --extension <path> Load additional extension\n",
);
process.stdout.write(" --tools <a,b,c> Restrict available tools\n");
process.stdout.write(
" --list-models [search] List available models and exit\n",
);
process.stdout.write(
" --discover Force live verification for filtered --list-models\n",
);
process.stdout.write(" --version, -v Print version and exit\n");
process.stdout.write(" --help, -h Print this help and exit\n");
process.stdout.write("\nSubcommands:\n");
process.stdout.write(" config Re-run the setup wizard\n");
process.stdout.write(
" install <source> Install a package/extension source\n",
);
process.stdout.write(
" remove <source> Remove an installed package source\n",
);
process.stdout.write(
" list List installed package sources\n",
);
process.stdout.write(
" update Update SF to the latest version\n",
);
process.stdout.write(
" sessions List and resume a past session\n",
);
process.stdout.write(" logs tail|follow Follow merged SF logs\n");
process.stdout.write(
" status --live Show aggregate project status\n",
);
process.stdout.write(" dash Alias for status --live\n");
process.stdout.write(" stats models Summarize model outcomes\n");
process.stdout.write(
" worktree <cmd> Manage worktrees (list, merge, clean, remove)\n",
);
process.stdout.write(
" autonomous [args] Run autonomous mode through the machine surface (pipeable)\n",
);
process.stdout.write(
" headless [cmd] [args] Machine surface for direct SF commands\n",
);
process.stdout.write(
" graph <subcommand> Manage knowledge graph (build, query, status, diff)\n",
);
process.stdout.write(
" plan <cmd> Manage SF planning artifacts and docs exports\n",
);
process.stdout.write(
" schedule <cmd> Manage time-bound reminders (add, list, done, cancel, snooze, run)\n",
);
process.stdout.write(
"\nRun sf <subcommand> --help for subcommand-specific help.\n",
);
}
export function printSubcommandHelp(
subcommand: string,
version: string,
): boolean {
const help = SUBCOMMAND_HELP[subcommand];
if (!help) return false;
process.stdout.write(`SF v${version} — Singularity Forge\n\n`);
process.stdout.write(help + "\n");
return true;
}