Skip to content

Latest commit

 

History

History
1101 lines (852 loc) · 60.8 KB

File metadata and controls

1101 lines (852 loc) · 60.8 KB

Forge — the complete guide

One brain for every AI coding agent. A language model is stateless — one context window, wiped every call. It can't remember what your team learned, can't foresee what an edit breaks, and has no enforced guardrails. Forge is the cognitive substrate — the layer that runs before the model edits code — that supplies exactly those three things, and it ships them as native config to nine AI coding tools at once. The brain is the point; one config for every tool is how the brain gets delivered.

This is the exhaustive reference: every command with a worked example and its real output, the everyday workflow, how to make it run itself inside any agent, common recipes, and how to extend each piece. If you just want to get going, the 5-minute onboarding is shorter; this is the reference you come back to.

Command groups

Every command is real and wired. Grouped by what it does:

Group Commands
Config / cross-tool sync forge init · forge sync · forge doctor · forge update · forge docs · forge config · forge harden · forge catalog · forge brand
Memory & ledger (PCM) forge ledger · forge recall · forge remember · forge brain · forge cortex · forge reuse · forge handoff · forge decide
Code graph & retrieval forge atlas · forge stack · forge context
Substrate / pre-action forge substrate · forge preflight · forge route · forge impact · forge scope · forge imagine · forge anchor · forge diagnose · forge lean · forge cost
Verification & safety forge verify · forge scan · forge spec
UI / design forge taste · forge uicheck
Dashboard forge dash

Storage in one line: the code graph is .forge/atlas.json (plain JSON, not SQLite); the ledger is content-addressed claims under .forge/ledger/ (git-committable, union-merge). The runtime is zero-dependency Node — optional tiers (FORGE_EMBED embeddings, Playwright for uicheck visual) are opt-in and still add no required dependency.


Mental model

A coding model starts every call from zero: its training is frozen and it forgets everything the moment a session ends. Five gaps follow, and no prompt closes them — it can't remember across sessions, can't learn from outcomes, can't imagine what an edit breaks, can't reliably check itself, and can't see what already exists beyond its current window. (Formally: inference is a fixed function y = f(x) with no state between calls.)

Forge supplies those faculties from the outside, in three layers:

  • tools — know-how the model loads on demand (lean, atlas, recall…).
  • crew — isolated sub-agents for focused work (scout, verifier…).
  • guards — deterministic shell hooks that enforce what prose can't (the only layer the model can't drift from).

Two subsystems sit on top: Cortex (self-correcting memory) and the cognitive substrate (forge substrate — the pre-action check). The full argument is the white paper.


The everyday workflow

The daily loop — every outcome an oracle observes lands in the team ledger, and the ledger informs the next task:

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#201a15','primaryTextColor':'#f2ede7','primaryBorderColor':'#372c22','lineColor':'#f26430','secondaryColor':'#272019','tertiaryColor':'#171310','fontFamily':'ui-sans-serif, system-ui, sans-serif'}}}%%
flowchart LR
    W["work — substrate pre-checks,<br/>then edit"] --> O["oracles — forge verify ·<br/>imagine --run · CI · human accept/revert"]
    O -->|"outcomes move claim val"| L[("team ledger<br/>.forge/ledger/")]
    L <-->|"git + forge ledger merge"| T["teammates' ledgers"]
    L -->|"lessons · facts · reuse hits"| W
    classDef accent fill:#f26430,stroke:#f26430,color:#171310;
    class L accent;
Loading
cd your-project
forge init                              # once per repo: emit every tool's config
forge atlas build                       # once: index symbols so impact/scope work well

# then, for any non-trivial change:
forge substrate "<what you want to do>" # is it clear? which model? what breaks? split?
# …make the edit…
forge anchor "<the goal>"               # still on track? flags changed files that drifted off-goal
forge verify                            # prove it — real tests + hallucinated-symbol check

On Claude Code the forge substrate step happens automatically on every prompt (see Auto-use); on other tools you or the agent run it.


Command reference

Outputs below are copied verbatim from a real run against a four-file demo repo where src/login.js and src/session.js both import { verifyToken } from "./auth.js".

forge substrate "<task>" — the one pre-action check

Bundles assumption gate + route + impact + scope + memory + verify into one verdict. This is the command you'll use most.

A clear task — cleared to proceed, with the blast radius:

$ forge substrate "Change verifyToken in src/auth.js to require length > 20; update tests"

Forge substrate — pre-action check

  proceed: yes
  assumption: medium risk · completeness 0.63

  route: Haiku 4.5 (simple) · complexity 0.15
    driven by: base cost of any task

  context: complete — 4 required item(s), 1840/12000 tokens (`forge context` for the assembly)

  impact: 3 file(s) predicted
    - src/auth.js
    - src/login.js
    - src/session.js

  verify:
    - review impacted files before editing
    - run the narrowest affected test first, then the broader suite

It found login.js and session.js — the two files that import verifyToken but you never named. That's the "forgot the coupled file" bug, caught before the edit.

A vague task — it tells you to ask first:

$ forge substrate "make the auth better"

Forge substrate — pre-action check

  proceed: ASK FIRST
  assumption: high risk · completeness 0.23

  clarify:
    - What exactly should this produce, and how will we know it is correct?

  route: Haiku 4.5 (simple) · complexity 0.15
  impact: 0 file(s) predicted

Add --json for machine-readable output (see Use it in a script).

forge preflight "<task>" — is this clear enough to start?

The assumption gate on its own. Flags names the repo doesn't define and vague wording.

$ forge preflight "fix the thing in authManager so it works properly"

Forge preflight — assumption check

  info-gap: 1.00  · completeness 0.01  (referenced 1 symbol(s), 0 file(s))

## Before starting — clarify (Forge Preflight)
This task has unknowns that would otherwise become assumptions:

