Skip to content
Download PDF

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.

text
$ forge --help
forge — Maistik Forge: Ecosystem, Frameworks, Workflows

USAGE
  forge                     Launch interactive TUI
  forge <command> [flags]
# exit 0

Two global flags apply everywhere:

FlagMeaning
--help, -hPrint 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:

sh
forge version

Where 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.

text
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 help
sh
forge install --scope workspace          # this project only
forge install --agents claude-code,cursor
forge install --dry-run                  # preview the plan, write nothing

init ​

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.

text
forge init [--yes] [--preset <name>] [--agents <list>] [--scope workspace|global] [--reconfigure]
FlagDefaultEffect
--yesoffSkip 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
--reconfigureoffHeadless: 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.

sh
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 scratch

config 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.

text
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.

text
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.

text
forge config set <key> <value> [--cwd <path>]
sh
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 default

An 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.

sh
forge sync

sync 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.

text
forge doctor [--fix] [--selftest]
FlagDefaultEffect
--fixoffMutating. Install the missing dependencies the check reports, then print the report
--selftestoffRun 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.

sh
forge doctor              # read-only report
forge doctor --fix        # report AND install what is missing
forge doctor --selftest   # binary self-test only

Verify 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.

text
forge import specify|openspec [--from <path>] [--cwd <dir>]
                              [--dry-run] [--force] [--include-generated]
FlagDefaultMeaning
--from <path>auto-detectedSource project root
--dry-runoffPrint the plan; write nothing
--forceoffOverwrite existing .forge/ content
--include-generatedoffAlso 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.

text
forge migrate [--cwd <dir>] [--dry-run]
FlagDefaultMeaning
--cwd <dir>current directoryRepo root to migrate
--dry-runoffPrint 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:

sh
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 files

Two gotchas worth knowing before you run this:

  • forge install takes no --cwd — unlike migrate and skill-registry, you must actually be cd'd into the target repo. --scope workspace writes into that repo's local agent directories (.claude/, .codeium/windsurf/, etc.); the default --scope global writes into your home directory instead — pass --scope workspace explicitly, don't rely on a project default.
  • forge sync is 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.

