VibeFlow CLI flag reference

Contents

This document tracks every non-trivial flag in the VibeFlow CLI that the release notes, marketing copy, or new contributors need to know about. It is maintained alongside the source: when a flag is added or its behavior changes, update the entry here in the same commit.

vf init --ai --autopilot

Added in: feat(ai-init): add --autopilot flag for engine auto-fallback

Summary: When the chosen engine is unavailable or returns a permission/unauthorized error, automatically fall back to the next-best ready engine instead of failing hard.

Default: false (preserves pre-existing single-shot behavior; a failure is the user’s problem to debug).

Scope: The flag is on vf init --ai, NOT a global flag. Only the AI-powered enrichment phase (Phase 2 of vf init) is affected. The deterministic baseline (Phase 1) is unchanged.

Behavior:

  1. If forceEngine (from --engine) is set and the engine is not ready, autopilot clears forceEngine and falls through to selectBestEngine(readiness), which picks the next-best engine in priority order: claude > copilot > codex.
  2. If the engine invocation reports the CLI as unavailable (e.g. copilot binary missing), autopilot retries with the next-best engine.
  3. If the engine spawner returns a permission-denied or unauthorized response (e.g. copilot missing --allow-all flags), autopilot retries with the next-best engine.
  4. Timeouts and unknown non-zero status codes are NOT retried — those indicate an engine-side issue, not a fallback opportunity.
  5. Retries are capped at 3 (4 total attempts). The fallback engine must be DIFFERENT from the one that just failed (the loop never retries the same engine twice).
  6. The AiInitResult includes a fallback: { original, used } field when the chosen engine differs from the original request. The CLI surfaces this as ✔ AI analysis complete (used; fell back from original via --autopilot).

Result on total failure: If every engine fails (or the only candidate is unavailable), the result reason is wrapped with — exhausted 3 autopilot fallbacks; original request was <engine> so the caller knows fallback was attempted and gave up.

Non-retryable failures (preserved single-shot): When autopilot is off, the loop is bypassed entirely. A failure on the first attempt returns immediately — no fallback is attempted. The original error message is preserved verbatim.

Tests: 7 new tests in test/ai-init.test.ts (autopilot flag block) and 2 new tests in test/commands-coverage.test.ts (CLI integration). All pass; 100% lcov line coverage maintained.


Coverage policy: 100% lcov, 100% branch, always

The vibeflow-docs repo is contractually at 100% line and branch coverage. Every PR must keep it that way.

How it’s enforced

  1. scripts/coverage-gate.cjs — parses coverage/lcov.info after bun test --coverage --coverage-reporter=lcov runs. Refuses merge if any src/*.ts file is below 100% line or branch.
  2. bun run coverage:check — runs the lcov generator + the gate.
  3. bun run check — runs typecheck + lint + test + coverage:check.
  4. .github/workflows/ci.yml — runs bun run check on a self-hosted runner (PR #30 + #31). If red, the PR cannot merge.

What to do when you add a new file and coverage drops

Option A: Cover the new code with a test (always preferred)

  1. Add the new src/ file.
  2. Add test/foo.test.ts with at least one expect() per public function.
  3. Run bun run coverage:check to confirm.

Option B: Inline unreachable defensive code

If a catch block or if (cond) return; is truly unreachable in practice, use the single-statement form:

// Bad — bun:coverage counts the } as a separate line, drops coverage.
if (cond) {
  return;
}

// Good — no standalone }, the line is the same.
if (cond) return;

Option C: Extract a test seam (last resort)

If the code path is hard to test (real network, real fs), inject the dependency. See src/commands.ts for examples.

bun:coverage quirks

  1. Standalone } on its own line is counted as executable. Inline single-statement blocks to avoid.
  2. bun:coverage emits no BRDA records, so branch coverage shows as 0/0 in the lcov. The plan called this out — the ::notice:: line in coverage-gate.cjs documents this.
  3. setInterval(() => {...}, 25000) callback body never hits in tests because tests complete in <25s. Either exercise the callback in a test, or use an inject.timer seam.

Anti-patterns suite

test/coverage-anti-patterns.test.ts is the contract that locks the 100% invariant. A future agent that introduces a top-level spawn, an empty catch, or a test using raw Bun.spawn will fail this suite BEFORE the coverage gate runs.

When you have a real coverage blocker

Open an issue with: file + line, the code that can’t be hit, and the runtime condition. A maintainer can add an inject seam, add a // biome-ignore, or accept the gap and document the rationale.

Do NOT silently merge <100% coverage. The 100% invariant is load- bearing: future contributors rely on it.


Related: Master Spec · Generated Files Edit this page on GitHub