- `authManager` — not found in the code. Different name, or should it be created?
- Ambiguous: "properly" — state concrete acceptance criteria.
- Which specific file, module, component, or symbol should this change touch?
- How will we verify it: tests, acceptance criteria, benchmark, or reference behavior?

_Advisory: ask rather than assume._

forge route "<task>" — the cheapest capable model

A transparent, deterministic rubric (never a second LLM call), and every score is attributable. The text side works by resemblance: your task is compared to ~50 example tasks with known difficulty, and the closest matches — which the output names — set the estimate (a similarity-weighted k-NN over the labeled EXEMPLARS bank in src/route.js). The repo side scores real signals (files in scope, impact fan-out, churn, past-mistake density, ambiguity). Whichever facet detects difficulty sets the tier.

$ forge route "write an is_prime function"
  → Haiku 4.5  (simple, $1/$5 per M tok)
    lint, formatting, docs, stubs, trivial well-defined edits
    driven by: similar to "check if a number is prime" (sim 1.00, complexity 0.08)

$ forge route "design and implement a distributed rate limiter with sliding windows across 3 services"
  → Opus 4.8  (complex, $5/$25 per M tok)
    architecture, cross-module refactor, novel algorithms, multi-layer debugging
    complexity 0.73 · driven by: similar to "implement a rate limiter with a token bucket" (sim 0.43, complexity 0.78)

Unseen phrasings route by resemblance — "two threads deadlock when the queue is full" lands in the concurrency neighborhood without any keyword list needing the literal token "race condition". To tune routing, add labeled rows to EXEMPLARS (data, not weights). ANTHROPIC_MODEL / FORGE_MODEL override the tier choice entirely.

Run forge route gateway to emit a LiteLLM config so the routing happens automatically.

forge route calibrate is the advisory → gated promotion (overview §4): it fits an affine correction of the rubric's score toward a held-out labeled fixture and reports whether that calibration measurably beats the raw rubric (lower held-out MAE past a margin) — the same kill-criteria discipline as the risk predictor (src/predictor.js), generalized in src/promote.js so any advisory signal (routing weights here; consolidation and hazard next) can only become active by measurement, never by assertion. It is advisory: routing keeps the rubric until a promoted calibration is explicitly adopted.

$ forge route calibrate
Forge route calibrate — outcome-calibrated routing (measured gate)

  samples: 24 labeled task(s)
  held-out MAE: rubric 0.152 · calibrated 0.226
  → keep the rubric — baseline retained — candidate did not beat it by the margin

  advisory — routing stays on the rubric until a promoted calibration is adopted

Here the gate does exactly its job: the rubric already generalizes well, the affine calibration would make held-out error worse, so it is refused. A promotion only happens when the measurement earns it — an assertion never does.

forge config — provider setup

Shows, switches, and registers model providers, and sets the default model. Forge auto-detects the provider from the environment with zero config — the priority order is LITELLM_BASE_URLANTHROPIC_BASE_URL (a URL that answers /health or names a gateway is classified as one) → OPENROUTER_API_KEYANTHROPIC_API_KEYANTHROPIC_AUTH_TOKENOPENAI_API_KEYGEMINI_API_KEY (or GOOGLE_API_KEY). Anthropic credentials win when present (forge is Claude-native); OpenAI and Gemini are picked up as the zero-config fallback when they are the only key set, and are reached over their OpenAI-compatible chat/completions surface. An explicit .forge/providers.json always wins over detection.

$ forge config show          # active provider + how it was resolved
$ forge config providers     # everything detected + configured
$ forge config provider <name>            # switch
$ forge config provider add <name> --base-url <url> [--env-key VAR]
$ forge config model <tier-or-id>         # default model
$ forge config gateway       # emit litellm.config.yaml (same as forge route gateway)
$ forge config setup         # guided first-time setup

Corporate gateway environments work out of the box: with ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN set (LiteLLM-style gateways), detection classifies the gateway, auth uses the token as a Bearer credential, and ANTHROPIC_MODEL pins the model.

Custom gateways that rename models. The tier table ships public Anthropic IDs (claude-haiku-4-5-…, claude-sonnet-5, …), but a self-hosted gateway often serves its own names (bedrock-claude-haiku, prod-sonnet-5). When a non-default gateway base URL is set, Forge asks it once per process (GET /v1/models) and scores each advertised model against every tier's family — the family word (haiku/sonnet/opus/fable) gates the match, the overlap score picks the best id — then remaps each tier onto a real gateway model. It is a silent, zero-config fallback: no gateway, an unreachable /v1/models, or no family match and the stock IDs are used unchanged; direct api.anthropic.com sessions never probe. An explicit model in .forge/providers.json (or ANTHROPIC_MODEL) always wins over the remap. forge doctor prints the resolved tier→model mapping under gateway models so you can verify it and pin explicit IDs if a family scored wrong.

forge impact <symbol|file> — what will this edit break?

Reverse-dependency blast radius from the atlas graph. Run forge atlas build first.

$ forge impact verifyToken
Forge impact — blast radius

  target: verifyToken  ✓ found
  impacted files: 3
    - src/auth.js
    - src/login.js
    - src/session.js

forge scope <file…> — can this be split into sessions?

Groups the files you name into independent clusters and surfaces coupled files you didn't name — so you split cleanly instead of overloading one session.

