The forge CLI
Maistik Forge ships as a single Go binary named forge. This page is the reference for that terminal binary — every command, its real flags, and the exit codes the enforced gates return. Run forge --help any time to print the same synopsis the binary reports.
forge is one of two front doors to the same stateful engine. In a shell you type forge <command>; inside a configured agent you type /forge.<command>. This page documents the binary. The /forge.* agent slash commands are cataloged separately.
Looking for the slash commands?
The in-agent /forge.<command> equivalents — the workflow spine, foundation, and cross-cutting commands — live in Commands. This page does not duplicate that catalog; it documents the terminal binary and its harness gates.
Invocation
Run forge with no arguments to launch the interactive TUI. Pass a command to run it directly.
$ forge --help
forge — Maistik Forge: Ecosystem, Frameworks, Workflows
USAGE
forge Launch interactive TUI
forge <command> [flags]
# exit 0Two global flags apply everywhere:
| Flag | Meaning |
|---|---|
--help, -h | Print the global synopsis. Every review subcommand also honors it. |
--cwd <repo> | Target a repo other than the current directory. Most stateful commands accept it. |
Print the binary version at any time:
forge versionWhere state lives
Every stateful command reads and writes .forge/ in the target repo — specs, changes, ledgers, receipts, and timelines. Use --cwd <repo> to point a command at a repo other than your shell's working directory.
Setup
Bootstrap Forge and configure the agents on this machine.
install
forge install detects installed agents and writes the Forge command set into them. It backs up existing config first and is idempotent — run it again and it converges to the same state.
USAGE
forge install [flags]
FLAGS
--agent, --agents <list> Agents to install
--component, --components <list> Components to install
--skill, --skills <list> Skills to install
--persona <name> Persona to apply
--preset <name> Preset to apply
--sdd-mode single|multi SDD orchestrator mode
--scope global|workspace Install scope
--channel stable|beta|nightly Release channel; nightly is an alias for beta
--dry-run Preview plan without executing
--help, -h Show this helpforge install --scope workspace # this project only
forge install --agents claude-code,cursor
forge install --dry-run # preview the plan, write nothinginit
forge init initializes this repo's .forge/config.yaml. Run it with no flags for the wizard, or pass any headless flag to skip it.
forge init [--yes] [--preset <name>] [--agents <list>] [--scope workspace|global] [--reconfigure]| Flag | Default | Effect |
|---|---|---|
--yes | off | Skip the wizard and initialize with detected defaults |
--preset <name> | (empty) | Ecosystem preset to apply |
--agents <list> | (empty) | Comma-separated agents to configure; repeatable |
--scope workspace|global | (empty → workspace) | Install scope |
--reconfigure | off | Headless: rebuild an existing .forge/config.yaml from scratch instead of update-only merging |
Passing any of --yes, --preset, --agents, --scope, or --reconfigure requests the headless path — the wizard never runs. Each flag also falls back to an env var when unset: FORGE_INIT_YES, FORGE_INIT_PRESET, FORGE_INIT_AGENTS, FORGE_INIT_SCOPE, and FORGE_INIT_RECONFIGURE. The flag always wins over the env var.
forge init # interactive wizard
forge init --yes # accept defaults, no prompts
forge init --agents claude-code --scope workspace
forge init --yes --reconfigure # rebuild config.yaml from scratchconfig explain
forge config explain resolves the effective .forge/config.yaml — every field with its resolved value and the layer it came from (built-in default, file, or environment). A missing config file is not an error: the resolver treats it as zero-config and falls back to the built-in defaults.
forge config explain [--cwd <path>]config edit
forge config — with no subcommand, or spelled forge config edit — opens an interactive section editor for .forge/config.yaml. Pick a section, walk its fields, then choose Save and exit. ctrl+c quits without writing anything.
forge config [edit] [--cwd <path>]It edits the project layer only: just the fields you actually change are written, so every untouched key keeps its default origin instead of being baked into the file. The section menu annotates each field with its effective value and origin layer, so a field already overridden by config.local.yaml or a FORGE_* variable says so before you edit it.
The editor needs a terminal. In scripts and CI, use config set instead.
Fields whose values are free-form rather than a fixed set (docsite.out, persona.language, review.provider, workflow.phases, docsite.sections, docsite.exclude, agents.enabled, models, repo.hosts, repo.related) are not in the menu — set them with config set, or edit the YAML directly.
config set
forge config set is the headless single-key equivalent, for scripts and CI. It writes exactly the key named and nothing else.
forge config set <key> <value> [--cwd <path>]forge config set persona.mode forge
forge config set workflow.breaker_threshold 5
forge config set budget.max_usd 12.50
forge config set mcp.servers context7,playwright
forge config set learn.threshold "" # unset: fall back to the defaultAn empty value unsets the key, removing it from the file so the next lower layer takes over again. Lists are comma-separated. Machine-specific keys — agents.enabled — are written to .forge/config.local.yaml instead, merged into whatever is already there. Both commands snapshot .forge/ before writing, so any edit is reversible via forge restore.
An unknown key is an error; run forge config explain to see the resolved key list. Note that both writers round-trip the file through the schema, so comments and out-of-schema keys in an edited file are not preserved.
sync
forge sync re-syncs your global (home-scoped) agent configs and skills to the current binary version — the agents it targets come from DiscoverAgents, which inspects your machine, not the repo you're standing in. Run it after upgrading the binary to bring your global managed files back into agreement.
forge syncsync never touches a workspace-scoped project
sync has no --scope flag — it cannot write into a repo's local .claude/, .codeium/windsurf/, or similar directories. If your project uses agents.scope: workspace (see .forge/config.yaml), forge sync is not the command that refreshes it, no matter how recently you upgraded the binary. See Updating an existing project below for the command that actually does.
uninstall
forge uninstall removes Forge-managed files from this machine. See Installation for the snapshot and cleanup workflow around it.
doctor
forge doctor inspects your environment and reports problems. Run bare, it is read-only. It is not read-only with --fix — that flag installs the missing dependencies it finds.
forge doctor [--fix] [--selftest]| Flag | Default | Effect |
|---|---|---|
--fix | off | Mutating. Install the missing dependencies the check reports, then print the report |
--selftest | off | Run the binary's self-test instead of the environment check and exit |
--selftest is checked first and short-circuits: with it present, no environment check and no fixing happens at all. doctor always runs against the process working directory — it takes no --cwd.
forge doctor # read-only report
forge doctor --fix # report AND install what is missing
forge doctor --selftest # binary self-test onlyVerify and maintain
forge update reports available updates; forge upgrade applies binary-only updates. See Installation for the full maintenance loop.
import
forge import migrates an existing Spec-Kit (.specify/) or OpenSpec (openspec/) project into the unified .forge/ layout, non-destructively.
forge import specify|openspec [--from <path>] [--cwd <dir>]
[--dry-run] [--force] [--include-generated]| Flag | Default | Meaning |
|---|---|---|
--from <path> | auto-detected | Source project root |
--dry-run | off | Print the plan; write nothing |
--force | off | Overwrite existing .forge/ content |
--include-generated | off | Also import regenerable site/ and scripts/ |
Content that has no place in the .forge/ layout is preserved losslessly under .forge/_imported/ rather than dropped. Regenerable site/ and scripts/ directories are skipped unless --include-generated is passed.
migrate
forge migrate brings an existing .forge/changes/{name}/ folder created by an older forge-kit version up to date with the current artifact conventions (for example, the design.md → plan.md rename), so an old change folder never becomes unusable just because the files it generated don't match what the current version expects.
forge migrate [--cwd <dir>] [--dry-run]| Flag | Default | Meaning |
|---|---|---|
--cwd <dir> | current directory | Repo root to migrate |
--dry-run | off | Print what each step would change; write nothing |
Non-destructive (never overwrites an existing canonical file), snapshotted before any write (reversible via forge restore), and idempotent — safe to run repeatedly, and safe to run against a change folder that's already current, which reports "no changes needed".
Updating an existing project
There is no single command that brings an already-initialized project fully up to date with a newer forge-kit version — it's three separate steps, each owning a different piece of .forge/ state. Run them from inside the project's repo root, in this order:
forge migrate --dry-run # 1. preview which change folders need updating
forge migrate # 2. apply — snapshotted, reversible via `forge restore`
forge skill-registry refresh # 3. rebuild .forge/skill-registry.md
forge install --scope workspace --agents <your-agents> # 4. refresh the installed
# skill/command filesTwo gotchas worth knowing before you run this:
forge installtakes no--cwd— unlikemigrateandskill-registry, you must actually becd'd into the target repo.--scope workspacewrites into that repo's local agent directories (.claude/,.codeium/windsurf/, etc.); the default--scope globalwrites into your home directory instead — pass--scope workspaceexplicitly, don't rely on a project default.forge syncis not part of this sequence. It only refreshes your global, home-scoped configs (see the warning above) — it has no way to target a specific project's workspace-scoped install.
If you skip straight to forge install --scope workspace without first running forge migrate, an old change folder using a stale filename (e.g. a pre-rename design.md) stays exactly as stale as it was — install only refreshes the agent-side skill/command files, it never touches .forge/changes/ content.
Workflow & status
Drive and inspect the SDD lifecycle from the shell. These commands are safe to run at any time — the status readers are read-only.
| Command | Signature | What it does |
|---|---|---|
sdd-status | sdd-status [change] | Print native SDD phase status for orchestrators. Read-only. |
sdd-continue | sdd-continue [change] | Print native SDD dispatcher routing output. |
status | status [change] [--cwd <repo>] [--json] | Colorized, single-shot snapshot of a change's progress for a human. Read-only. |
analyze | analyze [<spec-path>] [--portfolio] [--save] [--json] [--cwd <repo>] | Reproducible 100-point spec quality score. Read-only unless --save. |
health | health [--save] [--cwd <repo>] | Portfolio-wide risk dashboard: every active change's phase progress, verify state, and a deterministic HIGH/MEDIUM/LOW/UNKNOWN risk classification, plus a prioritized action list. Read-only unless --save. Distinct from analyze — see Spec quality. |
archive | archive [change] [--cwd <repo>] | Promote a completed change — merge its delta specs into .forge/specs/ and move it to .forge/changes/archive/, once every task in tasks.md is Done. |
analyze scores a single spec when you pass a <spec-path>, or the whole .forge/specs portfolio when you pass none. Add --json for stable machine-readable output and --save to persist the score.
$ forge analyze
No specs found under .forge/specs or .forge/changes; nothing to analyze.
# exit 0sdd-status prints the phase rollup orchestrators route on:
$ forge sdd-status
## SDD Status: continuous-learning-loop
next: spec
### Summary
- apply: blocked
- verify: blocked
- tasks: 51/51 complete
# exit 0Migration
forge import migrates an existing Spec-Kit (.specify) or OpenSpec (openspec) project into the unified .forge/ layout, non-destructively.
forge import specify|openspec [--from <path>] [--dry-run] [--force] [--include-generated]Regenerable site/ and scripts/ are skipped unless you pass --include-generated. See Migration for the full flow.
forge migrate separately brings an existing .forge/changes/{name}/ folder created by an older forge-kit version up to date with the current artifact conventions:
forge migrate [--cwd <dir>] [--dry-run]It is non-destructive, snapshotted (reversible via forge restore), and idempotent — safe to run repeatedly, including against a change folder that's already current.
board
forge board derives the phase-status board from the active changes — one row per change, one column per SDD phase — and prints it as markdown. Read-only unless you pass --write.
forge board [--cwd <repo>] [--write] [--output <path>]| Flag | Effect |
|---|---|
--cwd <repo> | Repo root (defaults to the current directory) |
--write | Also save the board to .forge/board.md |
--output <path> | Write to <path> instead of the default .forge/board.md (implies --write's intent; only takes effect with --write) |
roadmap
Deprecated — use forge board instead. forge roadmap still works and produces the same output, but prints a deprecation notice to stderr and, for backward compatibility, defaults --write to .forge/roadmap.md instead of .forge/board.md.
forge roadmap [--cwd <repo>] [--write] [--output <path>]| Flag | Effect |
|---|---|
--cwd <repo> | Repo root (defaults to the current directory) |
--write | Also save the board to .forge/roadmap.md (legacy path) |
--output <path> | Write to <path> instead of the default .forge/roadmap.md |
wireframe
forge wireframe reads a spec, decides whether it describes a UI, and — when it does — embeds a managed ## Wireframe section (ASCII + Mermaid) into that spec. The injection is replace-or-append-once, so re-running is idempotent.
forge wireframe <spec-path> [--ui | --no-ui] [--cwd <repo>]The UI decision is a keyword heuristic; override it explicitly with --ui or --no-ui.
drift
forge drift classifies every spec-code cross-reference into Ghost (the spec points at code that is gone), Phantom (code claims a spec that does not exist), and Mutated (both sides exist but disagree). It is read-only — the classifier writes nothing and neither does the command.
forge drift [--cwd <dir>]scan is the default subcommand — forge drift and forge drift scan are the same command. waive accepts a finding by fingerprint; reset clears the waiver ledger.
| Flag | Default | Meaning |
|---|---|---|
--cwd <dir> | current directory | Repository root to classify |
--change <c> | active change | Scope the waiver ledger to one change |
--enforce | off | Turn the report into a gate — see exit codes below |
--fail-on <csv> | mutated,phantom | Which categories --enforce treats as blocking |
--fingerprint <fp> | (none) | The finding waive accepts |
By default drift reports and exits 0, even with discrepancies. It only gates when you opt in with --enforce, which returns non-zero if any unwaived Certain finding falls into a --fail-on category. Ghost is excluded from the default --fail-on set, so a spec pointing at deleted code does not block until you ask it to.
$ forge drift
No drift detected: every spec-code cross-reference resolved cleanly.
# exit 0$ forge drift
Drift: 2 discrepancy(ies)
Ghost (1):
- .forge/specs/checkout.md :: internal/checkout/total.go -- referenced file no longer exists
Phantom (1):
- .forge/specs/(none) :: internal/billing/retry.go -- code references a spec that is not present
Mutated (0):
(none)
# exit 0archive
forge archive promotes a completed change: it merges the change's delta specs into canonical .forge/specs/ and moves the change into the dated, immutable .forge/changes/archive/YYYY-MM-DD-<name>/ trail.
forge archive [change] [--cwd <dir>]Archive is gated on honest task status: it refuses while any task in tasks.md is not Done. That refusal is the point — a successful archive is a real signal that the change is finished, not a formality you can run early.
analyze
forge analyze prints the reproducible 100-point spec quality score. With a <spec-path> it scores one spec; with none (or with --portfolio) it scores the whole .forge/specs portfolio.
forge analyze [<spec-path>] [--portfolio] [--save] [--json] [--cwd <dir>]| Flag | Default | Meaning |
|---|---|---|
--portfolio | off | Force portfolio rollup even when a path is given |
--save | off | Persist the score; the only writing mode |
--json | off | Stable machine-readable output |
The score is a pure function of the spec text — same input, same score, every run. A constitution conflict caps the total regardless of the other dimensions. Read-only unless --save. See Spec quality.
health
forge health is a distinct, portfolio-wide command with its own engine (internal/health) — not an alias of analyze. Where analyze scores ONE change's spec/plan/tasks for cross-artifact consistency, health classifies EVERY active change's current risk (HIGH/MEDIUM/LOW/UNKNOWN) from structured verify, task-blocking, circuit-breaker, and review-gate state, and prints a prioritized "what needs attention" list.
forge health [--save] [--cwd <dir>]| Flag | Default | Meaning |
|---|---|---|
--save | off | Write the rendered report to .forge/memory/health-report.md |
Read-only unless --save. It reports risk and state, not dates or ETAs — forge stores no per-phase timestamps. /forge.health is its agent-side slash command.
estimate
forge estimate is a distinct command with its own engine (internal/execgraph) — not an alias of tasks. It idempotently refreshes a change's tasks.md ## Execution Graph JSON block (waves, blockers, critical path, per-task hour estimates, summary) by re-parsing the CURRENT checklist and recomputing the graph from scratch, mechanically applying the same derivation rules sdd-tasks uses. It never rewrites task descriptions, tiers, or [P] markers — only the derived JSON block. Running it twice in a row with no checklist changes produces byte-identical output.
forge estimate [--change <c>] [--cwd <dir>]| Flag | Default | Meaning |
|---|---|---|
--change <c> | active change | Which change's tasks.md to refresh |
--cwd <dir> | current directory | Repository root |
Use this after hand-editing a checklist (bumping a tier, adding/removing a [P] marker, adding a task) so forge.roadmap and forge.swarm schedule work from an up-to-date graph instead of a stale one. /forge.estimate is its agent-side slash command.
reqid
forge reqid check is an advisory scan of requirement IDs across every spec: it flags malformed IDs, duplicates, and IDs whose area prefix does not match the spec domain they live in.
forge reqid check [--cwd <dir>]It always exits 0. It reports and never rewrites an ID, because renumbering requirements silently would break every traceability reference pointing at them.
sdd-status
forge sdd-status prints the native SDD phase status for a change, in the shape orchestrators consume.
forge sdd-status [change]This is the deterministic Go implementation of what an agent would otherwise have to infer by reading .forge/changes/<c>/ itself.
sdd-continue
forge sdd-continue prints the native SDD dispatcher routing output — which phase comes next for a change, and what should run it.
forge sdd-continue [change]Like sdd-status, it is a read-only projection of change state: it decides nothing on its own and writes nothing.
status
forge status prints a colorized, single-shot snapshot of one change's progress for a human glancing at a terminal: a phase checklist (done/active/ pending), task completion, budget spend, review/remediation gates, and a verify circuit-breaker warning when failures are accumulating. It resolves state once and exits — no live refresh, no --watch.
forge status [change] [--cwd <repo>] [--json]$ forge status api-rate-limiting
Change: api-rate-limiting
========================
✓ propose
✓ spec
✓ design
✓ tasks
⠇ apply
○ verify
○ archive
Tasks
14/18 complete
Budget
$2.41 / $10.00
# exit 0Colors auto-degrade for a non-tty or NO_COLOR output. --json emits the same underlying sdd-status fields plus the budget ledger/limits and circuit-breaker state, which live in their own per-change files and are not part of sdd-status's own schema.
Harness gates
These are the terminal-facing controls of the harness. The enforced gates return a non-zero exit to block a runaway loop; a host hook or CI step reads that exit and stops. The four enforced controls are verify (circuit-breaker), budget (spend ceiling), secscan, and the review delivery gates.
verify
forge verify runs the configured tests and lint plus spec scenario coverage. With --change, it arms a circuit-breaker: a single FAIL still exits 0 but increments the breaker; once N consecutive failures accumulate (--max-failures, else workflow.breaker_threshold in config, else 3), it returns non-zero so the implement loop stops and escalates instead of retrying forever.
verify [--change <c>] [--max-failures N] [--reset-breaker] [--isolated] [--cwd <repo>]| Flag | Default | Effect |
|---|---|---|
--change <c> | (none) | Opt into circuit-breaker tracking for that change |
--max-failures N | (from workflow.breaker_threshold in config, else 3) | Override the consecutive-failure threshold for this invocation |
--reset-breaker | off | Clear the breaker for --change instead of running verification |
--isolated | off | Run the configured commands against an ephemeral copy of the working tree instead of the working tree itself |
--cwd <repo> | current dir | Repo root |
$ forge verify --change c1
Verdict: FAIL
Breaker: 1/3 consecutive failures for change "c1" — fix the smallest cause and re-run.
# exit 0
$ forge verify --change c1 # third consecutive failure
Verdict: FAIL
Breaker: CIRCUIT OPEN — 3 consecutive verify failures for change "c1".
STOP looping and escalate to a human.
# exit 1Clear an open breaker after a human intervenes:
forge verify --reset-breaker --change c1See the circuit-breaker for the full state model.
budget
forge budget enforces a per-change spend ceiling. record accumulates reported spend and returns non-zero the moment a ceiling is strictly crossed; check gives the same verdict read-only; reset clears the ledger. Each ceiling comes from its --max-* flag, else from the matching budget.* key in .forge/config.yaml.
budget record|check|reset --change <c> [--tokens N] [--usd X] [--tool-calls K]
[--max-tokens N] [--max-usd X] [--max-tool-calls K] [--cwd <repo>]The delta flags report spend; the --max-* flags set the ceiling compared against the accumulated ledger. Both parse into the same struct, so either group is accepted on any subcommand — only record applies the deltas.
| Flag | Default | Effect |
|---|---|---|
--tokens N | 0 | Tokens to add to the ledger |
--usd X | 0 | Dollars to add to the ledger (accepts a decimal) |
--tool-calls K | 0 | Tool calls to add to the ledger |
--max-tokens N | (from budget.max_tokens in config, else no ceiling) | Token ceiling |
--max-usd X | (from budget.max_usd in config, else no ceiling) | Dollar ceiling (accepts a decimal) |
--max-tool-calls K | (from budget.max_tool_calls in config, else no ceiling) | Tool-call ceiling |
$ forge budget record --change checkout --tokens 600 --max-tokens 1000
Budget[checkout]: tokens=600 usd=0.00 tool_calls=0 (records=1)
Budget: within limits
# exit 0
$ forge budget record --change checkout --tokens 600 --max-tokens 1000
Budget[checkout]: tokens=1200 usd=0.00 tool_calls=0 (records=2)
BUDGET EXCEEDED — tokens 1200/1000. STOP and escalate to a human.
# exit 1No ceiling means no blocking
With no --max-* flag and no matching budget.* key in config, the ledger accumulates spend and never blocks. Reaching a limit exactly is within budget — only strictly going over returns non-zero.
See Spend Budget for the ledger format and the honest limits of token accounting.
secscan
forge secscan is an enforced static gate. scan inspects files for leaked secrets, PII, and prompt-injection strings and returns non-zero on any unwaived finding; waive accepts a fingerprint; reset deletes the change's waivers.
reset clears waivers, not findings
It removes .forge/changes/<change>/secscan.yaml, so every previously waived finding blocks again on the next scan. It makes the gate stricter, not looser.
secscan scan|waive|reset --change <c> [--path P ...] [--min-severity high|medium] [--fingerprint FP] [--cwd <repo>]--change is required by all three subcommands. --path defaults to . and repeats; --min-severity defaults to high; --fingerprint is required for waive.
$ forge secscan scan --change checkout --path leak.txt
secscan[checkout] high secret leak.txt:1 fp=0c9ab6bbabac aws-access-key AKIA…
secscan[checkout] high secret leak.txt:2 fp=657437b6dcc8 github-pat ghp_…
SECSCAN BLOCKED — 2 unwaived finding(s). STOP and escalate to a human; a human waives with: forge secscan waive --change checkout --fingerprint <fp>, or resets with: forge secscan reset --change checkout
# stderr: Error: secscan blocked for change "checkout": 2 unwaived finding(s)
# exit 1Waive a reviewed finding by its fingerprint, then re-run to pass:
forge secscan waive --change checkout --fingerprint 0c9ab6bbabacSee Secret & PII Scan for the waiver workflow and the scanner's limits.
clarify-gate
forge clarify-gate is the Sensor half of forced interactive clarification — not one of the four enforced gates above. It reads two persisted marker scopes (zero model judgment) and warns on any unresolved clarifying question, but by design never hard-blocks: it deliberately never returns Claude Code's PreToolUse-only exit 2, and lets the session continue either way.
clarify-gate [--cwd <repo>] [--session-id <id>] [--change <name>] [--format text|json]| Flag | Default | Effect |
|---|---|---|
--cwd <repo> | current dir | Repo root |
--session-id <id> | (none) | Per-session marker scope; an empty, missing, or path-like id silently degrades to change-scope-only |
--change <name> | (active change) | Per-change marker scope; explicit flag wins, else the single active change under .forge/changes/ |
--format text|json | text | Output shape |
$ forge clarify-gate --cwd . --change checkout
clarify-gate: clean (no unresolved clarifying questions)
# exit 0
$ forge clarify-gate --cwd . --change checkout
CLARIFY GATE — unresolved clarifying question(s) found. This is a warning, not a hard block; the session continues.
[change:checkout] 1 unresolved:
- q1: Which auth model?
A human answers via the interactive tool, or waives with: forge clarify waive --id <qid> [--change <c>|--session <id>]
# exit 1See Interactive Clarification for the marker lifecycle, the Claude Code Stop-hook and OpenCode session.idle wiring, and the honest gap across the other ten supported harnesses.
clarify
forge clarify open|answer|waive|list is the write group behind the marker clarify-gate reads: open records a new unresolved question, answer and waive are the two ways to clear one, and list prints what a scope still has unresolved.
clarify open|answer|waive|list --id <qid>
[--question <text> --asked-in <skill-or-command>]
[--tool AskUserQuestion|question --answer-digest <12hex>]
[--reason <text>]
[--change <c> | --session <id>] [--cwd <repo>]| Flag | Applies to | Effect |
|---|---|---|
--id <qid> | all | The caller-supplied, stable question id. Required |
--question <text> | open | The question text. Required |
--asked-in <name> | open | The skill/command that asked. Required |
--tool AskUserQuestion|question | answer | The recognised interactive tool the answer came through. Required |
--answer-digest <12hex> | answer | 12 lowercase hex chars — a sha256 fingerprint of the answer text, same discipline as secscan. Required |
--reason <text> | waive | Optional note; echoed to stdout only, not persisted |
--change <c> | --session <id> | all | Mutually exclusive scope selector; neither given falls back to the single active change |
--cwd <repo> | all | Repo root |
forge clarify open --id q1 --question "Which auth model?" --asked-in sdd-propose --change checkout
forge clarify answer --id q1 --tool AskUserQuestion --answer-digest a1b2c3d4e5f6 --change checkout
forge clarify waive --id q1 --reason "confirmed in standup" --change checkout
forge clarify list --change checkoutanswer is agent-runnable and best-effort
answer requires a recognised tool name and a well-formed digest, but forge cannot verify the agent didn't invent either. waive is the only command that always sources waived_by from the resolved git/environment identity (identity.Resolve), never from caller-supplied text — mirroring secscan's "a human waives, never the agent" discipline. See Interactive Clarification for the full trust-gap caveat.
trace
forge trace is the per-change session event timeline. The verify breaker and budget gates emit into it automatically; you can also record your own events and show the log.
trace record|show --change <c> [--kind <k>] [--summary <s>] [--actor <a>] [--field k=v] [--cwd <repo>]| Flag | Effect |
|---|---|
--kind <k> | Event kind |
--summary <s> | One-line event summary |
--actor <a> | Who produced the event — attributes the entry to an agent, a human, or a gate |
--field k=v | Structured field; repeatable. Rejects a value with no = |
record and show are the only subcommands.
$ forge trace show --change checkout
[1] 2026-07-18T21:17:28+02:00 budget — budget +600t/0tc (exceeded=false, tokens=600)
[2] 2026-07-18T21:17:28+02:00 budget — budget +600t/0tc (exceeded=true, tokens=1200)
# exit 0See the session timeline for how gates attribute events.
review
forge review is the bounded review lifecycle. It produces a content-bound receipt: start opens a review against the current scope, finalize records lens results and evidence, and validate checks the receipt against a delivery gate. Repository scope, authority, and canonical artifacts are derived by the binary.
review start [--cwd <repo>] [--focus <risk|resilience|readability|reliability>]
[--lineage <id>] [--policy <file>] [--trace <path>]
review finalize [--cwd <repo>] [--result <review.json> ...] [--evidence <path>]
[--validation <file>] [--refuter <file>] [--correction-lines N]
[--failed] [--lineage <id>] [--trace <path>]
review validate --gate <gate> [--cwd <repo>] [--lineage <id>] [--base-ref <ref>]
[--pre-pr-ci-attestation <file>] [--policy <file>]
[--release-configuration <file>] [--release-generated <file>]
[--release-provenance <file>] [--release-publication-boundary <file>]
[--release-evidence-freshness <file>]review start freezes live Git scope and derives the tier, lenses, and correction budget.
| Flag | Default | Effect |
|---|---|---|
--cwd <repo> | . | Repository path |
--focus <lens> | reliability | Dominant standard-risk focus: risk, resilience, readability, or reliability |
--lineage <id> | (derived) | Explicit review lineage identifier; defaults to review- plus the snapshot identity prefix |
--policy <file> | (native) | Custom review policy file; the native bounded policy is used otherwise |
--trace <path> | (none) | Diagnostic operation-metadata trace path |
review finalize canonicalizes reviewer output and evidence, performs the required native transitions, and materializes the terminal receipt.
| Flag | Default | Effect |
|---|---|---|
--result <file> | (none) | Reviewer result JSON, or - for stdin. Repeatable, in selected-lens order |
--evidence <file> | (none) | Final test/verification evidence, or - for stdin |
--validation <file> | (none) | Targeted correction-validation JSON, or - for stdin |
--refuter <file> | (none) | Refuter outcomes JSON, or - for stdin |
--correction-lines N | 0 | Positive predicted correction changed-lines forecast, supplied before editing |
--failed | off | Bind the supplied final evidence as a failed verification |
--lineage <id> | (discovered) | Lineage override when discovery is ambiguous |
--trace <path> | (none) | Diagnostic operation-metadata trace path |
Only one of the file inputs may read from stdin per invocation — passing - to more than one is an error.
review validate auto-discovers the authoritative state and receipt, then checks them against live Git. --gate is required.
| Flag | Default | Effect |
|---|---|---|
--gate <gate> | (required) | post-apply, pre-commit, pre-push, pre-pr, or release |
--cwd <repo> | . | Repository path |
--lineage <id> | (discovered) | Lineage override when discovery is ambiguous |
--base-ref <ref> | (none) | Expected remote publication base, for pre-pr |
--pre-pr-ci-attestation <file> | (none) | Signed exact-merged-tree CI attestation for a compatible base advance |
--policy <file> | (native) | Explicit custom policy carrying compatible-base CI trust |
--release-configuration <file> | (none) | Release configuration artifact |
--release-generated <file> | (none) | Generated-artifact manifest |
--release-provenance <file> | (none) | Release provenance artifact |
--release-publication-boundary <file> | (none) | Sealed publication-boundary artifact |
--release-evidence-freshness <file> | (none) | Current release-evidence freshness artifact |
forge review start --focus risk
forge review finalize --result review.json --evidence evidence.txt
forge review validate --gate pre-commitThe delivery gates (pre-commit, pre-push, and the rest) validate the existing receipt against the staged content and return non-zero when the tree no longer matches — they never open a new review. See Review for the lifecycle and lens selection.
Compatibility commands
Six top-level review-* commands remain as legacy v1 compatibility surfaces. They are not the modern path and are documented here only so an existing script that calls them is recognizable. review-start and review-step are read-only: they reject mutation and refuse to create new authority. Reach for the review start / review finalize / review validate subcommands above instead.
| Legacy command | Status | Modern equivalent |
|---|---|---|
review-start | Read-only; creates no authority | forge review start |
review-step | Read-only; mutation rejected | forge review finalize |
review-resume | Re-emits current authority without consuming budget | forge review finalize (re-run; native discovery resumes) |
review-validate | Receipt validation via --request or native authority flags | forge review validate --gate <gate> |
review-bundle-export | Exports compact authority or a read-only legacy v1 chain | (no modern replacement; still the export path) |
review-bundle-import | Recovers compact authority or installs a read-only legacy v1 chain | (no modern replacement; still the import path) |
Legacy authority is read-only
A lineage that already carries a legacy v1 chain is refused by review start — choose a new lineage for compact authority. Mixing the two stores makes review authority ambiguous and review validate fails closed rather than guessing.
run
forge run is the bounded autonomous loop. It drives a user-supplied executor through repeated iterations and stops the moment an enforced harness gate opens.
Forge has no model
forge run cannot write code. It orchestrates an executor you name with --cmd — typically a coding-agent CLI invocation — and enforces the gates between iterations. Without --cmd the command refuses to run at all.
run [--change <c>] --cmd <shell command> [--max-iterations N]
[--max-tokens N] [--max-tool-calls K] [--dry-run] [--yes] [--cwd <dir>]| Flag | Default | Effect |
|---|---|---|
--cmd <command> | (required) | The shell command that performs one iteration. Run via sh -c in the repo root |
--change <c> | (active change) | The change the gates are evaluated against |
--max-iterations N | 5 | Iteration ceiling. Must be between 1 and 50 — a value outside that range is a parse error |
--max-tokens N | (from budget.max_tokens in config, else no ceiling) | Token ceiling for the spend-budget gate |
--max-tool-calls K | (from budget.max_tool_calls in config, else no ceiling) | Tool-call ceiling for the spend-budget gate |
--yes | off | Actually execute. Without it nothing runs |
--dry-run | — | Explicit form of the default posture |
--cwd <dir> | current dir | Repo root |
Dry run is the default. Bare forge run --cmd '…' prints the plan and executes nothing; --yes is what turns it into a real run. The --max-iterations ceiling of 50 is deliberate and cannot be raised at the command line — a run that would need more iterations needs a human, not a bigger number.
The three gates — the verify circuit-breaker, the spend budget, and secscan — are all read-only here: the loop reads persisted state and never records new gate state. The budget gate takes each ceiling from its flag, else from the matching budget.* key in .forge/config.yaml; with none configured either way it has no ceiling and can never open. budget.max_usd has no run flag, so config is the only way to enforce a dollar ceiling in the loop. Secscan uses the same scope and threshold as forge secscan scan: the whole working tree, blocking on high severity.
Gates are checked before the first iteration and again after each one. Three consequences follow, and all of them are deliberate:
- A gate that is already open refuses to start — the loop exits with zero executor runs.
- A gate that opens mid-run halts the loop immediately and escalates.
- Exhausting the iteration cap is not success. Any outcome other than the executor reporting completion returns a non-zero exit so a human is pulled in.
A non-zero exit from your --cmd is also an error: that iteration failed, so the loop stops rather than looping over a broken executor.
$ forge run --change checkout --cmd 'claude -p "implement the next open task"'
Plan: up to 5 iteration(s) on change "checkout"
executor: sh -c "claude -p \"implement the next open task\""
gates: verify circuit-breaker, spend budget, secscan
stop: any gate opening halts the loop and escalates to a human
dry run: nothing executed (pass --yes to run)
# exit 0$ forge run --change checkout --cmd './agent.sh' --max-iterations 3 --yes
Plan: up to 3 iteration(s) on change "checkout"
executor: sh -c "./agent.sh"
gates: verify circuit-breaker, spend budget, secscan
stop: any gate opening halts the loop and escalates to a human
run: STOPPED after 0 iteration(s) — gate open
gate: verify circuit-breaker
detail: circuit breaker open after 3 consecutive verify failures
escalate: a human should review before continuing
# exit 1See the autonomous run for the loop's full stop model.
mcp
forge mcp serve exposes the harness as MCP tools over stdio, so an agent can query forge directly instead of only through shell hooks.
forge mcp serve [--cwd <dir>]The serve subcommand is required — there is no default, because starting a stdio JSON-RPC session is never something a caller should do by accident. Like hook, it stays silent on stdout apart from the protocol: no system detection, no self-update chatter, either of which would corrupt the session.
MCP is queryable, not enforcing
Exposing the gates over MCP makes the harness queryable, not enforcing. The enforcing paths stay exactly what they were: the non-zero exits consumed by hooks, pre-commit, and CI. An agent can ask the harness for a verdict over MCP and read the answer as data; it cannot be compelled by MCP alone, because nothing on this path blocks anything. A refused gate arrives here as a tool result, not as a stop.
Each tool handler invokes the same Run* function the shell command uses, with --cwd pinned to the resolved root, and returns its captured stdout. A gate's refusal surfaces as an isError result — readable data, not a transport failure. See Harness as MCP.
plugin
forge plugin assembles forge's skills, subagent templates, and MCP/hook wiring into a portable VS Code Agent Plugin bundle — the schema at https://agent-plugins.org/schemas/1.0.0/plugin.schema.json that lets a third-party tool ship skills, MCP servers, custom agents, and lifecycle hooks as one installable unit.
forge plugin build [--out <dir>] [--cwd <dir>]
forge plugin install [--out <dir>]| Subcommand | Effect |
|---|---|
build | Write the bundle to <dir> (default .forge/vscode-plugin/, relative to --cwd) for manual "Chat: Install Plugin From Source" use |
install | Write the bundle straight to VS Code Copilot's auto-discovery location (default ~/.copilot/installed-plugins/forge/) and register it in VS Code's settings.json under chat.pluginLocations — no manual VS Code command needed |
Both subcommands are idempotent: rerunning either never duplicates or corrupts existing content. install merges into settings.json rather than overwriting it, so any other settings you already have stay intact.
Auto-discovery path and settings key are unverified
The ~/.copilot/installed-plugins/<name>/ auto-discovery directory and the chat.pluginLocations setting are implemented as specified, but neither is confirmed against primary VS Code or Agent Plugins documentation (the reachable docs at build time didn't name either). Treat install as best-effort until independently verified; build plus VS Code's own "Chat: Install Plugin From Source" command is the documented, schema-verified path.
See VS Code Agent Plugin for what gets bundled, how to verify an install, and the full set of schema-uncertainty caveats.
metrics
forge metrics answers "is my harness actually working?" by rolling up the per-change state forge already records — session timeline, verify circuit-breaker, runtime budget, secscan waivers — into one report. It is read-only: it aggregates and prints, and never writes.
forge metrics [--json] [--cwd <dir>]| Flag | Default | Effect |
|---|---|---|
--json | off | Emit the whole report as indented JSON (stable machine output) |
--cwd <dir> | current dir | Repo root |
$ forge metrics
Harness metrics (3 change(s): 1 active, 2 archived)
Verify no runs recorded
# exit 0With no recorded runs the report says so rather than printing "0% pass", which would read as failure rather than as absence of data. See Harness metrics.
hook
forge hook is the live-agent feeder. Your coding host calls it after each tool use and passes the hook payload on stdin, so real activity lands in the timeline and the budget ledger.
forge hook post-tool-use # payload on stdinpost-tool-use is the supported event. The command is deliberately fast and silent and never triggers system detection or self-update — it is best-effort plumbing, not a gate. See Connect a live agent.
Delivery
Turn a change's tasks into issues, and a completed change into a merge or pull request. Both commands are provider-native: the host is resolved through the git-provider adapters plus the repo: config, and the CLI and argv come from that provider — so the same command works on GitLab (glab) and GitHub (gh).
issues
forge issues create reads the change's tasks.md, skips tasks already done, and creates one issue per remaining task on the resolved provider.
forge issues create [--change <c>] [--dry-run] [--label L]... [--workspace] [--cwd <dir>]| Flag | Default | Effect |
|---|---|---|
--change <c> | (active change) | The change whose tasks are exported |
--dry-run | off | Print the exact commands and execute nothing |
--label L | (none) | Label to apply; repeatable |
--workspace | off | Run the same export in every workspace repo carrying the change |
--cwd <dir> | current dir | Repo root |
create is the only subcommand and it is required. A change with no open tasks is reported and exits 0 — there is nothing to create.
$ forge issues create --change checkout --dry-run
[dry-run] glab issue create --title "T2 Add retry to the charge call" ...
# exit 0deliver
forge deliver opens a merge/pull request for a completed change. It refuses an incomplete change — mirroring archive — unless --allow-incomplete is passed.
forge deliver [--change <c>] [--dry-run] [--draft] [--title T] [--label L]...
[--base B] [--allow-incomplete] [--require-ci] [--workspace] [--cwd <dir>]| Flag | Default | Effect |
|---|---|---|
--change <c> | (active change) | The change to deliver |
--title T | (derived from the change name) | Change-request title |
--base B | (provider default) | Target branch |
--label L | (none) | Label to apply; repeatable |
--draft | off | Open as a draft |
--allow-incomplete | off | Deliver even with tasks not done; prints a warning per pending task |
--require-ci | off | Opt into the CI-status gate |
--workspace | off | Run the same delivery in every workspace repo carrying the change |
--dry-run | off | Print the provider argv and execute nothing |
--cwd <dir> | current dir | Repo root |
The body is rendered from the change's own artifacts: the proposal when present, a checklist of every task, and a provenance footer. A missing proposal is skipped, never fatal.
--require-ci fails open
The CI gate blocks only on a confident "failed" verdict. An unresolvable branch, a provider with no CI support, a runner failure, a still-running pipeline, and unparseable output are all reported as warnings and let delivery proceed. Determining CI status means parsing provider CLI output, which is brittle — so the gate never blocks on uncertainty. The check is read-only and therefore runs under --dry-run too, so the preview stays honest about whether delivery would actually be refused.
Both commands' --workspace fan-out shares the same isolation contract: a target that is not a local checkout is reported as skipped and counts as neither a delivery nor a failure; each remaining repo runs independently so one repo's failure never aborts the others; and the command still exits non-zero when any repo failed.
$ forge deliver --change checkout --workspace
Workspace delivery for change "checkout" across 2 repo(s): api, web
── api (GitLab) ──
opened merge request
── web (GitHub) ──
opened pull request
delivered 2/2 repo(s)
# exit 0Docs & memory
Generate the offline dashboard and surface prior context.
docs
forge docs has two distinct modes. build renders the offline, read-only spec-viewer from .forge/ to a directory (--publish also emits a Pages-ready public/). serve runs the embedded, interactiveweb control panel — it reads .forge/ live and can also act on it.
docs build|serve [--out <dir>] [--publish] [--addr <host:port>] [--cwd <repo>]forge docs build --out public --publish
forge docs serve # http://127.0.0.1:4321 (loopback)
forge docs serve --addr localhost:4000serve is a mutating surface
The panel can run verify, archive a change, clear a tripped breaker or budget, and waive a secscan finding. Mutations require POST + same-origin + a per-session X-Forge-Token, and the server binds loopback by default — think twice before overriding --addr.
docsite
forge docsite hosts two architecturally separate families under one noun. They share no state, no manifest, no config key, and no output directory.
install/dev/build— "forge's own site": scaffold and run the generator Forge installs into.forge/itself.docsite.targetin.forge/config.yamlhas no role here — the installed target lives in.forge/.docsite-install.json, sodev/buildalways read the manifest, never config.publish— "your site": renders forge's.forge/state into a public documentation site you already own, distinct fromdocs(which renders forge's internal dashboard) and frominstall's own site.
forge docsite install [--target <t>] [--force] [--no-gitignore]
[--dry-run] [--json] [--cwd <dir>]
forge docsite dev [--print] [--cwd <dir>]
forge docsite build [--out <dir>] [--print] [--cwd <dir>]
forge docsite publish [--target <t>] [--out <dir>] [--source-only]
[--dry-run] [--json] [--cwd <dir>]| Subcommand | What it does |
|---|---|
install | Scaffold the chosen generator into .forge/. Refuses a conflicting already-installed target unless --force; --no-gitignore skips the managed .gitignore block. |
dev | Exec the installed generator's dev server in the foreground over live .forge/ content. --print prints the command instead of running it. Errors naming install if nothing is installed, or by name if the installed target has no dev backend. |
build | Run the installed generator's static build — no derive stage, no config drift: the same generated config dev serves. --out controls the output directory. |
publish | Derive fresh from .forge/specs/changes, then render the site with ownership tracking. --source-only stops after staging, before any render. |
Targets: VitePress (the only install-able target today), or — for publish only — VitePress, Docusaurus, Mintlify, Starlight, MkDocs Material, or plain CommonMark. detect, plan, render, and derive were removed with no aliases; running one names its replacement (detect → install --dry-run, render/derive → publish, plan → no direct replacement). See Documentation Site Generator.
forge docsite install --target vitepress
forge docsite dev
forge docsite build --out .forge/.vitepress/dist
forge docsite publish --target vitepress --out sitepanel
forge panel launches the observe-only lifecycle control panel as a TUI. It reads state and never mutates it.
forge panel # current directory
forge panel -cwd /path/to/reporecall
forge recall is advisory — it surfaces prior specs and decisions relevant to a change's intent from memory to inform explore and spec. It is not a gate and never blocks.
recall --change <c> [--query Q] [--limit N] [--cwd DIR]| Flag | Default | Effect |
|---|---|---|
--change <c> | (required) | The change whose intent seeds the query |
--query Q | (derived from the change intent) | Explicit query, overriding the derived one |
--limit N | 5 | Cap on how many prior observations are surfaced |
--cwd DIR | current dir | Repo root |
--change is required. Omitting it is an error, not an advisory no-op — the message goes to stderr and the process exits 1:
$ forge recall
# (stdout is empty)
# exit 1
$ forge recall 2>&1 >/dev/null
Error: recall requires --change <name>That exit code is the binary's uniform error path, not a recall gate: once --change is supplied, recall stays advisory and a failed search, an absent intent, or zero results all print a note and exit 0.
memory
forge memory is the front door to the project memory store that backs recall. Every subcommand resolves the effective config, auto-detects the project from the repo root, and delegates to the provider.
forge memory save <text> [--cwd <dir>]
forge memory search <query> [--cwd <dir>]
forge memory context [--cwd <dir>]
forge memory sync [--import] [--cwd <dir>]| Subcommand | Effect |
|---|---|
save <text> | Write an observation. The text is every non-flag token, joined |
search <query> | Search the project's memory. No results is not an error |
context | Print the project's accumulated context. Takes no positional arguments |
sync | Export the project's memory into .forge/memory/. Takes no positional arguments |
--cwd <dir> is accepted by all four. The only other flag is --import, on sync: it stages memory already committed at .forge/memory/ — a teammate's checked-in memory from a fresh clone — as the provider's import input before syncing, so it is reconciled into the local store rather than only ever being overwritten by a fresh export.
forge memory save "checkout retries are capped at 3 by policy"
forge memory search retries
forge memory context
forge memory sync --import # reconcile a fresh clone's committed memoryThere is no --project flag
memory derives the project from the repo root and accepts no override. Passing --project is rejected as an unknown argument and the command hard-errors. Use --cwd to point at a different repo instead.
Memory operations are explicit and user-invoked, so a missing engram binary exits non-zero — there is no meaningful success to report. It degrades cleanly though: one clear message telling you to install it, never a panic or a raw stack trace.
codegraph
forge codegraph init initializes the codebase-graph provider for a project. It is a deliberately narrow, Forge-owned boundary used by generated agent guidance, and accepts no raw provider arguments.
forge codegraph init --cwd <project-root>learn
forge learn is the continuous-learning ledger: it mines a change's session log and git diff for recurring patterns and, only on explicit human approval, promotes them into memory.
forge learn extract|review|promote|reset [--change <c>] [--cwd <dir>] [--approve] [--threshold N]| Subcommand | Effect |
|---|---|
extract | Mine the change's session log and diff into deduped candidates |
review | List the pending candidates |
promote | Write eligible candidates into memory — requires --approve |
reset | Clear the ledger |
| Flag | Default | Effect |
|---|---|---|
--change <c> | (active change) | The change whose session log and diff are mined |
--approve | off | Authorize promotion. promote without it does not write |
--threshold N | (from learn.threshold in config) | Override the promotion threshold for this invocation — meaningful to review and promote only |
--cwd <dir> | current dir | Repo root |
Advisory, never a gate
Promotion is human-gated and deliberately never blocks: a skipped or degraded learn run does not return a failing exit code, unlike verify / budget / secscan.
Repo detection
All three commands are read-only — they detect and report, and write nothing.
profile
forge profile reports the repo-adaptivity profile Forge derives for this repo: detected stack, configured test and lint commands, git-hosting provider, size bucket, related repos, and which skills are relevant versus skippable.
forge profile [--cwd <dir>]provider
forge provider reports the git-hosting provider Forge resolves for this repo and any related repos, tying the provider adapters to the repo: section of .forge/config.yaml. It never writes or calls a host API.
forge provider [show] [--cwd <dir>]workspace
forge workspace resolves this repo plus every sibling declared under repo.related in .forge/config.yaml, and reports each one's provider, location, and active SDD changes — followed by the changes that span more than one repository.
forge workspace [status] [--change <c>] [--json] [--cwd <dir>]| Flag | Default | Effect |
|---|---|---|
--change <c> | (none) | Report which repos carry that change instead of the full status |
--json | off | Emit the resolved workspace as indented JSON |
--cwd <dir> | current dir | Repo root |
status is the only subcommand and it is the default when omitted. Config resolution is best-effort: a missing or invalid .forge/config.yaml yields a single-repo workspace rather than an error.
$ forge workspace --change checkout
Change "checkout" spans 2 repo(s): api, web
# exit 0Design system
Two non-destructive CREATE flows that seed .forge/design/. Authoring the actual content is agent-driven — these commands lay down the scaffold and hand you off to the /forge.brand and /forge.design agent commands. Existing files are never clobbered, and .forge/ is backed up first so the write is reversible.
brand
forge brand seeds only .forge/design/brand.md.
forge brand [--cwd <dir>]--cwd is the only flag; any other argument is an error.
design
Bare forge design seeds both .forge/design/brand.md and .forge/design/design-system.md. The three subcommands do real work on top of that scaffold.
forge design [detect|apply|sync] [--save] [--dry-run] [--cwd <dir>]| Subcommand | Effect |
|---|---|
| (none) | Seed the scaffold files when absent |
detect | Structural scan of existing design standards — CSS custom properties, Tailwind/theme config, component dirs, an existing design-system.md — printed as an inventory + gap report |
apply | Materialize the authored tokens into the repo: a CSS custom-properties file plus a stack-agnostic tokens JSON fallback |
sync | Classify design-token drift between the design system and the materialized code, rendered exactly like forge drift |
| Flag | Default | Applies to | Effect |
|---|---|---|---|
--save | off | detect | Also write the report to .forge/design/design-system.detected.md |
--dry-run | off | apply | Compute and print the same content without writing anything |
--cwd <dir> | current dir | all | Repo root |
detect without --save and sync are both fully read-only — no filesystem write of any kind. apply without --dry-run is the one mutating path, and it is backed up and reversible.
Extensions
Install third-party extensions from the catalog, and build a new skill from a natural-language description.
extension
forge extension manages installed extensions and the embedded official + community catalog. It works fully offline: both the installed list and the default catalog are local.
forge extension <list|add|remove|search|info|update|enable|disable> [args] [--cwd <dir>]| Subcommand | Positional | Effect |
|---|---|---|
list | (none) | Installed extensions, then every catalog entry not already installed |
add | <id|url|path> | Install from the catalog, a URL, or a local directory |
remove | <id> | Uninstall |
search | <query> | Search the catalog; all positional tokens are joined into one query |
info | <id> | Installed → the real on-disk manifest; otherwise the catalog metadata |
update | <id> | Reinstall at the catalog's version, if it differs from what is installed |
enable / disable | <id> | Toggle the registry entry's enabled flag |
--cwd <dir> is the only flag, accepted by every subcommand. add resolves its argument in three forms, in order: anything containing :// is downloaded as a zip and extracted, bypassing the catalog; an existing local directory is used as-is; anything else is looked up as a catalog ID.
update requires the extension to be both installed and present in the catalog — the catalog is the only source of "what is the newer version". A catalog load failure during list degrades to a warning rather than failing the command, since the installed list is still useful on its own.
forge extension list
forge extension add forge-python
forge extension add ./my-local-extension
forge extension info forge-python
forge extension disable forge-pythonagent-build
forge agent-build generates a validated skill from a natural-language description, previews it, installs it into every configured agent's skills directory, and registers the result as an extension — so it shows up in forge extension list and is removable with forge extension remove.
forge agent-build <description> [--engine <id>] [--cwd <dir>]| Flag | Default | Effect |
|---|---|---|
--engine <id> | (first available) | The AI CLI that drives generation. Must be available on PATH |
--cwd <dir> | current dir | Repo root |
Every non-flag token is joined into the description, so it needs no quoting — though quoting is clearer. An empty description is a usage error.
Without --engine, forge tries a fixed candidate order and takes the first one available on PATH: claude-code, opencode, gemini-cli, codex. If none is available the command fails with that list. It also fails when no configured agent exposes a skills directory — configure at least one agent first.
The preview is printed as soon as generation succeeds, before anything touches disk, and stays printed even when the subsequent install partially or wholly fails. Every agent-built skill registers at version 0.1.0: there is no version input for a single-shot description build.
forge agent-build "review Terraform plans for destructive changes"
forge agent-build "summarize failing CI logs" --engine opencodeSkills
Manage the skill registry the engine delegates to.
skill-registry
forge skill-registry refresh rebuilds .forge/skill-registry.md with a cache-hit fast path; list prints the resolved registry.
$ forge skill-registry list
algorithmic-art user /Users/you/.agents/skills/algorithmic-art/SKILL.md
cognitive-doc-design project /repo/skills/cognitive-doc-design/SKILL.md
# exit 0forge skill-registry refreshskill-discover
forge skill-discover discovers and installs external framework skills from configured sources (skills.sh and community git registries) into .forge/skills/ — distinct from Forge's embedded foundation and SDD skills.
usage: forge skill-discover [--source <name>] [search terms...] [--install <id>] [--cwd <dir>]forge skill-discover # list everything every source advertises
forge skill-discover react # search all sources for "react"
forge skill-discover --source skills-sh react
forge skill-discover --install react-19 # install into .forge/skills/react-19/A source that is unreachable is reported and skipped — it never aborts the rest of the command. Run forge skill-registry refresh after installing to index the new skill for delegation.
The binary itself
These commands act on the forge installation rather than on a project.
version
forge version
forge --version
forge -vPrints the version resolved at build time. The three forms are identical.
help
forge help
forge --help
forge -hPrints global help. Every review subcommand also accepts --help for its own usage.
completion
forge completion prints a shell completion script generated from the same command table that backs the CLI, so completions never drift from the binary.
forge completion zsh|bash|fish# zsh, via Homebrew's site-functions
forge completion zsh > "$(brew --prefix)/share/zsh/site-functions/_forge"update
forge updateChecks for available updates to forge and the tools it manages, and reports what is available. It only checks — nothing is downloaded or replaced.
upgrade
forge upgrade applies the updates update reports.
forge upgrade [--yes] [--no-backup]| Flag | Default | Meaning |
|---|---|---|
--yes | off | Skip the confirmation prompt |
--no-backup | off | Do not back up managed configs before writing |
A config backup is taken before anything is replaced unless you pass --no-backup; restore is what reads it back.
Binaries come from the private GitHub release repo, so every download carries your token — see Installation.
restore
forge restore puts a managed-config backup back.
forge restore [latest] [--list] [--yes] [--cwd <dir>]| Flag | Default | Meaning |
|---|---|---|
--list | off | List available backups instead of restoring |
--yes | off | Skip the confirmation prompt |
--cwd | current directory | Repo root to check for repo-scoped backups |
latest restores the most recent backup, which is the common case after an upgrade you want to undo.
restore checks both backup locations: the home-scoped ~/.forge/backups/ written by install/sync/upgrade/uninstall, and, when the current directory (or --cwd) is a project with its own .forge/ tree, that project's <repoRoot>/.forge/backups/ too — the location import and migrate snapshot into before they touch anything. --list labels each entry [home] or [repo] so you can tell which one applies to the project you're standing in.
Next steps
- Commands — the
/forge.*agent slash commands, the in-agent counterpart to this binary. - Harness overview — the four enforced controls and how they fit together.
- The circuit-breaker — the verify gate's state model.
- The autonomous run —
forge run's bounds and stop conditions. - Harness as MCP — the queryable, non-enforcing tool surface.
- Spend Budget — the ledger format and enforcement rules.
- Installation — install, maintain, and update the binary.