CommandSignatureWhat it does
sdd-statussdd-status [change]Print native SDD phase status for orchestrators. Read-only.
sdd-continuesdd-continue [change]Print native SDD dispatcher routing output.
statusstatus [change] [--cwd <repo>] [--json]Colorized, single-shot snapshot of a change's progress for a human. Read-only.
analyzeanalyze [<spec-path>] [--portfolio] [--save] [--json] [--cwd <repo>]Reproducible 100-point spec quality score. Read-only unless --save.
healthhealth [--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.
archivearchive [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.

text
$ forge analyze
No specs found under .forge/specs or .forge/changes; nothing to analyze.
# exit 0

sdd-status prints the phase rollup orchestrators route on:

text
$ forge sdd-status
## SDD Status: continuous-learning-loop

next: spec

### Summary
- apply: blocked
- verify: blocked
- tasks: 51/51 complete
# exit 0

Migration ​

forge import migrates an existing Spec-Kit (.specify) or OpenSpec (openspec) project into the unified .forge/ layout, non-destructively.

text
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:

text
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.

text
forge board [--cwd <repo>] [--write] [--output <path>]
FlagEffect
--cwd <repo>Repo root (defaults to the current directory)
--writeAlso 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.

text
forge roadmap [--cwd <repo>] [--write] [--output <path>]
FlagEffect
--cwd <repo>Repo root (defaults to the current directory)
--writeAlso 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.

text
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.

text
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.

FlagDefaultMeaning
--cwd <dir>current directoryRepository root to classify
--change <c>active changeScope the waiver ledger to one change
--enforceoffTurn the report into a gate — see exit codes below
--fail-on <csv>mutated,phantomWhich 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.

text
$ forge drift
No drift detected: every spec-code cross-reference resolved cleanly.
# exit 0
text
$ 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 0

archive ​

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.

text
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.

text
forge analyze [<spec-path>] [--portfolio] [--save] [--json] [--cwd <dir>]
FlagDefaultMeaning
--portfoliooffForce portfolio rollup even when a path is given
--saveoffPersist the score; the only writing mode
--jsonoffStable 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.

text
forge health [--save] [--cwd <dir>]
FlagDefaultMeaning
--saveoffWrite 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.

text
forge estimate [--change <c>] [--cwd <dir>]
FlagDefaultMeaning
--change <c>active changeWhich change's tasks.md to refresh
--cwd <dir>current directoryRepository 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.

text
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.

text
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.

text
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.

text
forge status [change] [--cwd <repo>] [--json]
text
$ 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 0

Colors 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.

text
verify [--change <c>] [--max-failures N] [--reset-breaker] [--isolated] [--cwd <repo>]
FlagDefaultEffect
--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-breakeroffClear the breaker for --change instead of running verification
--isolatedoffRun the configured commands against an ephemeral copy of the working tree instead of the working tree itself
--cwd <repo>current dirRepo root
text
$ 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 1

Clear an open breaker after a human intervenes:

sh
forge verify --reset-breaker --change c1

See 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.

text
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.

FlagDefaultEffect
--tokens N0Tokens to add to the ledger
--usd X0Dollars to add to the ledger (accepts a decimal)
--tool-calls K0Tool 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
text
$ 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 1

No 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.

text
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.

text
$ 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 1

Waive a reviewed finding by its fingerprint, then re-run to pass:

sh
forge secscan waive --change checkout --fingerprint 0c9ab6bbabac

See 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.

text
clarify-gate [--cwd <repo>] [--session-id <id>] [--change <name>] [--format text|json]
FlagDefaultEffect
--cwd <repo>current dirRepo 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|jsontextOutput shape
text
$ 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 1

See 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.

text
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>]
FlagApplies toEffect
--id <qid>allThe caller-supplied, stable question id. Required
--question <text>openThe question text. Required
--asked-in <name>openThe skill/command that asked. Required
--tool AskUserQuestion|questionanswerThe recognised interactive tool the answer came through. Required
--answer-digest <12hex>answer12 lowercase hex chars — a sha256 fingerprint of the answer text, same discipline as secscan. Required
--reason <text>waiveOptional note; echoed to stdout only, not persisted
--change <c> | --session <id>allMutually exclusive scope selector; neither given falls back to the single active change
--cwd <repo>allRepo root
sh
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 checkout

answer 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.

text
trace record|show --change <c> [--kind <k>] [--summary <s>] [--actor <a>] [--field k=v] [--cwd <repo>]
FlagEffect
--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=vStructured field; repeatable. Rejects a value with no =

record and show are the only subcommands.

text
$ 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 0

See 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.

text
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.

FlagDefaultEffect
--cwd <repo>.Repository path
--focus <lens>reliabilityDominant 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.

FlagDefaultEffect
--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 N0Positive predicted correction changed-lines forecast, supplied before editing
--failedoffBind 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.

FlagDefaultEffect
--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
sh
forge review start --focus risk
forge review finalize --result review.json --evidence evidence.txt
forge review validate --gate pre-commit

The 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 commandStatusModern equivalent
review-startRead-only; creates no authorityforge review start
review-stepRead-only; mutation rejectedforge review finalize
review-resumeRe-emits current authority without consuming budgetforge review finalize (re-run; native discovery resumes)
review-validateReceipt validation via --request or native authority flagsforge review validate --gate <gate>
review-bundle-exportExports compact authority or a read-only legacy v1 chain(no modern replacement; still the export path)
review-bundle-importRecovers 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.

text
run [--change <c>] --cmd <shell command> [--max-iterations N]
    [--max-tokens N] [--max-tool-calls K] [--dry-run] [--yes] [--cwd <dir>]
FlagDefaultEffect
--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 N5Iteration 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
--yesoffActually execute. Without it nothing runs
--dry-run—Explicit form of the default posture
--cwd <dir>current dirRepo 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.

text
$ 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
text
$ 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 1

See 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.

text
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.

text
forge plugin build [--out <dir>] [--cwd <dir>]
forge plugin install [--out <dir>]
SubcommandEffect
buildWrite the bundle to <dir> (default .forge/vscode-plugin/, relative to --cwd) for manual "Chat: Install Plugin From Source" use
installWrite 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.

text
forge metrics [--json] [--cwd <dir>]
FlagDefaultEffect
--jsonoffEmit the whole report as indented JSON (stable machine output)
--cwd <dir>current dirRepo root
text
$ forge metrics
Harness metrics (3 change(s): 1 active, 2 archived)

Verify      no runs recorded
# exit 0

With 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.

text
forge hook post-tool-use        # payload on stdin

post-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.

text
forge issues create [--change <c>] [--dry-run] [--label L]... [--workspace] [--cwd <dir>]
FlagDefaultEffect
--change <c>(active change)The change whose tasks are exported
--dry-runoffPrint the exact commands and execute nothing
--label L(none)Label to apply; repeatable
--workspaceoffRun the same export in every workspace repo carrying the change
--cwd <dir>current dirRepo 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.

text
$ forge issues create --change checkout --dry-run
  [dry-run] glab issue create --title "T2 Add retry to the charge call" ...
# exit 0

deliver ​

forge deliver opens a merge/pull request for a completed change. It refuses an incomplete change — mirroring archive — unless --allow-incomplete is passed.

text
forge deliver [--change <c>] [--dry-run] [--draft] [--title T] [--label L]...
              [--base B] [--allow-incomplete] [--require-ci] [--workspace] [--cwd <dir>]
FlagDefaultEffect
--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
--draftoffOpen as a draft
--allow-incompleteoffDeliver even with tasks not done; prints a warning per pending task
--require-cioffOpt into the CI-status gate
--workspaceoffRun the same delivery in every workspace repo carrying the change
--dry-runoffPrint the provider argv and execute nothing
--cwd <dir>current dirRepo 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.

text
$ 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 0

Docs & 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.

text
docs build|serve [--out <dir>] [--publish] [--addr <host:port>] [--cwd <repo>]
sh
forge docs build --out public --publish
forge docs serve                        # http://127.0.0.1:4321 (loopback)
forge docs serve --addr localhost:4000

serve 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.target in .forge/config.yaml has no role here — the installed target lives in .forge/.docsite-install.json, so dev/build always read the manifest, never config.
  • publish — "your site": renders forge's .forge/ state into a public documentation site you already own, distinct from docs (which renders forge's internal dashboard) and from install's own site.