$ forge scope src/auth.js src/report.js
Forge scope — task decomposition

  2 independent groups → consider a separate session per group:

  [1] src/auth.js
      ! also coupled (you didn't name): src/session.js, src/login.js
  [2] src/report.js

forge anchor "<goal>" — are your changes still on the stated goal?

Goal-anchoring (the paper's M4): it re-reads your original objective against the files you've actually changed (git diff HEAD + untracked, minus forge's own generated config), and flags work that wandered off-goal. Quiet on a clean tree — it only speaks once there's a diff to compare, so it's a mid-session "am I still on track?" check.

$ forge anchor "harden verifyToken in src/auth.js"
Forge anchor — goal-drift check

  changed: 2 file(s) · on-goal 1 · off-goal 1

  off-goal (unrelated to the stated goal — intended, or drift?):
    - src/report.js

src/auth.js maps to the goal (named file + where verifyToken lives); src/report.js doesn't — so it's surfaced as drift to confirm or undo. Advisory by design. The on-goal/off-goal call is graded and identifier-aware: each file's on-goal confidence is a noisy-OR over how many goal concepts it exhibits in its path and the identifiers it defines (via the atlas), so a file that implements the goal but never spells it in its path is still classed on-goal. forge substrate folds this in automatically. The result carries driftScore — the fraction of the checkpoint's changes classed off-goal — the signal the cusum change-point detector accumulates to catch sustained small drift (an on-goal checkpoint scores 0 and drains the chart).

The goal persists. forge anchor set "<goal>" stores it in .forge/goal.md; every new session re-injects it at SessionStart, a bare forge anchor checks against it, and forge anchor show / forge anchor clear manage it. A goal set on Monday still anchors Thursday's session — no more each-session re-assumption of what you're working toward.

forge handoff "<done>" — the bounded session snapshot

Session memory is volatile; .forge/state.md is the checkpoint that survives. One command rewrites it (never appends — it stays ≤150 lines forever) with what got done, what comes next, the gotchas, and any assumptions this session proceeded under (gathered automatically from the session log, along with in-progress git files):

forge handoff "built the export endpoint" \
  --next "wire pagination" --gotcha "sqlite locks on parallel writes" \
  --criteria "export completes under 5s on the fixture db"

Every new session re-injects the snapshot at SessionStart, so the next session — any machine, any day — resumes instead of re-assuming. Refuses secrets, like every forge store. Updating it also satisfies the completion gate (below): the weakest way to stop cleanly is to tell the future what happened.

forge decide "<decision — reason>" — the append-only decision log

Sessions re-decide (or silently contradict) what a past session already settled, because nothing durable recorded the choice. forge decide appends one ADR-lite line to .forge/decisions.md (- **D-0007** (2026-07-10): …) and mints a machine-readable decision claim in the ledger. Bare forge decide lists the last ten. Append-only by design: a decision that stops being true gets a new entry, never an edit — the log is history, and forge docs sync exempts it for exactly that reason.

forge docs sync — which prose did this diff make stale?

forge docs check reconciles the registries; docs sync answers the diff-shaped question. It extracts the changed identifiers (paths, definitions, called symbols — from added and removed lines, so deletions count), scans every doc artifact (atlas doc nodes + README/GUIDE/ARCHITECTURE), and gives each one a verdict:

$ forge docs sync
docs sync — diff vs a1b2c3d: 2 changed file(s), 7 identifier(s)
  UPDATED     docs/GUIDE.md (changed in this diff)
  STALE       README.md — mentions changed identifiers:
                README.md:41  `validateOrder` — Use `validateOrder` from `src/val.js`…
  VERIFIED    ARCHITECTURE.md — mentions none of the 7 changed identifiers

STALE hits are cited file:line — update them or justify out loud. VERIFIED means checked-not-assumed: the reason is recorded. Advisory by default (it runs mid-repair); --strict exits 1 on stale docs for CI, --base <ref> widens the diff (an unknown ref errors instead of silently mislabeling), --json for tooling. The base defaults to this session's git baseline when the hooks recorded one, else HEAD.

Precision rules the sweep applies: symbols come only from non-test code files (prose parentheses are not call sites); all-lowercase symbols like cusum count only inside backtick code spans (in plain prose they're indistinguishable from English); a doc touched in the same diff still answers for removed symbols (updated-for-one-reason isn't updated-for-the-rename); and .forge/state.md is never scanned — handoff writes the changed-file list into it by design, so the gate's mtime check covers it instead.

forge verify — did it actually work?

The independent check: runs the real test suite and flags edited symbols that aren't in the codebase (possible hallucinations). This is what turns "the model says it's done" into "the tests say it's done."

$ forge verify
Forge verify

  changed files:    2
  tests:            ✓ pass
  symbols checked:  7
  provenance:       .forge/provenance.json

  PASS

forge atlas build | query | has — the code-graph

$ forge atlas build
  indexed 5 symbols in 4 files → .forge/atlas.json

$ forge atlas query verifyToken
  src/auth.js:1  function verifyToken

$ forge atlas has doesNotExistSymbol
  ✗ not found (possible hallucinated symbol): doesNotExistSymbol

query costs a few hundred tokens instead of reading five files; has is a cheap "is this symbol real?" check before an agent calls it.

The atlas parses JS/TS, Python, Go, Rust, Java, Ruby, C#, PHP, Kotlin, Swift, and C/C++ — adding a language is a regex grammar in src/atlas.js RULES, and everything downstream (the walk, the completion gate's code-class, the docs sweep) picks it up from that one table.

forge stack — what is THIS repo actually built with?

The atlas lists the languages forge can parse; forge stack answers the other question — the repo's real stack — by reading its dependency manifests (not a hardcoded menu). It reports languages, frameworks, package managers, and the actual test command(s), which also feed the substrate's verification checklist:

$ forge stack
  languages:  JavaScript/TypeScript, TypeScript
  frameworks: Next.js, React
  pkg mgrs:   pnpm
  test:       npx vitest
  evidence:   package.json

Detection reads package.json (deps → frameworks, lockfile → package manager), pyproject.toml/requirements.txt, go.mod, Cargo.toml, Gemfile, composer.json, pom.xml/build.gradle, and *.csproj. Widening it is adding a data row, not code. --json for tooling. Nothing detected → an honest "no known stack".

forge update — self-update

No more manual "am I on the latest?". forge update --check reports whether a newer version is available (git checkout: commits behind upstream, from a cached hourly fetch); bare forge update applies it (git pull --ff-only for a checkout — symlink/npm-link installs go live immediately; npm-global installs get the npm i -g command). forge doctor also surfaces a one-line notice when behind (cached, never nags; silence it with FORGE_NO_UPDATE_CHECK=1). Fail-open: offline or a non-git install never errors.

forge recall add | list | consolidate — cross-session memory

Durable facts, one per file, injected at the start of the next session by the recall-load guard. Secrets are refused.

$ forge recall add "db-port" "Postgres runs on 5433 here, not 5432"
  saved: db-port
$ forge recall list
  - db-port

forge cortex [status | why <symbol>] — self-correcting memory

Status of the lessons Cortex has learned from this repo's correction history.

$ forge cortex
Forge cortex — self-correcting project memory

  lessons: 0  (active 0 · candidate 0 · quarantined 0 · retired 0)

  (no active lessons yet — Cortex learns from corrections as you work)

  stored in .forge/lessons/ (git-committable, auditable)

forge cortex why <symbol> shows the lessons that would be injected when you touch it.

forge ledger — proof-carrying team memory

The convergent store behind cortex, recall, brain, and reuse: every stored unit is a content-addressed claim whose confidence (val) only independent oracles can move. Lives in .forge/ledger/ — git-committable, conflict-free merge.

$ forge ledger stats
Forge ledger — proof-carrying memory

  claims: 12  (tombstoned 1)
    lesson: 7
    fact: 4
    artifact: 1
  val: trusted 5 · uncertain 6 · dormant 1

  stored in .forge/ledger/ (git-committable, conflict-free merge)

forge ledger blame <id-prefix> is the accountability view — every mint, every oracle outcome, every retraction, and per-author trust:

$ forge ledger blame 3f2a
Forge ledger blame — lesson 3f2a91c04d7e

  val 0.82 (trust-weighted 0.84)
  minted  day 20640  by juber
  confirm   day 20641  test.run → npm-test#a41 by juber
  confirm   day 20643  human.accept → pr-118 by sam

  author trust (earned from oracle outcomes on their claims):
    0.93  juber

The rest of the surface, briefly: forge ledger merge <path> folds in any other ledger tree (a teammate's checkout, a worktree, a backup) — merged: 3 new claim(s), 5 new record(s) — conflict-free, in any order; query "<text>" ranks live claims by the paper's Eq. 3; show <id> prints one claim with its computed val; ratify <id> and retract <id> are the human oracle — a manual accept or revert that appends evidence and moves confidence; verify recomputes every content hash (CI-friendly, exit 1 on tampering); import back-fills legacy lessons/facts idempotently. Add --personal to target the per-user ledger beside the global recall store, --json for scripts.

Retiring the legacy stores. Since P1 the ledger has been the convergent WRITE store (every lesson/fact dual-writes into it) and reads are the merged view (legacy ∪ ledger). Set FORGE_LEDGER_ONLY=1 to finish the job: the legacy files (.forge/lessons/*.md, recall/brain fact files) stop being written and every read — cortex injection, routing, recall/brain — comes from the ledger alone. Run forge ledger import first (idempotent) so nothing local is stranded; the ledger is content-addressed and merges conflict-free, so it is a complete standalone store. Default off keeps the legacy files as the canonical local copy.

forge reuse — proof-carrying code cache

Verified code becomes an artifact claim keyed by a normalized task fingerprint; a lookup walks exact → near → adapt → miss. An artifact serves only while its proof holds — confidence above the 0.6 floor and every declared dependency still in the atlas.

$ forge reuse query "debounce user input before firing search"
  sim: minhash
  NEAR hit (similarity 0.87) — module at src/lib/debounce.js
    claim 9c41d2ab77e0 — `forge ledger blame 9c41d2ab` for its proof

$ forge reuse query "quantum blockchain"
  sim: minhash
  miss — nothing verified matches; generate, then `forge reuse mint` it

Mint after verification — without an evidence ref it sits at the 0.5 prior and does not serve:

$ forge reuse mint "debounce helper for search input" --file src/lib/debounce.js --ref npm-test#green
  minted: 9c41d2ab77e0 (1 export(s), 0 dep(s))
  serving: yes — verification evidence attached

forge reuse stats shows lookups by outcome + estimated tokens saved (from .forge/metrics.jsonl). Honest limit: the MinHash near-match is weak on very short specs — a few words hash to too few shingles to rank reliably. The optional embeddings tier below lifts exactly this.

FORGE_EMBED — the optional embeddings tier (ADR-0005)

MinHash is the always-working, zero-dependency default. Set FORGE_EMBED and forge reuse query + forge ledger query swap the lexical similarity for embedding cosine — no new flags, the env var is the switch, and every query prints which backend served it (sim: minhash / sim: embed(cmd) / sim: embed(http)):

  • FORGE_EMBED=cmd:<shell-command> — the universal escape hatch (any local model, any script): forge writes {"texts":[...]} to its stdin and reads {"vectors":[[...]]} from its stdout.
  • FORGE_EMBED=http:<url> (or a bare https:// URL) — OpenAI-compatible POST {input, model: $FORGE_EMBED_MODEL} with Authorization: Bearer $FORGE_EMBED_KEY (the key is passed via environment only — never logged, never in argv).

Thresholds move with the scale: near/adapt are cosine ≥ 0.85/0.7 instead of Jaccard's 0.8/0.6, because dense cosines have a much higher noise floor (unrelated sentences commonly score 0.4–0.6) while unrelated shingle sets sit near 0. Vectors are cached in .forge/embed-cache.jsonl (content-hash keyed, corrupt-tolerant, size-capped truncate-oldest) so repeated queries never re-pay the provider. Any provider failure — crash, timeout, garbage output — silently degrades to the MinHash path (FORGE_EMBED_TIMEOUT_MS caps the wait, default 15000). Per ADR-0005: dependencies stays empty; this tier is configuration, not a package.

forge context "<task>" — budgeted assembly + completeness gate

Derives the required-knowledge set for an edit (the target's definitions, hop-1 dependents, sibling tests, trusted lessons), covers it under a token budget with a compression ladder (full → head → pointer), and computes what's missing as a set difference — not a feeling.

$ forge context "change verifyToken in src/auth.js to reject short tokens"
Forge context — budgeted assembly + completeness gate

  budget: 1840/12000 tokens · required 4 · COMPLETE
    + def:src/auth.js [full] 620t
    + deps:verifyToken [head] 410t
    + tests:test/auth.test.js [full] 480t
    + fact:c1d2e3f4 [full] 330t

On an incomplete assembly it lists the missing items and derived clarifying questions ("the task names X but the repo doesn't define it — which file implements it?") and exits 1. --budget <tokens> tightens the window; a tight budget downgrades granularity instead of silently dropping coverage.

forge diagnose "<error>" — doom-loop check

Hashes a failure into a normalized signature (line numbers, addresses, timestamps, paths stripped) and counts recurrences. The 3rd identical hit is thrash: it mints a diagnosis claim into the team ledger and tells the agent to stop retrying.

$ forge diagnose "TypeError: Cannot read properties of undefined (reading 'user')" --file src/session.js
Forge diagnose — doom-loop check

  signature: a41f7c20be91 · seen 3× in the recent failure window
  diagnosis claim: 5d0e88c21f3a  (`forge ledger show 5d0e88c2`)

  STOP retrying this fix. State the diagnosis out loud (claim 5d0e88c2 — `forge ledger show 5d0e88c2`, add what you already tried to its triedFixes), then escalate ONE model tier with the diagnosis as the head of the new prompt. The escalation must carry the diagnosis — never just "try again, but more expensive".

Below the threshold it just records and says keep going. Advisory — halting the retry loop is the agent's move, not an exit code. Because the claim rides the team ledger, the same loop becomes a one-per-team event, not one-per-session.

forge imagine "<task>" — consequence simulation

The static half of the paper's Eq. 4: entities → blast radius → predicted breaks with confidence, plus the minimal test suite that covers them (weighted greedy set cover).

$ forge imagine "change verifyToken in src/auth.js to reject short tokens"
Forge imagine — consequence simulation (pre-action)

  targets: verifyToken, src/auth.js
  risk score: 2.30  (Σ confidence over predicted breaks)

  predicted breaks (3):
    0.90  src/auth.js
    0.70  src/login.js
    0.70  src/session.js

  minimal dry-run suite (1) — run these, in this order:
    - test/auth.test.js

  (measure it: re-run with --run — sandboxed worktree dry-run of HEAD)

Add --run to actually execute that suite in a sandboxed worktree of HEAD — the dry-run result lands as oracle evidence on the prediction. It refuses a dirty working tree (your uncommitted changes wouldn't be in the run); commit/stash first or pass --allow-dirty to knowingly measure the last commit. It also flags predicted breaks no test covers — the risk you can't dry-run away.

forge uicheck — deterministic UI checks

Five subcommands: three are static parsing — no LLM, no screenshots — and visual and interact optionally drive a real browser.

contrast <fg> <bg> — exact WCAG math, asserted, never guessed (bare forge uicheck <fg> <bg> still works):

$ forge uicheck contrast "#777" "#fff"
  contrast #777 on #fff: 4.48:1  →  fail (FAILS AA)

fingerprint <file...> [--mint] — the design feature vector of your UI files: palette (hue histogram), spacing base + on-scale fraction, fonts, radius/shadow levels. --mint stores it as a shared fingerprint ledger claim — the design gate's "home":

$ forge uicheck fingerprint src/components/*.jsx --mint
Forge uicheck fingerprint — the design feature vector

  palette:  9 color(s), hue bins [2 0 0 1 3 0 0 0 0 2 1 0]
  spacing:  4, 8, 16, 24 px — base 4, 96% on-scale
  type:     Inter, ui-monospace
  shape:    radii 6, 12 (2 level(s)) · 1 shadow level(s)

  minted fingerprint claim e7a90b12cd34 — the gate's "home"

design <file...> — the two-sided gate for generated UI (exit 1 on fail): slop distance to known generic templates must stay HIGH, conformance to your minted project fingerprint must stay LOW, plus scale-conformance checks (spacing on base, level caps). Failures are actionable per-feature edits, never a bare score. Honest limit: the fingerprint doesn't resolve CSS var() indirection yet — fully tokenized palettes are partially invisible to it.

visual <file-or-url> [--taste <name>] [--json] [--remote] — the Playwright visual loop: renders the page headless at two viewports (1280×800, 390×844), fingerprints the computed styles of every visible element — what the cascade, var() resolution, and runtime theming actually produced — and runs the exact same design gate as design (exit 1 on fail). Screenshots land in .forge/ui/ for human review. Playwright is an optional tier (ADR-0005): package.json stays dependency-free; without a browser runtime the command prints a "skipped (no browser runtime)" note and exits 0 — enable it with npm i -D playwright-core or point FORGE_PLAYWRIGHT at an existing install (e.g. FORGE_PLAYWRIGHT=/path/to/node_modules/playwright-core). Security default: http(s) targets must be loopback (localhost, 127.*, [::1]) — fetching arbitrary URLs is an exfiltration hazard, so non-local URLs are refused unless you pass --remote.

$ forge uicheck visual src/dash.html
Forge uicheck visual — rendered fingerprint + design gate

  rendered:      file:///…/src/dash.html (80 visible element style(s))
  screenshots:   .forge/ui/dash-1280x800.png, .forge/ui/dash-390x844.png
  slop distance: 0.516  (need ≥ 0.25 — farther from generic is better)
  ...
  ✓ spacing-scale: 100% of 6 spacing value(s) on the 4px base (ε 0.5px)

  ✓ PASS

interact <file-or-url> [--record] [--enforce] [--json] [--remote] — the Playwright interaction loop: where visual fingerprints what the page paints, interact checks what it does, headless under prefers-reduced-motion. Four checks: console-clean (no console errors on load), keyboard-reachable (Tab lands on an interactive control), focus-visible (the focused control shows a visible focus ring — WCAG 2.4.7), and reduced-motion (nothing animates when reduced motion is requested). The verdict is advisory by default — it is recorded through the ledger's weakest, cross-family-gated behavioral oracle, so a lone interaction run can never move a claim on its own (overview §4 honesty register). --record appends the verdict as evidence on your minted project fingerprint claim; --enforce (or FORGE_ENFORCE=1) turns a fail into a non-zero exit. Playwright and the loopback-only target guard are shared with visual (same optional tier, same --remote rule).

$ forge uicheck interact src/dash.html --record
Forge uicheck interact — browser interaction checks

  driven:        file:///…/src/dash.html (headless, prefers-reduced-motion)
  ✓ console-clean: no console errors on load
  ✓ keyboard-reachable: Tab reached <button>
  ✓ focus-visible: the focused control shows a visible focus indicator
  ✓ reduced-motion: no animations run under prefers-reduced-motion

  ✓ PASS  (advisory)
  recorded as behavioral evidence on design claim e7a90b12cd34

forge dash [--port N] — the local dashboard

A read-only lens over .forge/ — stdlib node:http, localhost-only, one self-contained HTML page (no CDN, no build step). Panels: Ledger (claims with val bars, contested claims, per-author trust), Cost/Cache (measured stage counters), and Impact (blast-radius explorer). Every claim row shows its forge ledger blame command.

$ forge dash
Forge dash — read-only lens on .forge/

  http://127.0.0.1:4242  (localhost-only · Ctrl-C to stop)

forge cost --stages — the measured cost report

Per-stage cost factors as pure arithmetic over .forge/metrics.jsonl. A stage with no events says no data — never a default; the composed figure is a lower bound over measured stages only.

$ forge cost --stages
Forge cost — measured stage factors (.forge/metrics.jsonl)

  stage     factor     events
  gate      6.2%       16
  cache     no data    0
  route     no data    0
  context   no data    0

  composed measured reduction: 6.2% (from: gate) — lower bound, measured stages only
  totals: 16 metric event(s) · ~0 tokens saved (stage self-estimates)

  context (not a local measurement): the paper measured a 62% routing saving on live tokens (paper §9)
  target (unmet until measured): the plan's composed target is ~90% (docs/plans/substrate-v2/05-cost-model.md)

Plain forge cost remains the per-day spend view via ccusage.

The rest

Command Answers
forge init Emit every tool's native config from one source.
forge sync Recompile source/ → each tool's files (idempotent).
forge doctor Health check: layers, install, drift, cortex.
forge docs check Docs↔code drift: commands, env vars, MCP tools, CHANGELOG reconciled against the code (CI-gated on the forge repo itself).
forge docs sync Diff-driven stale-docs sweep: UPDATED / STALE (file:line hits) / VERIFIED-UNAFFECTED per artifact (see the full section above).
forge catalog Start-Here index of every tool / crew / guard.
forge brain / forge remember Portable project memory inlined into AGENTS.md.
forge cost Real per-day spend (via ccusage) + the cost ceiling; --stages for the measured report.
forge scan <target> Vet a skill/MCP (SKILL.md/.mcp.json) for injection/RCE/exfil before install.
forge harden Wire gitleaks pre-commit + sandbox settings.
forge spec [init|lock|check] Spec-as-contract drift check.
forge brand Print the active brand token map.
forge lean "<task>" Scope-minimality footprint for a task — advisory (the Lean Path as a command).
forge taste [<style>] Pick one visual direction → writes DESIGN.md (the anti-slop reference uicheck design --taste reads).

Use it in a script

forge substrate "update verifyToken in src/auth.js" --json
{
  "okToProceed": false,
  "assumption": { "risk": "high", "shouldAsk": true, "questions": [""] },
  "route": { "tier": "simple", "model": { "name": "Haiku 4.5" } },
  "impact": { "impactedFiles": ["src/auth.js", "src/login.js"] },
  "verification": { "checklist": ["npm test", "npm run typecheck"] },
}

Gate your agent's next step on okToProceed; feed route.tier to your model picker; read impact.impactedFiles before editing. forge impact <target> --json is available too.


Auto-use inside an agent

The point of Forge is that you don't have to remember to run these checks.

Claude Code — fully ambient

Install the plugin and the substrate runs on every prompt via a UserPromptSubmit hook. It adds a short advisory only when something needs attention and never blocks:

Forge substrate — pre-action advisory (advisory, never blocks):
- Under-specified (high risk). Ask before editing:
    • What constraints must be respected: performance, dependencies, style, compatibility?
- Suggested model: Haiku 4.5 (simple); escalate only on a verifier failure.
- Predicted blast radius (2): login.js, auth.js. Review these before editing.
- Verify with: review impacted files before editing · run the narrowest affected test first

Nothing to wire — the plugin's hooks/hooks.json installs the SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, and Stop guards for you.

Three more ambient layers ride the same hooks:

Session rehydration (SessionStart). Besides lessons and the anchored goal, every session start injects the handoff snapshot (.forge/state.md), the last 10 commits, and any uncommitted changes — and records the session's git baseline (HEAD) that the completion gate and forge docs sync diff against. A --resume never moves the baseline.

Intent protocol cards (UserPromptSubmit). The prompt is classified by the same exemplar k-NN math as model routing (labeled examples + overlap similarity — extended by adding rows, including Hinglish ones, never by editing regexes). A work intent (bugfix / feature / refactor / release) injects a ~6-line protocol card once per run of that intent; questions get no ceremony, and below-confidence prompts get nothing. FORGE_INTENT=0 disables.

The completion gate (Stop). The deterministic floor under "done": when the session tries to finish, everything changed since the baseline (committed ∪ working tree) is classified against the same registries the atlas is built from — and if code moved but no doc or state artifact moved with it, the stop is blocked once, with the repair checklist as the reason (forge docs sync → update stale docs, forge handoff, forge decide; plus a CUSUM goal-drift alarm when the session's recorded drift series sustained). The decision table, first match wins:

# Condition Decision
1 stop_hook_active (already continuing from a block) allow
2 no session_id in the hook payload (per-session promises impossible) allow
3 not a git repo / git unusable allow (fail-open)
4 this session already blocked once — or the marker can't be persisted allow
5 FORGE_STOPGATE=0 allow (kill switch)
6 nothing changed (or only .forge/ internals / generated files) allow
7 docs changed — or .forge/state.md/decisions.md touched since session start allow
8 code changed ∧ no doc/state artifact moved block once + checklist
9 only tests / configs / other files changed allow
10 any internal error in the gate itself allow (fail-open)

"Changed" is session-scoped, not repo-scoped: files from commits made during the session (committer time ≥ session start) plus working-tree changes minus whatever was already dirty when the session began (snapshotted at SessionStart). Pre-existing dirt, commits reached by a branch switch or git pull, and vendor trees (node_modules/…) are never attributed to the agent — near-zero false blocks is the gate's credibility. Test-only sessions pass on purpose (a regression test owes no prose), and the state snapshot counts via its mtime against the baseline because .forge/ is gitignored. The gate can never loop (rows 1+4) and never brick a session (rows 3+10) — it costs at most one extra turn, exactly when that turn was owed.

Every other tool — a rule + MCP tools

forge init writes a rule into each tool's native config (AGENTS.md, .cursor/rules, GEMINI.md, …) telling the agent to run the check itself:

Before ambiguous, expensive, multi-file, or mutating work, run forge substrate "<task>" --json (or the MCP tool substrate_check). If okToProceed is false, ask the questions first; read impact.impactedFiles before editing.

…and exposes the substrate as 19 MCP tools any MCP-capable agent can call directly (the stdio server is launched with forge cortex-mcp, wired automatically via the emitted .mcp.json):

MCP tool Does
substrate_check full pre-action check
preflight_check assumption / info-gap check
assumption_gate ask/proceed + questions
predict_impact blast radius (code and the docs that reference it)
route_task model recommendation
scope_files independent vs. coupled
cortex_lessons learned lessons for given files/symbols
cortex_status memory lifecycle summary
forge_brain durable project facts
forge_ledger_query ranked retrieval over the PCM ledger
forge_remember write: add a durable project fact
forge_ledger_ratify write: human-ratify a claim into a decision
forge_ledger_retract write: tombstone a claim
forge_diagnose doom-loop failure check
forge_doctor health check
forge_provider_status provider detection + gateway reachability
forge_cost spend + stage factors
forge_dash_data / forge_dash_summary dashboard data feeds

Forge never pretends it can force a hook into a tool that has none — ambient on Claude Code, agent-invoked everywhere else.


Reading substrate output

Field What it means Do this
proceed: ASK FIRST / okToProceed: false task is under-specified ask the clarify questions, don't guess
route cheapest capable model start there; escalate only if a verifier fails
impact predicted blast radius read these files before editing
scope independent vs. coupled work split independent groups into separate sessions
memory past Cortex lessons for this area context, not law — tests override it
verify how to prove it works run it, show the output, then say "done"

Recipes

Before a risky refactor. forge impact <symbol> (or forge substrate "<task>") to see every dependent, including the ones the ticket didn't name; read them; edit the shared function once at the root, not each caller.

Splitting a big change. forge scope <files…> → if it reports more than one independent group, do a session per group; keep coupled files together.

Controlling cost. forge route "<task>" before you pick a model; forge cost for real spend; the cost-budget guard warns when a day exceeds FORGE_COST_CEILING.

Teaching the repo a fact. forge recall add "<name>" "<fact>" — it's injected next session. For learned-from-mistakes memory, just work: Cortex captures recurring corrections on its own.

UI work. forge uicheck contrast <fg> <bg> for exact contrast; forge uicheck design <files> --taste <style> as the anti-slop gate (mint the project fingerprint first: forge uicheck fingerprint <files> --mint); the ui-workflow and taste tools for the rest.


Extending Forge

Everything is small, single-sourced, and testable. Change one piece, run npm test.

Add or change a rule

Edit source/rules.json, then forge sync. The rule is re-emitted into every tool's native file with a content-hash header.

Add a tool (skill)

Create global/tools/<name>/SKILL.md with name + description frontmatter. It's picked up by the plugin and by forge catalog.

Add a guard (enforced hook)

Create global/guards/<name>.sh (source _guardlib.sh for the shared fields + the re-entrancy lock), then wire it in global/settings.template.json and hooks/hooks.json. Guards must be idempotent and fail-safe — worst case they do nothing.

Add a crew member (sub-agent)

Create global/crew/<name>.md with frontmatter. It installs into ~/.claude/agents/.

Tune the cognitive substrate

To change… Edit
how often it asks source/substrate.jsondefaults.askThreshold (0.6)
blast-radius sensitivity source/substrate.jsondefaults.impactThreshold (0.1)
a routing outcome src/route.js → add a labeled row to EXEMPLARS (data, not weights); constants in RUBRIC
model tiers / prices src/model_tiers.js
an assumption question src/preflight.jsDIMENSIONS[]
the verify checklist src/substrate.jsverificationChecklist()
when the ambient hook speaks src/substrate.jssubstrateContext()
the cross-tool rule wording source/rules.jsonsubstrate section (then forge init)
opt-in LLM adjudication FORGE_LLM=1 (+ FORGE_LLM_AMBIENT=1 for the hook); config in source/substrate.jsonllm
opt-in enforcing gate (halt, don't just advise) FORGE_ENFORCE=1 — blocks a no-anchor prompt or a very-large-blast action; src/substrate.jsenforceDecision(). Off by default.
verify test timeout FORGE_VERIFY_TIMEOUT_MS (default 600000)

Opt into LLM-assisted judgments

By default every judgment is a deterministic rubric. FORGE_LLM=1 adds a thin proposer layer (src/adjudicate.js) — one shared claude -p primitive used by M2/M1/impact/M4. The model never decides: each proposal is verified against the rubric, the code graph, or a grep before it can move a verdict. The reconcile is bidirectional but rail-guarded by default — a verified reading can clear a false ask or route a task down a tier, not only add caution, but never past a hard floor (no concrete anchor, unresolved repo entities, or a strong-signal routing floor). Impact edges must be real + grep-confirmed; goal-drift moves off→on only. Any failure falls back to the deterministic path, so the flag is safe to leave off or on. --json exposes llm.provenance per faculty (llm-cleared / llm-tightened / llm-raised / llm-lowered / …). Set llm.bidirectional: false in source/substrate.json for the conservative tighten-/raise-only mode. Each faculty pairs a pure *LLM proposer with a reconcile step — extend by adding both, never by trusting the model's answer directly.

Support a new tool

Add an emitter module in src/emit/<tool>.js (mirror an existing one like src/emit/cursor.js), then register it in src/sync.js. A golden-file test in test/sync.test.js keeps it honest.

Rebrand

Edit the brand token in brand.json, the bin key in package.json, and name in .claude-plugin/plugin.json. The whole CLI, banner, and emitted headers follow.

Environment variables

The complete env contract (forge docs check keeps this table honest — a variable the code reads but this table misses fails CI on the forge repo):

Variable Does
ANTHROPIC_API_KEY direct Anthropic auth (also used by gateways when set)
ANTHROPIC_AUTH_TOKEN gateway/proxy Bearer credential — recognized everywhere the API key is
ANTHROPIC_BASE_URL custom API endpoint; gateway-looking URLs auto-classify as LiteLLM
ANTHROPIC_MODEL / FORGE_MODEL pin one model — bypasses tier routing entirely
LITELLM_BASE_URL / LITELLM_API_KEY hosted LiteLLM gateway endpoint + key (highest detection priority)
OPENROUTER_API_KEY OpenRouter provider
OPENAI_API_KEY OpenAI provider (OpenAI-compatible chat/completions); zero-config fallback after Anthropic
GEMINI_API_KEY / GOOGLE_API_KEY Google Gemini provider via its OpenAI-compatible endpoint; zero-config fallback after Anthropic
FORGE_LLM 1 enables the LLM proposer layer (off = fully deterministic)
FORGE_LLM_AMBIENT 1 lets the ambient hook use the proposer too
FORGE_LLM_HTTP 1 forces direct HTTP (Anthropic Messages or OpenAI-compatible, per the resolved provider) instead of the claude CLI; automatic when the CLI is absent
FORGE_ENFORCE 1 turns the substrate advisory into a hard block on the strongest signals
FORGE_AUTOSYNC 0 disables the Stop-hook AGENTS.md auto-repair
FORGE_LEDGER_ONLY 1 retires the legacy stores — stop writing lessons/*.md + recall/brain fact files; the ledger is the only store (reads materialize from it)
FORGE_EMBED / FORGE_EMBED_MODEL / FORGE_EMBED_TIMEOUT_MS optional embeddings tier (ADR-0005)
FORGE_HOME override ~/.forge (recall store location)
FORGE_ROOT repo root override for the MCP server
FORGE_AUTHOR identity stamped on ledger provenance (defaults to git identity)
FORGE_COST_CEILING daily spend (USD) the cost-budget guard warns at (default 10)
FORGE_LOOP_THRESHOLD identical tool calls before the doom-loop guard speaks (default 4)
FORGE_LEAN_THRESHOLD lines-per-task-word ratio the lean guard nudges at
FORGE_VERIFY_TIMEOUT_MS verify test-run timeout (default 600000)
FORGE_SKILLGATE_NOEXTERNAL 1 skips the external scanner in forge scan (heuristic only)
ENABLE_CORTEX_DISTILL 1 distills new lessons into prose via a cheap model call
FORGE_STOPGATE 0 disables the Stop completion gate (code-without-docs block)
FORGE_INTENT 0 disables intent protocol cards on prompts
FORGE_VERBOSE 1 restores the Forge <cmd> title line on command output (also --verbose)
FORGE_NO_UPDATE_CHECK 1 silences the forge doctor update notice
FORGE_DEBUG 1 writes fail-safe error details to stderr instead of swallowing them

Honest limits

  • Guards reduce, don't eliminate the "ignored my rules" problem — semantic rules still live in prose.
  • recall / cortex are file + prompt memory, not weight-level learning.
  • The atlas graph is regex-approximate — conservative, not a sound call graph; dynamic dispatch and generated code can be missed.
  • forge reuse's MinHash near-match is weak on very short specs — a few words hash to too few shingles to rank reliably; write a sentence, not a keyword — or configure the optional FORGE_EMBED embeddings tier, which replaces exactly this term.
  • The UI fingerprint doesn't resolve CSS var() indirection yet — a fully tokenized palette is partially invisible to the design gate.
  • forge cost --stages reports measured stages only — a stage with no events says "no data", never a default; the composed figure is a lower bound and ~90 % is a labeled target, not a claim.
  • The substrate's rubrics are heuristic, not benchmarked — judge them after real use. What's asserted (safe to gate on): repo grounding, graph traversal, scope decomposition, routing arithmetic, and the test/build commands. Everything else is advisory. Tests and human corrections always win.