The CLI
The forge executable handles local file mechanics and artifact previews. It never selects a phase, approves meaning, or judges a candidate.
There are two forges and it's worth keeping them straight. Forge with a
capital F is the skill you invoke in agent chat, and it's the thing that selects
phases and exercises professional judgment. The lowercase forge executable runs
in a shell and does deterministic bookkeeping.
Its own help says so:
Usage: forge [options] [command]
Local file mechanics and artifact previews. Invoke the Forge agent skill for
semantic Explore/Spec/Plan/Build/Review/Acceptance/Ship.So there's no forge plan and no forge build. A shell command never selects a
phase, grants authority, approves meaning, or advances delivery, and CLI success
establishes only the mechanical claim that operation names.
You need the executable to initialize a managed loop and to create or validate its documents. Protected decision recording, Spec application, prepared memory changes, and knowledge operations also go through it, and direct shell writes are not a substitute for any of them.
Getting it
The installer writes it to ~/.local/bin/forge, so if you've
installed Forge you already have it.
The compiled executable embeds its document templates and runtime, so running it
doesn't need Bun, Python, or a source checkout. It needs Git for the GitHub fetch
inside setup and for candidate; setup --pack needs neither. Locking uses
POSIX file facilities.
Treat --help from your installed version as authoritative over this page.
Commands
serve Serve an artifact directory on localhost until interrupted
setup Install Forge skills into this repository from GitHub or --pack
init Initialize a loop; this is not approval
docs Scaffold and validate identified documents
decision Record an authorized human decision in the canonical bundle
candidate Fingerprint current candidate including dirty files
memory Validate or apply prepared canonical files
kb Query or maintain canonical local knowledgeserve
forge serve <directory> [--port <number>] [--json]Runs a foreground static server bound to 127.0.0.1. --port defaults to 0,
which picks an available port; an explicit occupied port fails rather than
silently moving. --json prints one readiness record with root, url, port,
and pid after the socket binds. Read the printed URL instead of guessing.
The directory is explicit and relative to your working directory, and it doesn't
use --repo or need a Git checkout. The selected directory is the entire served
scope: its index.html opens at the root URL, nested directories serve their own
index and redirect to a trailing slash so relative assets resolve, and files are
read fresh on reload with caching disabled. HTML, CSS, JavaScript, images, PDFs,
and Markdown keep their file MIME types, so Markdown is served as a file rather
than converted to HTML.
Missing files and directories without an index return 404. There's no directory listing and no SPA fallback, and dotfiles and symlinks under the root aren't served. No daemon, server registry, transpilation, Vite installation, or browser launch is involved, and no live-reload script gets injected. Use your project's existing build pipeline for framework source, then serve the output.
Ctrl+C or SIGTERM closes the server and releases the port. Readiness proves a bound local server and nothing about what's rendered in it.
setup
forge setup [--repo <path>] [--tools <list>] [--pack <path>]Installs the skill pack into a project. By default it fetches the latest default
branch of github.com/brightstack/forge into a temp directory, copies the pack
out, and deletes the temp tree, so an unreachable GitHub fails with a clear error
rather than a half-install. --pack points at a local package root instead and
skips the network entirely.
--tools defaults to agents, which is Cursor and Codex. The other two are
claude and cursor, and a comma-separated list installs into several hosts at
once. An unknown tool name fails. Setup writes only Forge-owned names and merges
into them, so your other skills survive it.
Skills live on GitHub or in --pack, never inside the binary, which is why
setup exists as a separate step from installing the executable. Run it from the
project directory rather than from ~/.local/bin.
init
forge init <loop_id> --repo <path> --title <title>Creates .forge/loops/<loop-id>/ with four active records — index.md,
decisions.md, log.md and build/log.md — so they can carry current state.
--repo defaults to ..
Active status means the record is in use. It doesn't accept Spec meaning, prove a candidate, or authorize a phase, which is why the help text says so out loud.
docs
forge docs create <kind> <path> --repo <repo> --title <title> [--code <code>] [--body-file <file>]
forge docs update <path> --repo <repo> [--body-file <file>] [--set <key>=<json-or-string>]
forge docs validate [paths...] --repo <repo>
forge docs append <path> --repo <repo> --heading <heading> --body-file <file><kind> is a fixed list, not free text: issue, bug, spec-change,
standing-spec, plan, build-log, product, design, visual, tech,
work, acceptance, review, ship, concept, process, interface,
index, log, decisions, kb-decision, kb-index, kb-log. Anything else is
rejected outright.
Creation assigns id (a UUID), a readable code, an immutable type, plus
title, status, createdAt, and updatedAt. Codes use letters and numbers
separated by -, _, or .. Creation and identity fields stay fixed; --set
handles other metadata.
The usual rhythm is scaffold, edit the ordinary Markdown body with your normal file
tools or agent, then validate. --body-file takes body-only Markdown, frontmatter
is preserved through ordinary body edits, a missing top-level heading is supplied
from --title on creation, an existing heading is kept, and empty bodies are
rejected. Updates still need a descriptive heading.
docs validate with no paths checks the corpus listed in .forge/identities.json,
minus docs/knowledge/, which belongs to kb verify and is rejected if you name
it explicitly. Pass paths to check a specific draft.
The frontmatter reader is deliberately scoped rather than a general YAML implementation. Unknown nested or block metadata is retained verbatim during body edits, and it isn't interpreted as authority or schema.
decision
forge decision record [loop_decisions_path] --repo <repo> \
--authorization-file <file> --body-file <file> \
[--title <title>] [--supersedes <Dn-or-legacy-id>]Records an authorized human decision in the canonical decisions bundle, assigns the
next readable code (D1, D2, and so on), updates the numeric decisions index, and
retains a memory receipt. Supply the loop decisions path to also record the
canonical reference there, or omit it for standalone recording. --supersedes
creates a new record and preserves the prior one.
The authorization file is JSON with actor: "human", a local transcript path or
URL in source, an actual quote, a bounded scope, and an ISO date. When
source is a local path, the operation checks that the quote is really in it; when
it's a URL the quote is embedded unchecked. Either way it does not authenticate the
human. Generic document updates can't create or overwrite decision authority.
candidate
forge candidate --repo <repo> [--path <path>...] [--expect <identity>]Fingerprints the current candidate, including dirty files: tracked, untracked,
deleted, mode, and symlink state for the selected paths. .forge loop mechanics are
excluded by default, so writing a verdict doesn't invalidate the candidate it's
about. Gitignored content is excluded too, because the walk is
git ls-files --cached --others --exclude-standard; name such a path with
--path to pull it back in. Include changed accepted Spec or decision paths explicitly with repeated
--path, or pin their exact accepted revision separately in the packet.
memory
forge memory verify <manifest> --repo <repo>
forge memory apply <manifest> --repo <repo>Validates or applies prepared canonical files. Canonical docs/specs and
docs/knowledge are protected from generic document writes, so the flow is to
prepare drafts under a loop or another ordinary path, pin the proposed hashes and
the independently retained authority inputs, then verify and apply after approval.
Forge spec apply <change> in chat uses exactly these two commands with an
application: spec manifest. There's no executable Spec apply or merge alias.
Generic memory jobs omit that discriminator and stay compatible.
Spec normally records approved meaning as a proposal. An explicit Spec apply
installs it when requested; Forge finish can invoke the same guarded operation
at actual work completion. Finish is an agent operation, not a CLI command or a
sixth phase.
A document-only receipt establishes the checked mutation. It doesn't establish implementation, Review, Acceptance, or Ship.
kb
forge kb scaffold <path> --repo <repo> --type <type> --title <title> --code <code> [--body-file <file>]
forge kb ask <query> --repo <repo> [--scope <kind>...]
forge kb verify --repo <repo> [--scope <kind>...]
forge kb history --repo <repo>
forge kb add|update|remove <manifest> --repo <repo>kb ask retrieves authority from the canonical local corpus and returns cited
sources with explicit gaps. kb verify reports OKF structure and health findings.
Neither proves that a candidate complies with anything.
kb history is receipt-derived: it summarizes successful applied memory operations
from .forge/memory/, including Spec changes, knowledge and decision maintenance,
and evidenced delivery. A manual log, a failed operation, or a structural check
doesn't become shipped behavior, and malformed or incomplete receipts are reported
as issues.
Create only knowledge topics the work actually earned. OKF concept types stay open
and no empty corpus is required. KB write verbs reject empty changes, so an
integrated delivery with no canonical document delta gets recorded through memory verify and memory apply with an empty changes list plus human authorization and
nonempty source, evidence, and acceptance references. That receipt doesn't imply
deployment or publication.
A worked sequence
forge setup --repo /path/to/project
forge init sidebar-label --repo /path/to/project --title "Clarify sidebar labels"
forge docs create issue .forge/loops/sidebar-label/spec/issues/label.md \
--repo /path/to/project --title "Identify the sidebar action"
forge docs validate --repo /path/to/project
forge candidate --repo /path/to/project --path src
forge kb scaffold .forge/prepared/sidebar.md --repo /path/to/project \
--type "Domain Concept" --title "Sidebar" --code "KB-SIDEBAR"
forge kb ask "sidebar" --repo /path/to/project
forge kb verify --repo /path/to/project