text
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>]
SubcommandWhat it does
installScaffold the chosen generator into .forge/. Refuses a conflicting already-installed target unless --force; --no-gitignore skips the managed .gitignore block.
devExec 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.
buildRun the installed generator's static build — no derive stage, no config drift: the same generated config dev serves. --out controls the output directory.
publishDerive 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.

sh
forge docsite install --target vitepress
forge docsite dev
forge docsite build --out .forge/.vitepress/dist
forge docsite publish --target vitepress --out site

panel ​

forge panel launches the observe-only lifecycle control panel as a TUI. It reads state and never mutates it.

sh
forge panel            # current directory
forge panel -cwd /path/to/repo

recall ​

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.

text
recall --change <c> [--query Q] [--limit N] [--cwd DIR]
FlagDefaultEffect
--change <c>(required)The change whose intent seeds the query
--query Q(derived from the change intent)Explicit query, overriding the derived one
--limit N5Cap on how many prior observations are surfaced
--cwd DIRcurrent dirRepo root

--change is required. Omitting it is an error, not an advisory no-op — the message goes to stderr and the process exits 1:

text
$ 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.

text
forge memory save <text> [--cwd <dir>]
forge memory search <query> [--cwd <dir>]
forge memory context [--cwd <dir>]
forge memory sync [--import] [--cwd <dir>]
SubcommandEffect
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
contextPrint the project's accumulated context. Takes no positional arguments
syncExport 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.

sh
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 memory

There 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.

text
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.

text
forge learn extract|review|promote|reset [--change <c>] [--cwd <dir>] [--approve] [--threshold N]
SubcommandEffect
extractMine the change's session log and diff into deduped candidates
reviewList the pending candidates
promoteWrite eligible candidates into memory — requires --approve
resetClear the ledger
FlagDefaultEffect
--change <c>(active change)The change whose session log and diff are mined
--approveoffAuthorize 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 dirRepo 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.

text
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.

text
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.

text
forge workspace [status] [--change <c>] [--json] [--cwd <dir>]
FlagDefaultEffect
--change <c>(none)Report which repos carry that change instead of the full status
--jsonoffEmit the resolved workspace as indented JSON
--cwd <dir>current dirRepo 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.

text
$ forge workspace --change checkout
Change "checkout" spans 2 repo(s): api, web
# exit 0

Design 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.

text
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.

text
forge design [detect|apply|sync] [--save] [--dry-run] [--cwd <dir>]
SubcommandEffect
(none)Seed the scaffold files when absent
detectStructural scan of existing design standards — CSS custom properties, Tailwind/theme config, component dirs, an existing design-system.md — printed as an inventory + gap report
applyMaterialize the authored tokens into the repo: a CSS custom-properties file plus a stack-agnostic tokens JSON fallback
syncClassify design-token drift between the design system and the materialized code, rendered exactly like forge drift
FlagDefaultApplies toEffect
--saveoffdetectAlso write the report to .forge/design/design-system.detected.md
--dry-runoffapplyCompute and print the same content without writing anything
--cwd <dir>current dirallRepo 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.

text
forge extension <list|add|remove|search|info|update|enable|disable> [args] [--cwd <dir>]
SubcommandPositionalEffect
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.

sh
forge extension list
forge extension add forge-python
forge extension add ./my-local-extension
forge extension info forge-python
forge extension disable forge-python

agent-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.

text
forge agent-build <description> [--engine <id>] [--cwd <dir>]
FlagDefaultEffect
--engine <id>(first available)The AI CLI that drives generation. Must be available on PATH
--cwd <dir>current dirRepo 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.

sh
forge agent-build "review Terraform plans for destructive changes"
forge agent-build "summarize failing CI logs" --engine opencode

Skills ​

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.

text
$ 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 0
sh
forge skill-registry refresh

skill-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.

text
usage: forge skill-discover [--source <name>] [search terms...] [--install <id>] [--cwd <dir>]
sh
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 ​

text
forge version
forge --version
forge -v

Prints the version resolved at build time. The three forms are identical.

help ​

text
forge help
forge --help
forge -h

Prints 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.

text
forge completion zsh|bash|fish
sh
# zsh, via Homebrew's site-functions
forge completion zsh > "$(brew --prefix)/share/zsh/site-functions/_forge"

update ​

text
forge update

Checks 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.

text
forge upgrade [--yes] [--no-backup]
FlagDefaultMeaning
--yesoffSkip the confirmation prompt
--no-backupoffDo 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.

text
forge restore [latest] [--list] [--yes] [--cwd <dir>]
FlagDefaultMeaning
--listoffList available backups instead of restoring
--yesoffSkip the confirmation prompt
--cwdcurrent directoryRepo 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 ​