跳到正文
cccskills
免费在 GitHub 公开

forge

Run Forge, which takes a story from approval to a merged pull request. Use when the human asks what to do next, wants to plan a story, start a task or fix, build, close, save a spec or decision, add to the roadmap, check the setup, or see the board.

中文简介正在准备中。当前显示原文说明。

knacklabs/symphony-forge星标 10(GitHub 10 + cccskills 0)
集成工具GitHubPostgreSQL

实测评分 未实测

查看安装方法

包含的文件(4)

  • SKILL.md165.1 KB
  • fde.md8.8 KB
  • migrate-skill.md4.6 KB
  • standards.md24.3 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

<!-- Generated by forge sync. -->

Forge

Start with forge next. It says where things stand and gives the exact next command.

Mainline Deploy is available to install through the organization's plugin catalog. Do not assume that the plugin and MCP are installed or that the user is signed in. Use Mainline Deploy only when the user explicitly requests deployment. First check whether it is installed in the current host. If it is missing, use the organization's plugin catalog and the host's installation flow. Do not invent an installation command or marketplace address. Then follow the Mainline Deploy skill. Check OAuth sign-in and have the user complete it if needed. Wait for successful authentication before deployment. Installation alone does not mean the user is signed in. Deploy only the requested app to the requested environment. A build, fix, merge, ship/land request, successful test or review, demo preparation, or Forge next step does not by itself authorize deployment. Do not install Mainline or start OAuth sign-in for those requests. An explicit request such as "deploy this to staging" or "publish this on Mainline" needs no separate routine deployment approval.

Our deploy platform reads services.toml at the repo root: one [[service]] per service, with name, dockerfile, port, health, expose (main, subdomain or internal) and, when needed, subdomain and command, and mcp with the path of the service's MCP server; deploy.md in Forge's conventions has the details. Close refuses a list that names a Dockerfile the branch does not have or does not have exactly one main service. When a client repo has a Dockerfile and no services.toml, write it before the first deployment the owner asks for: one service per Dockerfile the app runs, the port from its EXPOSE line and the health path from the code. Show it to the owner and ask with options: confirm it, or change a service's exposure. Then merge it in a fix. The owner picks the main subdomain on the platform, so the list does not hold it. In a starter app, the API service has mcp = "/mcp".

The first deployment that the owner asks for in a client repo is its deploy setup, and that request is the explicit request above. Follow the installed Mainline Deploy skill in this order, and skip a step that is already done:

  1. Have the owner sign in in the browser with Google or GitHub, and wait until the sign-in succeeds.
  2. Connect the client GitHub repository.
  3. Read services.toml. When the repo has none, write it as the paragraph above says and get the owner's confirmation.
  4. Set the main subdomain that the owner picks.
  5. Set each variable that .env.example names, with the values the owner gives, except the ones that the platform provides, such as DATABASE_URL.
  6. Deploy dev, and wait until dev reports healthy. Always deploy dev first.

After the setup, do not deploy. Mainline deploys dev at each merge to the default branch and a preview for each pull request. These deploys are Mainline's, not an agent's, and need no request. Production changes only when a person presses Promote in Mainline. Never deploy or promote production, also when the owner asks: tell them to press Promote in Mainline. When the owner asks for another deployment after the setup, tell them that Mainline deploys dev at the next merge.

The human saysRun
"What now?"forge next
"Show me progress"forge board
"I have an idea", "Fix this" or "Plan this"forge start "<their words>"
"Read the plan cold"forge read <KEY>, or forge read <slug> for a spec or decision
"I've amended it"forge read <KEY> again, for the next round
"Start this task"forge task start <KEY>/<TASK>
"Give this story or part to a developer"forge assign <KEY or KEY/TASK> <login>
"Name this fix"forge fix start "<why>" --done "<done when>" --slug <name>
"Change this fix's Done-when"forge fix amend <fix> --done "<done when>" --because "<why>"
"Add to this fix's Done-when"forge fix amend <fix> --append "<text>" --because "<why>"
"This fix is too big"forge story new <KEY> --from-fix <fix>
"Make it a story" (a fix)Add the key to the fix's roadmap and commit it, then forge story new <KEY> --from-fix <fix>
"Make it a fix" or "Just fix it" (a plan in planning)forge fix start "<its Problem>" --done "<its first Done-when>", then forge story drop <KEY> --because "Moved to the fix <fix>"
"Do it later" (a fix)forge fix drop <fix> --because "The owner wants it later", then forge start "Do this later: <its why> (kept on fix <fix>)". fix drop --undo brings it back
"Do it now" (an idea-list entry)forge start "<the owner's new words>: <the entry's idea without its deferral words>", for example forge start "Do it now: add dark mode". Then remove the entry from the main checkout's file while it is waiting, or else in the new item's branch
"Let this fix go over the limit"forge fix allow-large "<reason>"
"Build it"forge work <item>
"Tell the worker this round"forge work <item> --note "<text>"
"Ask about this code"Hand the question to the session's explorer subagent role
"Close it" or "Is it ready?"forge close <item>
"Land it"forge land <item>, run in the background and watched like forge work
"Land everything that's ready"forge land --all, run in the background; never a hand-made merge lane
"Merge this ready item"forge merge <item> when the default branch allows agent merges
"Let the agent merge"The owner runs forge merge enable in a terminal outside Claude Code or Codex; never you
"What should we build?" or "Find the real problem"Discovery, below
"Is it worth building?"forge spec payback --build-days <days> --day-rate <rate> plus a value group
"Save this spec"forge spec save <slug>
"The spec is confirmed"forge spec confirm <slug> --by "<name>"
"Record the check-back result"In a fix: forge spec measure <slug> --result "<result>"
"Record a decision"forge decision new <slug>
"The decision is accepted"forge decision accept <slug> --by "<name>"
"What does the client still need to decide?"forge decision open
"Record the client's answers"In a fix: forge decision answer --from <file> --by "<name>"
"Add it to the roadmap"forge roadmap add <spec>
"The new spec replaces this roadmap item"In a fix: forge roadmap retire <KEY> --by <spec>
"Change a finished story's outcome"On an existing work branch: forge story done <KEY> "<outcome>"
"Is my setup healthy?" or "Fix my setup"forge doctor, then forge doctor --fix for what it can repair. On the default branch, repairs to Forge's files need a clean checkout at origin/<default> and use a dated fix forge-files-<YYYYMMDD-HHMM>: forge close <name>, then merge it like any other. An existing fix/forge-files-* branch with no merged or closed pull request blocks another repair, record or not; follow doctor's finish-or-remove step. Finished-work cleanup skips only the open doctor fix. If a repair already used this minute's name, run forge doctor --fix in the next minute. A file it holds back as changed by hand: move that change out of the file, then forge doctor --fix again. In AGENTS.md, only hand edits inside the forge:begin and forge:end lines hold the file; your rules outside them stay as written when doctor refreshes Forge's block
"Set up a new repo"Before forge init, answer "Does this repo run GitHub Actions?" from the repo's settings: gh api repos/{owner}/{repo}/actions/permissions gives enabled. Ask the human only if the settings do not answer it. Pass --actions yes or --actions no; yes uses CI checks, while no selects laptop tests with checks = []. Then use forge init --starter for a new React app, forge init --starter python for a new app whose owner asks for a Python backend, or forge init for another stack. Propose a fast_test as in step 9 of Adopt a live app, below
"Bring our live app into Forge"Adopt a live app, below
"Change the test command"Ask, then in a fix: edit forge.toml (never its merge setting), forge close <fix>
"Run everything in Claude"Ask, then in a fix: set tools = "claude", workers = "claude" in forge.toml, then forge sync, forge close <fix>
"Run everything in Codex"Ask, then in a fix: set tools = "codex", workers = "codex" in forge.toml, then forge sync, forge close <fix>
"Use both"Ask, then in a fix: set tools = "both" in forge.toml, forge close <fix>
"Change who builds just for me"Edit forge.local.toml beside forge.toml in the main checkout (not a worktree): only tools, workers and [models.<kind>] tables. Git ignores it, so no fix; every worktree of that clone uses it. forge next and forge doctor name what it sets
"Close takes too long running every test"In a local-test repo, ask, then in a fix: set fast_test in forge.toml, a command close runs instead of test, with {base} replaced by the merge base with the default branch (for example npx vitest run --changed {base} plus lint). In a CI-test repo, its pull request check runs related tests; the nightly full run covers the rest. Then forge close <fix>
"Upgrade Forge"Ask which release, then forge upgrade <release>; Upgrade Forge, below

forge doctor lists started fixes with no work commits after more than seven days. Each row gives forge fix drop <fix> --because "<reason>". Only drop a fix after the owner agrees. forge doctor --fix does not drop these fixes. New and upgraded client repos get this check from the installed Forge command.

Send every new idea through forge start "<their words>". Never ask the human whether it is a story or a fix. When the answer is a fix, run its forge land <fix> in the background and watch it.

The human approves stories and chooses between options. In a client repo before the default branch has an accepted sign-off, the agent runs forge merge <item> once forge close says Ready. After sign-off, the default branch's forge.toml merge setting applies: the human merges by default (merge = "human"), or the agent runs forge merge <item> for merge = "agent". Forge's own repo follows its setting throughout. Never run gh pr merge; the agent merges only through forge merge. Ask one question at a time: a decision gets options with your recommendation first, a question of fact gets neutral choices.

The journey

Every client repo moves through seven steps. forge next names the current step in its first line.

  1. Ideas and client notes. The person gives you the client notes or an idea. Put each note in docs/context/ and read them all before you ask anything. You can say: "Here are the client notes" or "I have an idea: <idea>".
  2. Brief. You write docs/product/BRIEF.md from the notes, and ask only about a point that changes a screen, a field or a flow. You can say: "The brief looks right" or "Change this in the brief: <change>".
  3. Canvas design. You make the screens on a design canvas, which the client can click through in Play mode, and the owner or FDE approves the canvas. You can say: "Show me the screens" or "Change this screen: <change>".
  4. Sign-off. The client's named person approves the canvas and the answers, and the owner chooses Build now or Later. You can say: "The client approved it; build now" or "The client approved it; build it later".
  5. Build. You plan stories and fixes, build each one with its tests and a review, and merge it. You can say: "Build this next: <idea>" or "What is ready to merge?".
  6. Deploy on Mainline. When the person asks, you set up Mainline and deploy dev. A person presses Promote in Mainline for production. You can say: "Deploy this on Mainline".
  7. Run and improve. People use the app, and each new request or problem goes back to step 5. You can say: "Users asked for <request>" or "This is broken: <problem>".

The cold read loop. A reader who did not write the plan reads it cold and lists its gaps. You fix the plan, and the reader reads it again. After two rounds that still have open notes, Forge stops and the owner chooses: accept, read again, or split. It exists because a gap found in the plan costs minutes, but the same gap found in the code costs a build round.

The build and review loop. A worker builds the part and its tests, then a separate review checks the change. A serious problem goes back to the worker for another round. A task gets two review rounds and a fix gets one. At the limit Forge stops and asks the owner, but a P0 problem keeps the loop going. It exists because a reviewer sees problems that the worker cannot see in its own work.

The CI checks. GitHub runs the repo's checks on the pull request. Close waits while they are queued or running, and a red check goes back to the worker. They exist because the checks run the tests on a clean machine, the same way for every change.

forge next adds one line under each running item that says what it waits on, why and for how long. Give that line to the person when they ask why something takes time.

After forge init in a new repo, tell the person the seven steps in one line each, then ask for the first input: the client notes or the idea.

When a person asks what they can do, or seems unsure, run forge next, name the current step from its first line, and show that step's "You can say" phrases. Name no commands.

When a Forge command refuses, tell the person in plain words what Forge stopped, why, and the fix that it names. Then do the fix, or ask the choice. When the refusal looks like a Forge fault (a wrong or confusing message, a refusal of a correct step, a crash, or a success that did nothing), say so and offer to file a Forge bug as Reporting a Forge bug, below, says. File it when the person agrees.

Before sign-off FORGE-SIGNOFF-1 adds Build now and Later; until then, record "build it later" with forge start "Do this later: <the first story's idea>".

Save decisions and ideas

Understand each human decision, direction and idea. Save each in the same turn; never leave it only in chat. Grill new directions one question at a time, with your recommendation first. Save the answer before moving to the next question.

  • Put a choice about an item in its plan's Notes or the recorded choice: forge close <item> --resolve <more|split|accept> --reason "<human's choice>".
  • Save a standing rule or direction with forge decision new <slug>. Batch decision docs into the next fix; do not open one pull request per decision. For a large decision, run forge read <decision-slug> before acceptance. Amend the decision and answer findings between rounds, as for a spec. Notes stay beside the numbered decision record. Specs take precedence when slugs match; use forge read <NNNN-slug> to select the decision, or when several decisions share a slug. A cold read does not accept the decision.
  • In docs/specs/, state each rule once, on an R-<n>: line under ## Behaviour. Validation (VAL), UAT and acceptance (AC) lines name the rule that they check by its ID, for example VAL-11 checks R-13, and do not say the rule again. A decision that changes a rule names its ID. forge decision accept then lists every spec line that names that ID: in the same change, before close, update each line that no longer fits. On upgrade, the first forge sync lists the old spec lines that seem to repeat a rule, with a fix to start. Show the list to the owner and ask with options: rewrite those specs now in that fix, or later.
  • forge start keeps plans/ideas.md, a short dated list, when the owner says later, and carries waiting entries into the next fix or story. When you, a planner or a worker think of something useful that nothing needs, add one line - <YYYY-MM-DD>: <the idea> to plans/ideas.md in the branch in hand and build nothing for it. Remove an entry when the owner says to do it now, as the command table explains.

Merges and settings

If the default branch moved after close, forge merge compares its new changed files with the item's changed files. It merges the default branch locally to check the combined change. It runs fast_test, or test when none is set, in the one test lane with {base} as the new merge base. It merges the pull request only after the tests pass. A failure names the failed tests and gives forge work <item> as the next step. This is always on, with no setting. With no default-branch move, merge runs no extra tests. New repos get this rule at init; existing repos get it on upgrade and sync. If the file sets do not overlap, Forge restores the original head and merges without a push or another check wait. If the file sets overlap, Forge keeps the local merge, reuses the existing review and commits its coverage of the combined change. It pushes that commit, waits for its required checks, then merges. The nightly full run on the default branch covers the rest. With no retest, the usual CI wait applies. Before the retest, Forge refuses uncommitted changes and names their files. After tests and checks, Forge fetches the default branch again. If it moved again, Forge refuses the merge and gives forge merge <item> as the next step.

Forge leaves a pull request that changes its own merge setting to the repo owner. It compares the item's merge base with its head, so an older branch can merge after the owner enables agent merges. An already-merged pull request skips that check when tidying. Cleanup removes unchanged generated git hook shims and Husky's ignored rebuilt hooks; uncommitted work and later local commits keep the worktree in place. When another program holds the worktree open, the merge still finishes and keeps the local branch; close that program, then delete the folder the merge names.

When forge merge merges a story's last task, the same squash merge records the story as done. Give forge merge <KEY>/<TASK> --outcome "<outcome>" to name what it achieved; without it Forge uses the story's title. The board and forge next read that record from git, so no outcome fix, extra pull request, review or CI run is needed. Stories already marked done keep their outcome. Use forge story done only to correct an outcome later, on an existing work branch that carries the correction through its own pull request; the command opens no branch or pull request. When next says a remotely merged story needs its outcome, fetch origin before recording it.

Give each question one line of context and a header of 12 characters or less. Use 1-5 word options that say what happens, with the recommended one first for decisions. Use no IDs, paths or slugs in questions; write for the human's choice.

Write text that people read with the simplified-technical-english skill: a story's What changes for you, Why and Done when, a fix's why and Done when, pull request summaries, questions to the human, and board and forge next lines. Briefs, guides and code stay as they are.

The human never edits forge.toml; you keep it. When a setting must change, ask first with options, then make the change yourself in a fix: forge fix start, the edit, then forge close. A developer's own tools, workers and [models.<kind>] choices go in the git-ignored forge.local.toml instead, which you edit directly when they ask; Forge refuses any other key there. runner selects the GitHub Actions runner label for every job in the generated forge.yml; it defaults to "ubuntu-latest". For self-hosted Linux runners, initialise with forge init --runner self-hosted, or set runner = "self-hosted" (or a custom label) in an existing repo's fix, then run forge sync. New repos get it at init; earlier adopted repos get it after upgrading Forge and syncing. The jobs set up uv and Python, and install Node for Node tests, respecting version files or engines with Node 22 as the fallback. Close and land keep waiting while checks are queued. The wait line shows minutes observed queued during this wait: shared runners may be busy or no runner may match the runner setting. Only forge doctor reports a likely missing runner, when a current check has been queued at least five minutes and no matching job started in its sample of this repo's runs created in the last seven days. It reads at most 100 newest runs, skips successful runs, and reads one page of latest jobs per remaining run, without earlier attempts or older runs. Make a matching runner available or correct the setting; run forge sync only if you change the runner setting. Keep the required checks enabled. New and upgraded repos get the same waiting and doctor behaviour from the upgraded Forge commands. The merge setting is the owner's, because it is a gate on your own work: never change it to "agent" or run forge merge enable, even when the owner asks, and never merge a change to it. When agent merges are off and the owner wants you to merge, tell them to run forge merge enable in a terminal outside Claude Code or Codex; it opens the change for them to merge. If an interrupted switch's fix contains only that setting and the default branch has moved, it rebuilds the same fix on the current default branch, preserving its other settings. It leaves remote work outside that fix alone and refuses to replace a remote branch that changes after its check. The owner still merges the pull request. Close checks this generated fix mechanically instead of asking a model: only forge.toml's top-level merge = "agent" may change, with all other parsed settings equal to the fix's merge base. Extra changes are refused without review. Tests and CI still run; every other item keeps its model review.

Give status updates in one shape: Ready to merge (n): ... · Needs you (n): ....

Tools

Who does what. The coordinator is the app the developer opens: Claude Code or Codex. tools in forge.toml says which tools run Forge's work: "claude", "codex", or "both" (the default when absent). One tool puts every item on it, including design work with its own design model; workers is ignored. With both, workers keeps its usual meaning. Forge's planning, approval, build, review, CI and merge process stays the same. Doctor's SDK, project trust and UI skill checks follow the selected tool too.

When a round's tool is the coordinator's, its route is native: that session's own background subagent, even with both tools. The other tool runs through its kit: the Codex app server or the Claude Agent SDK. No coordinating session open in a one-tool repo means work waits for the session. Reviews run externally through Autoreview on the named tool; with both, Codex when installed, otherwise Claude. Normal, light and sign-off reviews use [models.review] or its per-tool entries. Unset entries use Autoreview's default model and effort; unset effort uses its default effort.

Running a handed-out round. When Forge prints a subagent instruction, run that instruction as a background subagent of the named role. Keep the item's chat while the session is open; continue the named subagent when told to, or start fresh with the whole brief in a new session. If that chat is unreachable in the same session, start fresh with the whole brief too. Run forge handback with its last message and id and the printed --round when it finishes. Send the nudge when asked; never edit the brief. Only the coordinator hands back or starts a round.

Native plan reads. With one tool, that tool reads every plan. With both, the first read chooses the other installed tool, and later rounds keep their reader while it is available. When that reader is your session's tool, forge read <target> prints a JSON instruction for the reader role in the plan's checkout. Continue its named subagent with prompt, or start one with fresh_prompt when no subagent is named. Run it in the background, read only, then give its message and id to forge handback <target> --round <round> --subagent <id> --text "<message>". The session holds the item's lock and machine place until handback or the session ends. forge stop frees Forge's hold; stop the subagent in your session too. Stale rounds record nothing. Forge checks for changed files, numbers and records findings. Init and upgrade deliver the reader role.

Native build rounds. forge work and forge land print the role (worker, fixer for a fix, frontend for design work), the model to start it on (over the role's own), the checkout and the brief. Pipe its last message to forge handback <item> --round <n> --subagent <id>; send any printed nudge, then hand back again. Hand-back restores forge.toml and records a Question:. Then run forge land or forge close.

Upgrade first. Older Forge refuses the tools key. Follow Upgrade Forge before adding it. New repos get the setting's default at init; existing repos get this guide when the upgraded Forge syncs, keeping their settings as written.

Reporting a Forge bug

When Forge itself misbehaves (a wrong or confusing message, a refusal that shouldn't happen, a crash, a command that reports success but did nothing, generated files that contradict each other), file a bug on Forge's GitHub. No client code, names, paths or secrets: describe what Forge did. Read the template text from GitHub and drop its front matter:

gh api repos/knacklabs/symphony-forge/contents/.github/ISSUE_TEMPLATE/forge-bug.md -H "Accept: application/vnd.github.raw"

Fill every section, then save the filled body with your own file-writing tool as UTF-8.

The template's sections, which the body file keeps exactly:

  • ## Forge version: Output of forge --version and the pinned version in forge.toml.
  • ## Host and operating system: The host (Claude Code or Codex) and the operating system.
  • ## Command and output: The exact command and full output, with private names replaced.
  • ## Expected and actual: Expected vs what happened.
  • ## Steps to reproduce: Upgraded from which version, new or adopted repo, and the forge.toml settings without secrets.
  • ## forge next: The forge next output at the time.
  • ## Tried and workaround: What was tried, and any workaround.
  • ## How often: How often: always, sometimes or once.

File it with no prompt:

gh issue create --repo knacklabs/symphony-forge --title "<short plain title>" --body-file <that file>

Laptop setup and after cloning

The repeatable laptop installers are published for Mac and Windows. They install the tools Forge needs; follow their prompts to sign in to GitHub and your agent.

After every clone, read the pinned version in forge.toml and install that release, even if the laptop installer installed a different one: uv tool install git+https://github.com/knacklabs/symphony-forge@<release> (replace <release> with the pin, such as v1.2.4). Then run forge sync to install the local git hooks and refresh the generated files, followed by forge doctor --fix. In Husky repos, sync preserves the team's hooks and adds Forge's checks to the committed .husky/pre-commit and .husky/pre-push, through .forge/hooks.sh. Commit those additions; npm install can rebuild .husky/_ without removing Forge's checks. The team's commands run first in a child shell; successful exit or exec cannot skip Forge, both pre-push checks receive Git's input, and a team hook failure still blocks Git. Resolve any remaining Doctor rows before starting work. An existing repo gets these instructions and the updated CI workflow on its next forge sync after upgrading.

Migrate copied-in Forge

Read the migration playbook before running forge migrate --dry-run or forge migrate. It ships beside this skill and covers the human's agreement, preservation review, merge, cleanup and rollback. Follow it in order.

Machine views

Forge gives agents half the available cores (at least one place), across all repos. Work rounds share a line. Plan reads and close reviews take no place in that line. Every run, including tests, reviews and plan reads, waits while the machine's one-minute load per core is 1.5 or more, even when no other Forge run is active; where the OS reports no load (Windows) the memory and core budgets still apply. Waiting items that other planned work waits on go first, first-come within each group; each waiting run says its place and why. The test lane makes all test runs share one process budget: the cores left after the agent share and busy local CI jobs, with at least one process. Small selections count too. Each run takes its allocation from the remaining budget. A worker's test runs use at most two processes. Nested pytest pickers keep the parent run's allocation. Older-release entries without an allocation count as the old default share, half the machine's cores. forge doctor names these older runs and shows both lane budgets, free memory and its floor, test processes in use, and busy runners. Every run waits when free memory is below 1.5 GB or ten percent of RAM, whichever is larger. An unreadable memory source does not block work. Each busy local Runner.Worker reserves one core from Forge's half-core budget, with at least one core left for Forge. Waiting runs check memory and runner use again on each pass and start when capacity returns. On Mac and Linux, doctor warns when a GitHub Actions self-hosted runner runs on this machine: its CPUs count against Forge's load limit.

forge board --json includes both machine-wide lanes, their sizes and entries in queue order, alongside OS load and memory. Each entry has an id, kind, repo_root, repo_name, item, model, effort, joined_at, started_at, process, output_path and progress. Waiting entries have no start time. Test output and progress are null until the test runner sets them. The process includes its pid and start identity; a reused pid cannot hold an old place, but the run keeps it while its own Forge process runs.

Only a person runs forge stop <item> (optionally --repo <root>) to stop that item's entries in both lanes, or forge stop --id <id> to stop one entry. The host asks for confirmation first. Workers never run it. A waiting run leaves the line; a running run's verified process tree ends before its place is freed. An unverifiable identity refuses without terminating anything. Stopping an item also clears its abandoned item lock when no live run holds it, so the next round can start even when the coordinating session stays open. This applies to runs Forge launched. A handed-out native run has no process tree, so forge stop frees its hold and the coordinator stops the subagent in its session.

In Claude Code's Forge pane, the Machine tab shows this release's agents and test lane across repos, who waits next, local item gates, and machine load when the OS provides it. Desktop draws an agent tree; narrow terminals use a list. Select a run, press o for its last 200 output lines or s to ask the person whether to stop that exact run. A run that ends or is replaced during confirmation is not stopped. Press l to show or hide recent events. The spinner names this session's place in line. /forge includes the machine summary where panes cannot draw. Lanes record each item's title when it joins, so other repos' stories and tasks keep their plain names in rows, selectors and stop confirmation. Only this repo's board adds tool, round, current step and gates. forge lanes --json supplies the Machine tab; other releases still count towards admission but are left out of its rows. New repos receive the mod on setup; existing repos receive it on upgrade and forge sync. An unreadable output or a failed refresh is explained in the pane; failed refreshes keep the last rows. The mod adds no background work or dependencies.

forge board opens the board as a live page on this computer: a server on 127.0.0.1 at a free port, with a per-run token in the page address that every request must carry, so no other computer and no old link can open it. The page refreshes by itself when the work changes, and the server stops when the command ends (Ctrl-C). It rebuilds only the rows whose inputs changed and fetches from GitHub at most once a minute. --out <path> writes every item's facts as JSON instead, finished ones included.

forge board --json and forge next --json print JSON for the Claude Code mod and other readers. The usual commands still print text. Both views include version (the running Forge release) and repo_root (the resolved main worktree path, shared by the repo's worktrees). forge next --json uses the same 60-second GitHub cache as forge board --json, while text forge next reads GitHub live. Pull request creates, edits and draft changes keep this shared cache. They show within a minute. Merges clear it at once. Each command fetches the default branch's settings at most once. After close, a matching receipt still shows "ready to merge" while the cached list lacks the pull request's link.

Both views' items has one row per story and fix, with tasks in the story's children. Finished stories, tasks and fixes older than seven days are left out of --json; --out keeps them. The live page reads the same rows, so each state and time on it matches the JSON. Each story shows its key small beside its title, the one ID on the board. Story rows carry read_rounds: each cold-read round of the plan with its number, its findings' first lines and their dispositions (cut, defer or keep with its reason), and read_at on the latest round only. Each call reads current state without a history cache. Assigned merged parts stay omitted rather than appearing as unstarted. Backticks around task IDs in the plan are ignored by the board. forge board --json supplies dependency_maps: one entry per roadmap story with planned (false for a story with no plan yet, which has no parts), done (its finished parts) and its full planned parts, plain titles, labelled states and waits_for item references, including old merged dependencies omitted from active rows. Each unfinished part has column: 0 when it waits on nothing unfinished, else one more than the largest column it waits on, across stories, so parts in one column can run at the same time; parts that wait on each other share a column and say so in note. Finished parts have a null column. When fixes are open, an entry with kind: "fixes" lists them in column 0 with running and started_on (the story part a stacked fix was started on). Startability and scope blockers come from the same rules as forge next, including approval and required rereads. Both views add team: sentence, counts, needs_you, in_progress, blocked, pace, go_live, trends and around_forge; a key not worked out yet is null. Every story, part and fix row has one state, and team.counts counts each kind apart, finished ones included: stories (done, building, approved but not started, waiting for approval, planning, no plan yet), parts of approved stories and fixes (done, ready to merge, in progress, to do), and finished_last_7_days. A story is building only once one of its parts has started. Rows also carry merged_at, and approved_by is a name or null. GitHub's pull request lists are read at most once a minute: the open ones and one search per week for the last six full weeks and this one. team.pace counts parts and fixes merged on the default branch per full week (Monday to Monday UTC) for six weeks. team.go_live gives a date range for the parts of approved stories still to merge, from the last three and six weeks' pace: range, about when the two agree, not enough history when the oldest merged part is under three weeks old or the last three weeks merged none, or done; it counts the stories with no plan yet as the unknown, and team.sentence says the same in a count, not names. team.in_progress lists the stories and fixes in progress (id, title, kind, state). team.trends counts merged pull requests per week for the team and each person (alphabetical, no totals or ranking), split into through Forge (a story/, task/ or fix/ branch) and around it; team.around_forge lists the ones merged around Forge in the last 7 days. Both are null without GitHub, and a week capped at 1,000 results is null. team.needs_you lists every item that waits on a person, oldest first, each with its reason, since and waited_seconds: approval (a plan waiting for approval), review loop (a review loop or stuck-worker choice), worker question, conflict (a merge conflict close stopped on), merge (a ready pull request a human merges) and blocked plan, with the question or the problem forge next reports in detail. Close and forge work publish these waits to the pull request, so every machine lists the same ones; answer a worker question with forge work <item> --note on any machine. team.blocked lists started parts and fixes idle 24 hours or more (stalled) and the unfinished part the most unfinished parts wait on (blocks the most), longest first, each with detail and seconds. team.sentence counts both. Ask the human about each Needs you item as forge next words it. Active parts and fixes show recorded Build, Tests, Review, CI and Merge times for their current round, with the current stage marked. Missing times remain unknown. New clients get this at init; earlier adopted clients get it after upgrading Forge and syncing. Cards and JSON use the same status and took derivation; running stages include their elapsed time. Roadmap completion marks the story and its parts finished. Finished items never stall; idle items past one day expose stalled, idle_seconds, and waits_on. A story's idle clock includes its parts' activity; a running part keeps the story active and a finished part resets its idle clock, including a merge reported by GitHub before its commit is fetched. An older worker round or a worker whose recorded lock is demonstrably dead is not a live run. Live readers, workers and reviews take precedence over a saved status, including in forge next. Approval history uses one display name per Git email, honoring mailmap, and fix titles shorten at a word boundary. Each row has id, kind, plain title, stage, worker (kind, model and started_at, or null), pr (number, url and checks: pass, fail, running or unknown), findings (count and titles), round, stages, total_seconds, occurrences, approval, needs_person, and its own next. needs_person is true while the item waits on a person, by the same decision as team.needs_you. A story awaiting approval has approval.doc, the absolute path to its document in its own worktree; other rows have null approval. Task and fix rows add time_breakdown and rounds; story rows add outcome (null until done). An empty board has an empty items list. Missing state shows unknown. No run start, end, round or occurrence id is invented when its producer has not recorded one. The last task's merged outcome marks its story done, even when the saved story state still says approved. New clients get these views and this guide at init; existing clients get them after upgrading Forge and running sync. Tasks and fixes are ready only while close's clean review matches the pull request's head (the local branch when it already holds that head) and the checks are not failing. The clean review is this machine's receipt or the readiness close published in the pull request, so every machine agrees. On a machine with no worktree for a ready item, its next step names the person who started it to merge it from their machine (or, in a human-merge repo, the pull request to merge); with the worktree here but no receipt, it is forge close <item>, which checks it again and writes the receipt that forge merge needs. A failed rerun shows checks failed with forge work as its next step, even after a successful close. A running rerun or unavailable checks retain a matching receipt's readiness; green checks alone cannot grant it. The item's next step uses the same evidence. If the next step cannot be checked, the row stays visible with no runnable command; check the connection and run forge next again.

Stages are Build, Tests, Review, CI and Merge, in that order. Each carries status, started_at, ended_at and seconds for the current round from Forge's timing records; unrecorded values are null. For tasks and fixes, total_seconds is elapsed time from the item's recorded start (or first recorded activity) to now, including idle time; a finished item needs a recorded end. Their snapshot already includes live time, so do not add a running stage's timer to it. Worker-owned tests are separated from building in the breakdown, while close's tests and forge test still appear in the Tests stage. A skipped test has status skipped. Run starts and ends provide live worker and reader metadata; completed timing durations supply stage end times. Values remain null where the producer has not recorded them.

time_breakdown separates building, own tests, reviewing, fixing findings, waiting for CI, waiting in line, waiting for the owner and nothing running. Overlapping steps count once: tests and recorded waits come out of the enclosing worker's time. rounds retains each round's plain line, its finding titles, priorities and files, and its new and repeated finding counts. Round lines number review attempts; the row's current round still identifies its worker turn. Repeated means the same title and file appeared in an earlier recorded review. A missing earlier review makes unseen findings' new-versus-repeat counts unknown. Repeats observed in the retained history remain known even beside unseen findings whose classification is unknown. CI lines distinguish passed, failed and gave up while queued or running; losing contact with GitHub without a known result stays unknown. An earlier release's failed CI timing without an explicit result also stays unknown. Old records are not filled in with guessed findings, durations or zeroes. A known original start can give an elapsed total even when some earlier activity is unknown; that unknown time is not called idle and the known breakdown need not add up to the elapsed total. Gaps between legacy timings stay unknown until complete activity recording begins. Earlier run and question events do not establish completeness; historical questions without a recorded end stay unknown.

Quote the item's round lines when explaining a long run, then quote the relevant breakdown categories rather than adding overlapping stage timers. The board page shows these same details. The pull request's short How it went section comes from the same logs and keeps earlier findings after later reviews. Forge keeps the pull request body in an agent squash merge; when the human merges, keep this section in the squash commit body too. New repos get this guide and the recording behaviour at init; previously adopted repos get them on upgrade and sync. Their earlier unrecorded history remains unknown. If a fresh clone or missing local logs cannot reconstruct already published measurements, How it went retains them and shows the current checkout's observations separately. Quote each snapshot separately; do not add their totals, because their intervals may overlap.

Quote the item's round lines when explaining a long run, then quote the relevant breakdown categories rather than adding overlapping stage timers. The board page shows these same details. The pull request's How it went section carries intervals, review rounds (including clean reviews), and waits with their ends. The shared record has a fixed size: while it is over, the oldest detail of any kind (round lines, intervals, then raw events such as repeated CI waits) folds into one "Rounds 1–N" line of totals and finding counts, oldest first; the newest round keeps its own numbered line, and its folded retries add to it. Free text in each entry is cut to 300 characters, and a round too big on its own, such as a huge review, folds whole into its line. Live progress stays local. So a long item stays inside GitHub's body limit, and folding never changes its total time. Close and merge retain observations from other checkouts and recompute repeated findings and overlapping time. The board reads this shared record alongside local observations; source says which it had. A GitHub merge ends the last wait at its merge time and names its merger. Story time counts each instant once: working when any part or recorded plan read is working, waiting when none is working and any is waiting, otherwise unknown. Inside a part's folded time range, the story's split between working and waiting is approximate; the totals stay exact. Human times use rounded plain units; JSON keeps raw seconds. Rebuilt history keeps its label and missing evidence remains unknown. New repos get this at init; existing repos, including earlier adoptions, get it on upgrade and sync. Worker questions and their answers refresh an existing pull request's record; a question before its first pull request stays local. A first-close merge conflict opens the pull request so its owner wait travels too, and resolving it records the wait's end. Resuming work ends a published merge wait even when another checkout started it. History refreshes retain the pull request's readiness; close publishes readiness for a newly checked head. Board and next reads use the current pull request history and head; cached checks apply only to the head they describe. Questions on older pull requests without local publication evidence stay local. Every pull request body stays within GitHub's size limit. Forge leaves tool-command prose out of the shared time record, shortens duplicated and advisory detail before the contract, and says plainly when it shortened anything. Exceptionally large time histories retain recent evidence and current readiness; omitted time stays unknown. Full contracts and proof stay in git. Items report working, waiting and unknown seconds alongside their detailed categories. An unfinished story's runway continues through now, including unknown time after its last read or recorded part activity; a read without a duration establishes a date, never guessed work.

Live rows add activity (status and a running action), idle_since and stalled after 24 idle hours. A recorded worker adds tool, model, effort, round, start, elapsed seconds and its latest tool step; close's live tests adds elapsed and done/total where the runner reports progress. gates has plan_read, review and ci, each with status; blocked reviews add count and running CI adds elapsed seconds. PR failures list job and cause (timeout or failed); findings add items with title and priority and a dismissed count. Both views' events list the last 20 run, review and question records as time, item and one plain line, newest last. Queue places stay absent until the shared lane entries ship.

The newest 25 open pull requests get checks in one GitHub request, cached for 60 seconds in the shared Git directory. Older pull requests and unreachable GitHub show unknown checks. Required checks, including matrix variants, must succeed; skipped or neutral required checks show fail, while optional ones count as passed. A failed check occurrence keeps GitHub's own identity: check-run:<databaseId>:<completedAt> or status:<id>. A rerun with a later completion is a new occurrence. Each occurrence has id, kind and plain title; run completions, undismissed review findings (including advisory findings) and unanswered worker questions retain the ids in Forge's event records. Advisory occurrences do not change the merge gate. Readiness uses the clean review's id plus the current head commit. Runs that start and finish between board refreshes still appear as completions.

Both views' next contains command, line and step. Command is the first Next line only when it is one runnable Forge command without a placeholder or alternative; otherwise it is null. Line carries the plain current state from the text report. Step is the first Next line's text even when it is not one runnable command, or null without one. Release notices appear in the text output and never replace the machine view's state line. Use each board row's next step for that item, never another item's step. Machine views do not grant approval or permission to merge.

The shared mod contract fixture is tests/fixtures/board.json; command coverage in tests/test_machine_views.py checks both views against it.

Claude Code plugin core

The Claude Code mod reads Forge's machine views, runs forge commands and draws; it never edits repo files or replaces the commands' approval and merge rules. All its consumers share one snapshot. On load, then every 10 seconds, one refresh runs forge next --json and forge lanes --json together. The next output also supplies the board rows, so the pane reads them once per refresh. If that Forge has no lanes command, lane data is absent. A due refresh is skipped while the previous one runs, rather than queued. There is no extra fetch on /forge. Drawing ticks advance live timers every second without fetching state.

A failed or malformed refresh, or a command taking over 60 seconds, keeps the last good rows and adds a dim Couldn't refresh: <first line of the error>. It retries on the next tick. Only a non-zero command result containing unrecognized arguments: --json shows This repo's Forge is too old for the pane: upgrade Forge here. Other failures use the normal refresh error. The board command owns the 60-second GitHub checks cache described above; a check changing without a local edit appears on the next refresh after that cache expires. Unknown checks stay unknown, including when GitHub is unreachable.

Installing the mod

forge sync installs or updates the mod from Forge's latest release at user scope, one per machine, independently of each repo's pinned Forge version. It needs Claude Code v2.1.287 or newer on PATH. When the marketplace is missing, sync runs claude plugin marketplace add knacklabs/symphony-forge, then always runs claude plugin marketplace update forge. It runs claude plugin install forge@forge --scope user for a new user install, or claude plugin update forge@forge --scope user for an existing one. Sync writes no repo files for the mod; Codex's setup and workflow are unchanged.

Without claude, sync skips the mod. A failed install or update (including no network or old Claude Code) prints one warning line and sync still succeeds; run forge sync to retry. forge doctor warns when Claude Code is missing or older than v2.1.287 and keeps exit status 0. In a running session, use /reload-plugins or restart to load the updated mod. Desktop's WSL sessions load no plugins; use the forge command there.

The mod runs forge from PATH with argv and no shell, in the session's repo root. It does not change the repo's pin. If the pane or strip says that repo's Forge is too old, upgrade Forge in that repo; other refresh errors follow the core's retry behaviour above. Sessions without the mod keep using the commands as today.

Approving with the pane

The pane has no Approve button: Claude Code's mod API cannot supply the story's exact text to its native plan-approval prompt. Use Plan Mode and the story doc forge next names, following Planning a story below. The existing approval hook and its trust checks are unchanged. Plan Mode remains the approval path in Claude Code, Codex, the VS Code chat panel and sessions without the mod; installing the mod changes no approval rule.

The pane and strip

Type /forge at any width, including 80 columns, to open and focus the pane. The mod requests it at interactive session start too; Claude Code places it automatically at 144 columns, or 110 after the person has opened it once. The strip below that wide-screen threshold ends with /forge for the board. The pane's Board tab, on the terminal and the Desktop app's Code tab alike, opens with one summary line, such as 3 in progress · 1 needs you · 2 stuck · go-live Oct 20. Each part shows only when the board's team fact behind it is known and not zero; when none of them applies, the line counts the rows the tab shows in its own group words instead, such as 1 moving · 2 ready to merge · 3 waiting; an empty board says only Nothing in progress.. An older Forge with no team shows no summary line. Below it come the board's own sentence (team.sentence) and its labelled totals, one line per kind, such as Fixes — Done: 0; Ready to merge: 0; In progress: 1; To do: 0, words built from team.counts. Groups follow in this order, each only when it has items, and each in the board's own order: NEEDS YOU (one row per team.needs_you entry, oldest first, with how long it has waited, such as waiting 3h), STUCK (one row per team.blocked entry with its reason in words and its age, such as 1 unfinished part waits on it · 2d, and the longest stall in days), MOVING (the board's In progress, team.in_progress, except what is ready to merge; a running item shows its current stage and how long, such as Review running · 12 min), READY TO MERGE (the rest of team.in_progress, each saying Ready to merge, with the checks in words when known, and its merge step; an item merged from another machine shows forge next's line naming who merges it), then a folded count of the work waiting or not started (planned stories and fixes not started), opened with a native button to the same rows. Each item shows its plain title, the story key small beside it, one line of state in words and, where there is something to do, its next step (next.step) after →. At most five rows show per group, then and N more · forge board for all. An older Forge without the team lists draws the same groups from each row's needs_person, stalled, running and ready flags, and shows no sentence or totals. Outside those totals, the pane never shows zero counts, unknown times, raw timestamps, priority codes or dismissed counts; findings read Review found 3 problems. Dates are short (Oct 5) or relative (12 min, 8 days), and every time rounds as the board's do (2m, never 1m 30s). Stage chains, round lines and the time breakdown stay on the board page. An empty board says Nothing in progress. /forge always returns the same lines as text, as well as opening the pane. Where mods draw nothing (the VS Code chat panel, claude -p and Remote Control from a phone or claude.ai), that text is the status reply. It uses the last completed snapshot, including any refresh error. Until a first refresh succeeds, the pane, the strip and /forge say Loading Forge…, or Couldn't refresh: <first line of the error> once that refresh failed.

The strip above the prompt uses at most three lines. Its first line shows Agents N/M (W waiting) · Tests N/M (K waiting) · <running item or idle> · 1: <next command>. It also shows a recorded current worker step. The other lines show up to two active items from this repo, each with Build → Tests → Review → CI → Merge, its round and total time. A third active item replaces the last line with +N more · /forge for all. Finished stages show ✓ and their duration, the current stage ● and a live timer, and failed stages ✗ in red. Unreached stages have only their name; skipped tests show Tests –. Symbols carry the meaning without colour. The total uses the elapsed-time snapshot, which includes live and idle time without counting concurrent stages twice. At widths under 80 columns it is one line: running and waiting counts, the first active item's current stage and time, its total and the next step; without lane data, the first line is only the next step; the narrow strip has no item or lane summary.

Press 1 on an empty prompt when Claude is idle to run the displayed next command as the user. Before submitting, the mod re-reads forge next --json: if the command changed it redraws and runs nothing. A failed re-read submits nothing and toasts Couldn't check the next step: <reason>; a failed submit toasts Couldn't run the next step: <reason>. A null command or busy Claude has no hotkey and shows the plain next-step line instead. Typing into a nonempty prompt stays typing.

Events

The mod starts a session turn for new review findings, failed checks, a pull request ready to merge, worker questions and finished runs. Progress only updates the pane: starting a run or running checks never asks the agent for a status turn. Each event line names the plain item title, what happened and that item's own next command, or forge next when there is no runnable command. When the turn arrives, act on it straight away through Forge's commands: resolve findings with evidence or send a worker round, follow a failed check's next step, close finished work, and merge ready work only when allowed. Answer a worker question with forge work <item> --note "<answer>"; a choice the human owns still goes to them. Do not build another polling or watcher loop for an interactive mod session.

Forge's occurrence ids distinguish events, never their text: review results, run ends and questions get fresh ids; CI completions use the workflow id and head commit. Failed checks outside a workflow use GitHub's id and completion time, failed commit statuses use their own id, and readiness uses the review id and head commit. The same question in another round or a check failing again after a later completion therefore gets another turn, even if its wording is unchanged. A run that starts and finishes between refreshes still has its completion occurrence.

Seen ids are stored for the repo root and session id together, so two sessions consume events independently. At session start or reload the current ids become seen without starting a turn. Several changes, including those found while Claude is busy, are combined in one turn after the current turn ends, one line per event. At delivery, each line uses the item's current next command. Events for items that have since merged are dropped. A CI completion names the workflow and its result, once per workflow and commit. A failed submission leaves them unseen for the next refresh to retry; a successful one is not repeated. Only interactive sessions on terminal or Desktop receive event turns, and only for their own repo. Forge-started workers, readers and reviewers have FORGE_WORKER=1 and never act on events. Headless sessions receive no event turns. Codex and sessions without the mod keep using forge next and the existing command flow.

Handoff

.git/forge/handoff.md in the main checkout, shared by every worktree, carries your state across a compaction. Before each compaction Forge rewrites its current-state part from forge next and keeps everything under ## Decisions and lessons. Add one line there at each milestone: a story approved, a task merged, a decision the owner made, a lesson learned or a mistake you won't repeat. After a compaction or in a new session, read it first. The file is local and never committed.

Coaching

The first time each of these happens for a person, add one plain sentence saying what just happened and what they can do next. Say it once, then never repeat it, and never name commands unless they ask. Note each one in the handoff so a new session doesn't repeat it.

  • A story plan shown for approval: "This is the plan in short; you can approve it as it is or tell me what to change."
  • A cold read finding: "A fresh reader found a gap in the plan, which I'm fixing; you can weigh in on the fix or wait for the next read."
  • A part starting: "A worker is building this part on its own copy; you can keep planning meanwhile."
  • A pull request ready: "This part passed its tests and review; you can look it over before it merges."
  • A merge: "This part is now in the main code; you can try it there or pick what comes next."
  • A prototype demo: "The customer has seen it working; you can tell me what they did and said to shape the next round."
  • Sign-off: "The customer approved the prototype; you can now choose the first real story to plan."

New directions

Grill any request with more than one reasonable reading or no stated done-when before starting a fix or story. Ask one question at a time with your recommendation and why; look up facts in the repo instead of asking the human. A clear request with a stated done-when can go straight ahead.

When the human states a new direction, principle or value, grill how it applies before changing anything. Ask one question at a time with your recommended answer and why; look up facts in the repo instead of asking the human. Continue until the plan for applying it is agreed, then make the changes.

Discovery

Every ask is a guess about a problem: find the problem before anything is planned. The worked example, question bank, customer call script and outlines for the pre-meeting brief and recap are in the reference page.

By size. On a fix or an everyday story, interview the engineer, and through them the customer: one question per turn, about something that already happened. Offer no solutions while discovering, and praise isn't evidence. A question of fact gets neutral choices; a decision gets your recommendation first. Add a one-line Why I ask: to each question until the engineer says they know why. Ask at most two questions for a fix and eight for a story, then write what is still unanswered as unknown.

A new project. On a new app, the FDE puts every client note in docs/context/. Read all of them before you ask anything. Fill docs/product/BRIEF.md and the problem cards in docs/product/DISCOVERY.md from the notes, and list each contradiction and gap for the FDE. Ask the FDE one question at a time, and only about a point that changes a screen, a field or a flow. A new app has no fixed number of questions. Give every other open point a default, mark it (assumed) where you write it, and list these assumptions in one message for the FDE to change. Put each question the FDE cannot answer on the recap's Still to find out list, and build the prototype without waiting for the answer.

Before the first meeting, draft a one-page pre-meeting brief in docs/context/ from the prospect's website: their likely jobs, two or three guessed problem cards each marked (guess), their terms, and the first five questions to ask. During discovery, keep the customer's own words in ## Words they use in docs/product/DISCOVERY.md, one - <their word>: <what it means> line each. Ask for the spreadsheet or form they use today and keep only its column headers, never its rows. The same day, draft a recap in docs/context/ for the customer to confirm: their problem in their words, the cost in their numbers, the one task the demo will cover, who signs off, the open "ask the client" questions and the demo date. When they reply, record the Demo workflow and Sign-off person answers from it with client as the source, as - Demo workflow: <task> (client, <YYYY-MM-DD>) and - Sign-off person: <name, role> (client, <YYYY-MM-DD>). The salesperson sends the recap; Forge sends nothing.

The card. Write each problem as a card under ## Problems in docs/product/DISCOVERY.md, adding the section to an older file on first use: ### <short problem title>, then Job, Workaround, Cost, Who feels it, How often and Evidence. Write customer notes in docs/context/ into cards and leave the notes where they are. Name the chosen card's heading in the brief's Summary and the story's Why (or the spec's Why when using a spec). Costs use rounded rates, never real salaries.

Options. For the chosen problem, offer two to four options, always with Don't build and Smallest slice, plus Use what they have (a setting, report or process change in tools they already run) whenever one could do the job. Give each option that builds something its own forge spec payback line, and recommend the one with the fewest months among those answering build or smallest slice first; a tie goes to the smaller build. If none does, recommend don't build, or find out first when an option's value can't be estimated. The human chooses; write the choice and one line of why into the story's Why, or the spec's Behaviour when using a spec.

Prototype

New clients get these steps at init; existing clients get the same guide after upgrade and sync.

In a new client repo, follow the FDE route: start with discovery of the customer's job, workaround, cost, people, frequency and evidence. Start with a canvas, as described below. The canvas in Play mode is the default clickable prototype: the customer clicks through it, and can sign off on it alone. Build a working prototype only when the FDE requests one to prove something the canvas cannot, such as real data or speed. Record that request with forge decision new prototype-build; the owner accepts it with forge decision accept prototype-build --by "<name>". Then build the smallest working demo that proves the request, demo it, review the whole prototype and answers, and obtain the customer's named sign-off; then write the app's constitution; then create stories. Use prototype fixes before sign-off. The salesperson can be the FDE at first; the developer takes over with docs/product/DISCOVERY.md, docs/product/BRIEF.md and the customer call script. Build the prototype on the starter app. Rename or remove the Project placeholder. Push back when a requested feature does not serve the problem card: explain why and offer to note it for after sign-off. Keep the same app as it grows into production.

For a requested working prototype, build the demo-data loader in the first version, so the customer sees their own kind of work instead of empty screens (see demo-data.md). Use the customer's words from ## Words they use in docs/product/DISCOVERY.md for screen labels. Tell the salesperson they can point at the screen: click an element in the demo build, write what's wrong and paste the output to the agent.

Open decisions. Write each question only the client can answer in this form, in the decision record under ## To confirm, the spec, or the plan's Notes where it belongs:

- Open question: <question>
  Touches: <screen or rule>
  Why we ask: <what the answer changes>
  Default: <choice>
  Why this default: <reason>
  If the default stands: <what happens>
  Due: <YYYY-MM-DD>
  Gain <1-5>, Work <1-5>, Harm <1-5>

Pick the default, due date and scores with the review bar before you ask. Copy a cold-read or review note that needs the client into this form. forge decision open prints the ranked list; go through the whole list with the client in one sitting, then record the answers with forge decision answer. When forge next says a due date passed, record the defaults with forge decision answer --defaults and tell the client each default and that a new decision can change it. After an upgrade, rewrite each existing to-confirm list and open question in this form once in a mechanical fix.

For each demo round, gather all requests from one conversation into one prototype fix. Build, test, review and merge that round before starting another. Tell the salesperson what changed in the demo: "the next version is ready to demo" or "the reviewer found a missing error message". Say a version is live only after the requested deployment is verified. Do not use Forge's words (fix, worktree, branch, pull request) in progress updates to the salesperson. When forge next says to connect the repo to our deploy platform, explain what is needed; that reminder does not authorize deployment. When the user explicitly requests deployment, use Mainline Deploy as above. After the requested deployment is verified, record its address under ## Demo in docs/product/BRIEF.md as - Address: <url>. Copy that address into the sign-off decision. A canvas-only prototype needs no demo address.

The demo. Every demo follows five steps, in order:

  1. their workaround today;
  2. the same job in the app;
  3. the time or money it saves, in their numbers;
  4. they take the controls;
  5. ask "what would stop you using this?"

Send the demo link only after the guided demo, never before it. With each new version, draft a three-line "what changed" note for the salesperson to send: what they can do now, what changed since their last look, and what to try first, all in their words.

After each demo, ask the salesperson one question at a time: what the customer did themselves, what they said word for word, and what they asked for. Write the answers under ## Prototype notes in docs/product/DISCOVERY.md as one ### <YYYY-MM-DD> block with - Did:, - Said: "<their words>" and one - Asked: line per request, ending each request in (serves the problem) or (after sign-off).

Ask one topic at a time when it first changes what you are building. Skip a topic the repo already answers. In the options column, our default is first where there is one; questions of fact have no default. Offer Ask the client on every question and add it to the customer call script in docs/product/DISCOVERY.md. For a topic that may wait, also offer Decide later. Standards already decide logging, security and error handling; take those defaults and include them in the read-back without asking again. The first seven topics must have answers from the client, salesperson or developer before sign-off; a default or agent-only answer does not settle them.

TopicQuestionOptions (default first)Before sign-off
Sign-off personWho at the customer approves the prototype?name and rolemust
Demo workflowWhich one task should the demo let them do from start to finish?from the problem cardmust
Users and rolesWho uses it, and what can each kind of user do?admin and staff; othermust
Existing systemsWhich systems must this work with?none; CRM; accounting; email or SMS; payments; company sign-inmust
Sign-inHow will people sign in?email and password; company sign-in (Google or Microsoft)must
Personal dataWill it hold personal, health or payment data, or data that must stay in one country?none; personal only; regulatedmust
Production hostWhere will the real app run?our platform; the client's cloud; the client's serversmust
Data importDoes data need to come in from today's tools?no; a spreadsheet; another systemmay wait
Email or SMSWhich provider sends messages?none yet; the client's providermay wait
DomainWhich web address?our subdomain; the client's domainmay wait
Backups and uptimeHow much downtime or data loss is acceptable?the platform's daily backups; strictermay wait
Log retentionHow long must logs be kept?the platform's default; longermay wait

Keep one ## Answers section in docs/product/BRIEF.md, one line per topic. Record an answer as - <Topic>: <answer> (<source>, <YYYY-MM-DD>), with the client, salesperson, developer, our default or agent as its source. Record an open request as - <Topic>: ask the client (<who asked>, <date>); replace it with the sourced answer when the client replies. Only a may-wait topic can read - <Topic>: later, when <trigger>.

Canvas and app design. Make each app's design from the whole docs/product/BRIEF.md: its purpose, users, records, fields, choice-list options and key flows. Use impeccable and emil-design-eng. Run impeccable's init when PRODUCT.md is missing. Make the screens in a Claude Design artifact with a shadcn design system and named shadcn components. On a Codex-only host without an artifact tool, make HTML artboards in the repo. Keep the canvas available for the customer and the sign-off review. New clients get this process at init; existing clients can use their current brief and design after upgrade and sync. Client sign-off does not require a root DESIGN.md, SCREENS.md or CHECKS.md, or a specific Things it keeps track of heading. When the canvas has a SCREENS.md screen list, add Build: now or Build: later to each screen. Put the whole product on the canvas, every screen, state and flow, later ones too, and mark Build: now only what the first stories build. Before sign-off, use impeccable's critique and polish and the emil-design-eng checklist. The owner or FDE can approve the canvas with forge decision new prototype-design and forge decision accept prototype-design --by "<name>".

For each story that adds a screen or changes a flow, use this design process before its cold read. Save the folder under docs/design/<KEY>/. End ## What changes for you with Design: docs/design/<KEY>/ (<tree id>), using git rev-parse HEAD:docs/design/<KEY>. One plan approval covers that exact folder. A changed folder needs a new tree id and approval. For small changes to existing screens that add no screen and change no flow, use Design: none (<why>) instead. Backend-only stories need no canvas. Earlier approved stories keep working. The build applies any existing app theme and follows the approved story design, including its components and fields. Workers and reviewers receive that design context.

Sign-off. Before anyone asks for sign-off, hold one read-back call with the customer that goes through every item from forge decision open and every answer on the answers page: our defaults, the agent's guesses and the topics marked later. Settle every open must-answer topic in that call: forge next lists those still missing, malformed, or marked ask the client or later, and sign-off also refuses one answered by our default or the agent, so record each as the client, salesperson or developer answered it. Then write forge decision new client-signoff (customer, demo address for a working build, and the answers page copied word for word, leaving approved via and approved on empty), and run forge decision accept client-signoff --by "<name>" before any reply is recorded. Put each demo step and its test under ## Proof for a working build, and settled choices under ## Decisions. Sign-off checks that the copied Answers section matches docs/product/BRIEF.md and that the full run on main is green. Its completeness review must pass after judging the screens against the whole brief, including its purpose, users, records, fields, choices and key flows; it does not require a separate screen list. Missing screens, fields, options or flows hold sign-off. When a precondition is missing, the refusal lists every gap and the next action for each. Existing clients can complete sign-off with the brief and design they have after upgrade and sync; they do not need to add prototype design files for this gate. The check uses Codex's gpt-6.1-sol at high effort, independent of [models.design]. Codex must be installed because only Codex can review the screen images. On Windows, large image folders use separate batches to fit the native command line limit. Fix gaps, then retry forge decision accept client-signoff. If the review engine fails, fix the engine problem and run the accept command again. A canvas-only sign-off needs no working build. A working build gets a full first product review. Each later attempt reviews only product files changed since the previous review and its open findings. Every reviewer gets earlier findings and answers. Settled findings stay settled. After two rounds, P0 findings still block until fixed. Other open findings need the owner's choice. Record that choice with forge decision accept client-signoff --by "<name>" --resolve accept|more|split --reason "<human's choice>". Accept keeps the findings in the item's Known issues. Split creates a separate fix. More permits one more review. These rules apply to new clients and earlier-adopted clients after upgrade. Forge reuses passed results while what they checked stays unchanged. The product review uses the green full run as test evidence. It needs no separate forge test run. If the product or completeness review cannot finish, the refusal names its saved raw report when one exists. Before the customer's reply, the command runs these checks and stops without accepting sign-off. Autoreview uses the repo's tool and [models.review] settings, with Autoreview's defaults for unset values. Fix what it finds and run it again. Once it passes, tell the salesperson to ask the customer's named person for sign-off their own way. Draft no sign-off email; Forge sends nothing. When they bring the reply back, record it in approved_via and approved_on, then run forge decision accept client-signoff --by "<name>" again to accept. The customer's reply is the approval evidence the sign-off decision records. Set approved_via to email, call or chat and approved_on to the reply date.

The Answers check ignores trailing whitespace and line-ending differences. A refusal names the first different line in the Answers section, including its heading.

Every normal, light and sign-off review runs through the external Autoreview program: Claude when tools = "claude", Codex when tools = "codex". With tools = "both", it uses Codex when installed and Claude otherwise. Forge passes the selected tool's [models.review] model and optional effort to Autoreview; without an entry, it uses Autoreview's defaults. New and earlier-adopted repos get this behaviour on the current release. A requested working prototype proves the demo steps and its security and data boundaries. Its light review checks demo breakage, security and data loss, using [models.light_review]. A requested working prototype gets a light review that blocks only on P0 findings. A Not done against the item's own approved Done-when still blocks at any priority. Full proof is due at sign-off.

After sign-off, every new story carries forward the available canvas, Proof and Decisions into ## Proven by the prototype. Treat that evidence as settled. Plan and cold read only new work or production risks. Start from the prototype canvas or a newer design approved with the plan. Plan the first stories only from screens marked Build: now. A Build: later screen stays on the signed canvas as the source for a later story.

When a later story needs a topic marked later, its cold read reports Decide first: <topic>. Ask that one question, put the answer in the finding's disposition and the story's Notes as Decided: <topic>: <answer> (<source>, <date>), and make the first task update the answers page. Until the decision, use a fake provider for an external service.

App constitution

Ask for the app's currency at setup (default INR) and write its code to DEFAULT_CURRENCY in libs/shared/util/src/format.ts and, in a Python app, apps/api/app/core/config.py.

During both halves of the interview, ask one question at a time in plain words for a person who may not be technical.

Before the prototype, take the product answers from the client notes: what the app is for, who uses it, the records and fields it needs, the options of each choice list, and the key flows. Write them in docs/product/BRIEF.md. Record the options for each choice list with the thing it belongs to. The prototype and sign-off review use the whole brief.

At the start, write docs/product/CONSTITUTION.md with every default from Constitution defaults filled in, and raise a default with the FDE only when the notes contradict it.

On a new app, check the client notes for five growth topics: the users and records in year one and year three, heavy background work, other systems that call in or that the app calls, rules on where the data stays, and parts that can become their own product. Ask about a topic only when the notes touch it and do not answer it, one question at a time. Write each answer under ## Growth notes in the constitution. A later story reads these notes before it plans modules, jobs or links.

In an app with libs/mcp or apps/api/app/mcp, check the client notes for tasks that people will ask an agent to do again and again. When the notes describe such a task and do not list them all, ask one question: "Which tasks will people ask an agent to do again and again?" Write each task under ## Agent tasks in the constitution as - <task>: <feature>; inputs: <the inputs it needs>. With no task, write - None. Ask it again only when the person wants to change the list.

In an app whose tests run Nx's module-boundary lint, copy the fixed rules into the constitution's ## Fixed rules section, and ask nothing about them. In an app that has libs/organisations or apps/api/app/organisations, also copy the built-in lines into ## Fixed rules, and ask nothing about the defaults they answer.

In an app with libs/core/server/src/common/request-log.middleware.ts or set_log_level in apps/api/app/core/logging.py, also copy this line into ## Fixed rules: "- Built in: each API request writes one log line with its method, route, status, time taken and request id, never the query string; LOG_LEVEL picks the lowest level written; password, token, secret, key and authorization values are removed from log details; Mainline sends error alerts from the error lines, so the app has no error-tracking service."

In an app whose .env.example has APP_COMPANIES, ask once: "Is the app for one company, or for many customer companies?" Write the answer under ## Defaults, set APP_COMPANIES to one or many in .env.example and in the host's settings, and ask it again only when the person wants to change it. When the answer is one company, also copy this line into ## Fixed rules: "- Built in: APP_COMPANIES=one makes a one-company app: corepack pnpm run setup:company (uv run --no-sync python -m app.setup_company in the Python starter) makes the company and its first admin, and people join only from an admin's invite." In an app made from the Python starter, whose tests run import-linter, copy these fixed rules into ## Fixed rules instead, and ask nothing about them:

  • One app split into modules: a FastAPI API on uv, with SQLAlchemy and Alembic on Postgres, and a React web app on pnpm.
  • Each module owns its tables, in its own models file.
  • Built in: each module keeps its tables in its own Postgres schema, with core tables in core; links between business modules store an indexed id without a foreign key, and links to core keep their foreign key.
  • A module uses another module only through its public service, never through its tables.
  • The tests run import-linter, so an import of another module's internal files fails them.
  • Built in: the API serves an MCP server at /mcp. An admin adds agent accounts on the Members screen, with a role and a key that shows once; agents use each feature's tools with that role's permissions, and delete and restore tools stay off until an admin turns them on for the feature.
  • Built in: each feature also offers its items, its field guide and its allowed values as MCP resources; agents get a prompt only for a task under ## Agent tasks; the MCP server never uses elicitation or sampling.
  • Built in: every action goes through its feature's service and permission, from the screens, the API and agents; each feature's create takes a retry key; an agent that a person signs in acts as that person, and the change history records both; reports are named, typed reads in their feature's report service.

In any other app, record the stack, package manager and folder layout that the app already uses, and leave out ## Fixed rules.

After the customer's sign-off and before the first story, finish the app's constitution in docs/product/CONSTITUTION.md. Use the defaults in Constitution defaults, except where ## Answers in the brief already gives a settled answer. Skip a defaulted question that ## Answers already answers. Check the brief's Personal data answer; ask whether any data is private or regulated only when that answer is missing, then record the answer in ## Answers.

Use this outline, and do not copy product facts from the brief into the constitution:

# <App name> constitution

The product facts are in docs/product/BRIEF.md.

## Principles
- Is it needed at all? Deleting beats fixing.
- Add work only when F x H >= 2 x C. F, for the first 1,000 users: 1 rare, 2 weekly, 3 daily. H: 1 cosmetic or the person's own data, 2 wrong information or a broken step, 3 a P0. C: 1 for 20 lines or fewer with no model call or user step, 2 for up to about 150 lines or one model call or user step, 3 for more, or a new table, job, workflow or flag.
- Choose the simplest option.
- Rules are stated, not policed.

## Defaults
- <the question in plain words>: <the default or the person's answer>

## Fixed rules
- One app on pnpm and Prisma, split into modules.
- Each module owns its tables, in its own schema file.
- Built in: each module keeps its tables in its own Postgres schema, with core tables in core; links between business modules store an indexed id without a foreign key, and links to core keep their foreign key.
- A module uses another module only through its public service, never through its tables.
- The tests run Nx's module-boundary lint, so an import across modules fails them.
- Built in: each account belongs to one organisation, as an admin or a member, and every query is scoped to that organisation.
- Built in: admins invite people by email; members cannot invite people or change roles.
- Built in: dates and times are stored in UTC and shown in the person's time zone, and money is stored as an exact decimal with its currency code; the helpers in @app/shared-util format them.
- Built in: each table has a CSV export, admin actions write change history, and records are soft deleted.
- Built in: the API serves an MCP server at /mcp. An admin adds agent accounts on the Members screen, with a role and a key that shows once; agents use each feature's tools with that role's permissions, and delete and restore tools stay off until an admin turns them on for the feature.
- Built in: each feature also offers its items, its field guide and its allowed values as MCP resources; agents get a prompt only for a task under ## Agent tasks; the MCP server never uses elicitation or sampling.
- Built in: every action goes through its feature's service and permission, from the screens, the API and agents; each feature's create takes a retry key; an agent that a person signs in acts as that person, and the change history records both; reports are named, typed reads in their feature's report service.
- Built in: each UI or API crash writes one error report to the logs.

## Growth notes
- <the growth topic>: <the answer from the client notes or the person>

## Agent tasks
- <the task>: <feature>; inputs: <the inputs it needs>, or None

## Architecture choices
- <the choice>: <its reason, in one plain line> (docs/decisions/NNNN-<slug>.md)

Copy ## Principles into every app's constitution word for word, and ask nothing about it. Do not add it to an approved constitution: the principles reach that app through its briefs, cold reads and reviews.

Make each architecture choice yourself, from the answers and the code, and write it as one plain line with its reason and its decision record. Record the stack that the code already uses. For parts with no code, use the stack default: an Nx workspace with React (Vite and React Router), NestJS and Postgres. Use a FastAPI backend only when the person asks for it. A FastAPI backend starts from forge init --starter python. Each architecture choice names an accepted record in docs/decisions/NNNN-<slug>.md.

Follow the database skill (.codex/skills/database/SKILL.md) for new tables and extensions. Follow the agents skill (.codex/skills/agents/SKILL.md) for any agent, chat, voice or in-app assistant work. When a part adds one, the plan names the skill's sections that apply and the model service it uses. An extension outside its allowed list needs the owner's OK for this client. Ask the owner, then write - Postgres extension <name>: the owner allows it on <date> (<why>) under Architecture choices in docs/product/CONSTITUTION.md.

A live app answers both halves at adoption: ask only what the code cannot answer. Record the stack the code already uses.

Create and accept a decision record for each architecture choice with forge decision new <slug> and forge decision accept <slug> --by "<name>". Create the constitution approval record with forge decision new app-constitution. When the brief answers what the app is for, who uses it, what it keeps track of and whether its data is private or regulated, and the constitution has its defaults and accepted architecture records, run forge decision accept app-constitution --by "<owner>". The owner approves the constitution once. If it is not ready, fill in the missing answers, defaults or decisions, then run the accept command again.

No new story starts until the owner approves the constitution. Existing stories, parts and fixes continue, and prototype work is not held. When forge next has a worktree cleanup step, finish cleanup first. The constitution reminder returns after cleanup. If the constitution is missing, start the constitution fix Forge names with forge fix start "Write the app constitution" --done "The owner approved docs/product/CONSTITUTION.md", then complete it before starting a story. A review or cold read in an app with an approved constitution checks work against its text; a conflict is a P1 finding or note. A change to documents only keeps its light review, which holds only P0 findings.

Adopt a live app

An app already in production joins Forge through one fix and one pull request the human merges. Adopting changes no app code.

When initializing an existing repo, answer "Does this repo run GitHub Actions?" from the repo's settings: gh api repos/{owner}/{repo}/actions/permissions gives enabled. Ask the human only if the settings do not answer it. Pass --actions yes or --actions no; omit --checks when the answer is no.

  1. Follow App constitution for both interview halves together. Ask only what the code cannot answer, and record the stack the code already uses.

  2. Write the codebase report in docs/context/codebase.md: the stack and how to run it; the tests and the areas they don't cover; the CI workflows and the checks branch protection requires; what a merge triggers; branch rules and reviewers; the hook tools already in use; secret files and variables by name only, never their values; where personal data lives; the migration tool; and the danger zones.

  3. Mark as danger zones the most-changed, most-reverted and most-hotfixed files in git history. A story that touches one names it under Risks.

  4. List the open pull requests and branches in the report, so a story's plan avoids colliding with a teammate's work.

  5. Link or import the repo's existing decision records and design docs as Forge decisions: forge decision new <slug>, the original linked or copied unchanged under the line Imported from <path>. Don't rewrite them.

  6. Read the review comments of about the last 100 merged pull requests. Each rule reviewers enforce repeatedly becomes a House rule citing the pull requests it came from.

  7. Ask the human three questions, one at a time, with options: who approves stories, who merges, and what must never be touched.

  8. Write the answers and the reviewers' rules under ## House rules in AGENTS.md, outside Forge's block. The repo's own rules win where they differ from Forge's default-stack conventions. A rule every review must follow, such as which tests a kind of change needs, goes under ## Review rules in AGENTS.md, outside Forge's block: every review reads that section from the default branch and follows it. A repo keeps its rules in AGENTS.md only: forge sync moves a CLAUDE.md's own lines into AGENTS.md, outside Forge's block, and deletes CLAUDE.md, since Claude Code reads AGENTS.md itself.

  9. Propose a fast_test for the repo: its own test command, keeping its configuration and setup, with the test tool's built-in changed-only option and {base} (for example vitest --changed {base} or jest --changedSince {base}). Once the human agrees, set it in forge.toml through a fix. For pytest repos, propose forge test --pytest {base}: Forge runs this picker outside the project, keeping the repo's full test command and narrows pytest to changed and module-related test files. Use the repo's Python runner for test; keep Forge outside the project, and keep lint and other checks in test. Forge writes no fast_test by itself. When fast_test is missing, forge doctor and forge upgrade print one suggested fast_test line: Python gets forge test --pytest {base}; vitest and jest keep the repo's own install and runner, adding --changed {base} or --changedSince {base} and --passWithNoTests. Mixed repos get one part per kind. The Python picker skips the JavaScript test runners, checks and installation steps, including when shared Python inputs require all Python tests; the suggested Node part installs first and runs its checks once. Forge's generated optional-package wrapper is kept around the Node suggestion. npm exec keeps its own options before -- and its runner after it so npm forwards the changed-file flags. Go, Rust, Java, .NET and Ruby get no suggestion. Check the suggestion against the repo's setup before agreeing to it; other shell flows need the agent to adapt the command.

  10. When the repo has a Dockerfile, write services.toml as the deploy paragraph at the top of this guide says, get the human's confirmation, and add it to the adoption fix.

On a live app, every story and fix also follows these:

  • Before changing code no test covers, first add a test that pins today's behaviour.
  • Migrations work with the previous release still running: expand, then contract. Add a new table or column before code uses it. Keep the old table or column for two releases, and remove it only when no live release uses it.
  • Close checks only migrations added by the change, and skips the check in a prototype repo with no live release. A migration that drops or renames a table or column, or changes a column's type, needs a -- Contract: ... line in Prisma SQL or a # Contract: ... line in Alembic Python. The line explains why the live release no longer reads the table or column; close allows it when the line is present, and review reads the explanation.
  • A change users would notice ships behind the team's own feature flags.
  • No production credentials on this machine; never ask for them.

Upgrade Forge

On the default branch, forge doctor --fix commits its dated Forge-files fix with a proof list of its Done-when and the files refreshed by the pinned release's sync. Run forge close <name> as doctor suggests; close starts tests alongside the first review and reports both results. New repos get this at setup, and existing repos get it when they move to this release.

When forge next says a newer Forge release is out, offer the upgrade to the owner. Never upgrade without the owner's agreement. The check runs at most once per UTC day, shares its cache across worktrees, and stays silent when GitHub cannot be reached.

An upgrade is one fix. Its pull request carries the new version and every file Forge keeps in the repo, rewritten by that version. One command does all of it.

Before starting another upgrade, it removes an earlier upgrade's leftover worktree and local branch when its pull request has merged and the checkout has no unpushed or uncommitted work. An open upgrade or local work still refuses and stays in place. Ignored configuration and data also stay in place; only recognized cache directories may be discarded.

  1. Ask which release to move to, recommending the newest.
  2. Wait until no Forge runs are in progress anywhere on the machine. Let workers, plan reads, tests and close finish first. An install during a close can mix two releases. forge upgrade names the items in the machine's lanes and asks you to wait if any remain.
  3. Run forge upgrade <release> in the main checkout, on the default branch. It installs the release, has that release refresh Forge's files in the fix, commits them and closes the fix. Its commit includes the upgrade's proof list; close reports tests and review results together. It changes only the version in forge.toml.
  4. When it refuses, follow its Next: line. Running it again picks up where it stopped.
  5. Close's last line says who merges: the human, or forge merge <fix> when the repo allows it.

Close runs the time-story backfill as part of the upgrade, with nothing extra to type. It rebuilds open items and items merged in the last six weeks from git and GitHub, marks them "rebuilt from history", and leaves spans without evidence unknown. If GitHub cannot be reached, the next close on this machine retries the backfill. The agent then opens the board and reports what is still missing.

A repo pinned to a release without forge upgrade runs it once through uv: uvx --from git+https://github.com/knacklabs/symphony-forge@<release> forge upgrade <release>. Until the upgrade merges, the default branch keeps working with the new release installed. After it merges, older branches keep their pinned release and its rules. When Forge says the default branch already pins the installed release, merge origin/<default> into your branch to use that release and its rules.

Refresh dependencies

When the default branch's lockfiles and Dockerfiles are over a week old by git log, forge next lists a refresh fix with its forge fix start command; start it like any ready item. In the fix's folder:

  1. Update dependencies within the ranges the manifests allow (npm update, pnpm update, yarn upgrade (Yarn 1), yarn up -R '*' '@*/*' (Yarn 3+), bun update, uv lock --upgrade, poetry update, cargo update, go get -u=patch ./... && go mod tidy); never raise a range. For Yarn 2, remove only yarn.lock, then run yarn install --no-immutable; keep every package.json unchanged. This re-resolves the whole dependency tree within its declared ranges; recursive yarn up -R starts in Yarn 3.
  2. Pull each Dockerfile's base image at its current tag, or move it to the newest patch of the same tag, and rebuild the image.
  3. Follow the repo's test_place: in local mode, run the related tests through forge test; in CI mode, do not run them here. Commit, then forge close <fix>.

Planning a story

Follow the agents skill (.codex/skills/agents/SKILL.md) for any agent, chat, voice or in-app assistant work. When a part adds one, the plan names the skill's sections that apply and the model service it uses.

A cold read passes when no note is at the bar (blocking): the plan is wrong, contradicts itself, or a builder could not act on it. Readers prefix numbered findings with Blocking: or Advisory:. Advisory and below-bar notes need no disposition from you or another round; unmarked findings block in round one. A cold read stops after round two when notes below P0 are still open, or when P0 notes are open in two rounds in a row. New repos get this rule on init; existing repos get it on upgrade plus sync.

Start with forge start "<their words>". A spec is optional: when Forge chooses a plan, its branch adds its missing roadmap entry, so no separate roadmap fix, spec read or spec confirmation is needed. The entry travels with its tasks to the default branch, including tasks waiting on another story. Write the problem, today's workaround and its cost in the plan's Why; the title seeds that section. Run the story's one cold-read loop, then get its one approval. A story already linked to a confirmed spec keeps that link and follows the same story read and approval as before. Promotion keeps the fix's existing roadmap entry: before using --from-fix, put that entry on the fix's branch and commit it. Promotion adds no entry or separate roadmap copy. Forge refuses promotion while the fix's roadmap has uncommitted changes. New repos get this guide at init; existing repos get it after upgrading Forge and running sync.

End each item of a plan's What changes for you with its source in plain words, such as (Source: the client's note on late orders, 12 Oct), or with the sourced item that needs it, such as (Needed by: the Customers screen). A source is a client note in docs/product/DISCOVERY.md, an answer in docs/product/BRIEF.md, a decision in docs/decisions/, a screen marked Build now at sign-off, a fixed rule in docs/product/CONSTITUTION.md, or the human's own request with its date. An item that has a source needs what it cannot work without: its validation, an error or empty state, an index for a filter that a list shows, or accessibility basics. Name each screen, field, endpoint, tool and setting that the plan adds in one of these items. Plan nothing that a source asks for but the work does not need: Is it needed at all? Deleting beats fixing. The canvas can show later screens; plan only the screens marked Build now. The starter's fixed rules are the base: plan nothing beyond them just in case.

Readers should return plain No findings. alone when a read finds nothing. forge read also accepts replies with at least one no-findings line when every numbered or bulleted item says there are no findings, ignoring other note lines. A numbered or bulleted blocking finding in round one still needs a disposition and another round.

Handle cold-read rounds yourself: amend the plan and write dispositions. Once a read is clean, before Plan Mode approval, list the choices you made alone answering notes in all rounds: each amendment that picked a behaviour, a limit, a refusal or a scope cut. Grill the human one question at a time, with your recommendation first. Record each answer in the plan, then ask for approval. Plain factual corrections need no question.

Use one framing line before showing a story in Plan Mode: Approving: <title>, <n> parts, <risks>.

The owner approves only the top of the story doc. Show it from its title down to ## For the builders, unchanged, in Plan Mode and exit Plan Mode with that part as the plan; in Codex, show the same part, then ask the approval question forge next gives. A doc with no ## For the builders heading is shown whole. The approval binds "What changes for you" and "Done when", so tightening anything below ## For the builders needs no new approval, while changing a result or "What changes for you" does. Once a round of cold read has passed, an edit only below ## For the builders needs no new round either, and forge read refuses to run one; an edit above it does. From round two, for stories, specs and decisions alike, the reader checks whether each earlier finding is closed and raises anything new only for a P0 that happens in normal use. A round-one fix that breaks the main path counts as a P0. Blank question replies count as unanswered. The approval hook's pin notice appears only after recording succeeds; if the repo pins a newer release, install that pinned release.

  • Done when: a few results the client or their user can observe, each answering the plan's Why and, when linked to a spec, tracing to its behaviour or success measure. Each item is one bold plain sentence and nothing more, with no code names, file paths or test names. "Code exists" is not a result.
  • Keep new story plans to at most six Done when items. forge read refuses larger plans in one line asking you to split them into smaller stories; already approved larger stories stay as they are.
  • Each item's evidence, edge cases and the test or check that proves it go under the same number in ### Done-when details, the first section under ## For the builders. Workers and reviewers get the entries of the items their task covers. An item with nothing to add has no entry.
  • Done-when or Notes names supported inputs and exclusions. A dismissal that narrows those inputs needs an amendment to the story doc before the review can close.
  • Put Risks right after Done when, then the ## For the builders heading, so the owner's sections come first and everything for the agents sits below.
  • Tasks: each row names the Done-when items it Covers, its Scope (the paths it may change) and its Tests. A task that covers nothing is cut; work wanted later goes to Notes, or the linked spec's Out of scope.
  • The Tests column names one end-to-end case per Done-when item that changes runtime behaviour, and none for settings, docs, deletions or test-only items: the check the item names proves those.
  • Keep tasks small: at most three Done-when items and about 400 changed lines each, counting additions and removals outside tests, Markdown files, docs/, plans/ and .factory/. Close stops a part over 400 lines before review; split it, or record an allowance with a reason by running forge fix allow-large "<reason>" in the part's folder, the same way fixes work. Stories approved before this limit keep working.
  • Keep each part's builder notes to its goal, files and one test per rule.
  • When a plan adds a new feature to a TypeScript or Python starter app, name its plural, singular and fields in the builder notes, and give the worker's first-step command: forge task feature <plural> --one <singular> --field '<field>:<kind>'. Quote every --field value with single quotes, including simple fields. For example:
forge task feature clients --one client --field 'name:text'
forge task feature jobs --one job --field 'name:text' --field 'description?:long-text' --field 'status:status(planned|active|done)' --field 'client?:link(clients)' --field 'hoursBudget?:number' --field 'budget?:money' --field 'dueDate?:date' --field 'contactEmail?:email' --field 'billable:yes-no'

A feature needs a text field for its name. The nine kinds are text, long-text, number, money, date, yes-no, email, status and link; ? makes a field optional. Add other kinds by hand. The worker runs the command; the human makes no technical choices and never types it. Keep the generated organisation checks, change history and five permissions. If the plan changes the role permissions, name that change for the worker to make in the feature's permission mapping. A non-starter or older starter missing the required parts gets a refusal naming what is missing; build that feature by hand in its existing layout.

  • In a starter app, each feature's agent tools are in its one tools file (libs/<plural>/server/src/<plural>.tools.ts, Python apps/api/app/<plural>/tools.py), and the feature command writes it. When a story adds a route that agents need, the part names the hand-made tool, its name in the form <feature>_<verb>, its permission and its annotations. When the owner wants a default tool hidden, the part says so. Every generated or hand-made tool follows these rules:
    • Names start with the feature. A description says what the tool does, when to use it and how it relates to the other tools, as for a new user.
    • Arguments use snake-case names, with <singular>_id for an id. Strict input and output schemas come from the feature's own schemas.
    • Use a search tool, not a list tool: query, the feature's filters, limit (20 by default, up to 100) and cursor. Return next_cursor, how many match in all (more than 10,000 when capped), and how to narrow the search.
    • Return useful fields with names beside ids, response_format set to concise by default and detailed on request, structuredContent with an outputSchema, and a short summary.
    • Return failures as tool results with isError, a plain message with the fix and an example where useful, kind, retryable and retry_after_seconds. Never return HTTP statuses or traces for tool failures; protocol errors are only for an unknown tool or malformed request. A missing or revoked agent key is an HTTP 401 before any tool call.
    • A create accepts idempotency_key; update, delete and restore are safe to run again.
    • Set all four annotations: readOnlyHint, destructiveHint, idempotentHint and openWorldHint, to describe what the tool does.
    • Limit each call to 10 seconds, then return a retryable unavailable error. Limit each key to 120 calls a minute per running API copy. Both errors say how many seconds to wait. Work can still finish after the time limit, so a create's retry uses the same key.
    • Keep the contract case every tool has a description, annotations, schemas and the error shape passing for every tool, including tools added by hand. Only starter apps get the MCP server. Upgrade and sync deliver this guidance without app files or a services.toml change. In an older starter without MCP support, the feature command builds the feature without tools and says so.
  • When a part adds a create or a send, pay or notify action, it says that the action takes a retry key. When it adds a report, it names the report, its result fields and their units. When it lets a person sign an agent in, the agent acts as that person, and the part names the agent's viaAgent text.
  • Each feature's resources come from its tools file's defineFeatureTools and need no code. When a story builds or changes a feature that a task under ## Agent tasks names, the part names its prompt: the name in the form <feature>_<verb>, its inputs and its permission. Write no other prompt. Add no elicitation and never use sampling.
  • Shared seams first: when two tasks share a function, field, file format or command, the first task pins it. It commits the shared names and stubs plus one test that crosses both sides, and the tasks that use it list it under After.
  • Aim for parallel work: split tasks so each owns its files. A line several tasks would edit (a command-table row, a guide list or a registry) goes to one task, or to a small last wiring task. Use After only when a task needs another task's code.
  • After names this story's tasks by ID; name another story's task as KEY/TASK (for example TURN-1/T4). forge next and forge task start hold the task until that task's pull request merges.
  • End the Tasks section with its one New moving parts: line (see Build simple).
  • Risks names every one-way step: deleting data, a destructive migration, a new vendor.
  • Use the stack already in the repo. Ask the human only when options differ in cost, lock-in or reversibility. Replacing behaviour deletes the old path in the same story.

The roadmap. Order stories by value, not by layer. The first story is the smallest slice, end to end and usable by the client; it brings only the setup, sign-in and data it needs. No setup-only, platform or "foundation" stories. Cut a story that answers no problem in its Why; when linked to a spec, it must also serve that spec's behaviour.

Start every task and fix forge next lists as ready at once, and close each as its worker finishes. One machine runs agents on half its available cores (at least one; work rounds, plan reads and close reviews), across all its repos; the rest wait in line. Every run also waits while load per core is 1.5 or more, including when external jobs cause the load, or free memory is below its floor. Busy local CI jobs reduce the available budget until they finish. Items that other planned work waits on go first, first-come within each group; runs print their place and why when they start waiting and each time it changes. Running agents keep their places. A run that dies frees its place once its agent ends. A waiting run is working as meant: keep watching it. When a fix changed a story's plan on the default branch, forge next and forge task start say so with the command that merges it into the story branch; run it, then carry on. Forge compares content history, never commit dates: an older copy on the default branch does not block a start. When only the story doc differs, forge task start merges the default branch into the clean story branch itself and says so; conflicting edits still need the printed merge command. When a story pins an older Forge release than the fetched default branch, forge task start first merges that default branch into the clean story branch so the task uses the upgraded release and rules. Resolve any merge conflicts in the named story folder, commit the merge, then retry task start. Matching release pins need no upgrade merge. This also works from an older story checkout or a stale default checkout: task start handles the upgrade before Forge can forward the command to the checkout's older release. forge next names the unmerged item a part waits on for overlapping files, including another story's work, using the same overlap rule as forge task start.

Cold read findings

Settled findings stay settled. Readers and reviewers receive all earlier rounds' notes, answers and fixed or dismissed findings, including the coordinator's recorded work notes. Forge ignores repeats of disposed cold-read text or a fixed review title and file; they cannot block or start another round. A dismissed finding stays dismissed while the line its file:line cites is unchanged, even when it moves; once that line changes, a repeat of it blocks again and needs a fix or a new dismissal. A new defect needs distinct text and new evidence. Human acceptance still expires when the accepted version changes. New repos get this on init; existing repos, including earlier adopted ones, get it on upgrade and sync.

  • Unproven: item <n>: <case> or Trap: <trap>: item <n>: add the case to that Done-when item and its test to the Tests cell of the task that owns it. Never resolve one only in Notes.
  • Not needed: <what> on a large addition: name the item's source or the sourced item that needs it, or cut the addition and add one line for it to plans/ideas.md. On a small addition it is advice: act on it only when that is cheap.
  • A finding inside the cold read's "What counts" boundary is cut (fix the doc) or deferred, never kept as unnecessary. keep only a finding outside the boundary or factually wrong, and give the reason as the Leave out line it falls under, the Raise line it lacks, the cited fact that disproves it, or the human's Decided: line.
  • Disputed keep <n>: <why>: the reader still disagrees with a finding you kept. Put it to the human as one question with options, record the answer in the doc's Notes as Decided: <finding>: <answer> (owner, <date>), and give both the kept finding and the disputed one the disposition keep citing that line.

Learn the traps. Before closing the story's last task, look back at the story's review rounds. For each kind of finding the plan missed that cost two or more fix rounds or hit two or more tasks, add one trap line to the ## Known traps section of the repo's AGENTS.md, outside Forge's block, in the last task's worktree; create the section when it is missing. Commit it before closing the task. Every cold read checks plans against that section. Count the story's items' entries per file in the spotted records on the default branch, open or done; each file with three or more, or one a task was stopped on (its state's stop), gets one trap line naming the file and the kind of problem that kept coming back.

Steering a Codex worker

Before forge work can start a worker, resolve any unfinished merge in the item's checkout and commit the merge, then rerun forge work <item>. Work refuses before changing its start record or the index, so both conflicted and resolved but uncommitted merges stay intact. New repos get this guidance at init; existing repos get it after upgrading and running sync.

When forge work says Claude ended without running a turn, no worker ran: Claude is often waiting on a prompt, such as a review of changed hooks. Run claude once in the item's checkout, answer its prompt, then run forge work <item> again.

Every Codex worker, plan reader and review runs with low model verbosity, no reasoning summaries, and a developer instruction to write no progress commentary, only the final handoff and any question. Forge sets these for each thread, including resumed threads; neither forge.toml nor user or project Codex settings can turn them up. New and upgraded repos get this automatically, with no setting to change.

When one sentence would help a worker finish its next round, give it with forge work <item> --note "<text>". The note appears under "From the coordinator" in that round's brief and is recorded with the turn. Give it again if a later round needs it; a note does not change the item's Scope. Read a close finding and the code it cites before deciding whether the worker needs a note or the finding should be dismissed with evidence.

If a worker ends with a Question: paragraph, answer with forge work <item> --note "<answer>". The worker waits for that answer: another work round without a note and forge close <item> both refuse until the answering round completes, and forge next shows the question with that answer step. The answer returns to the same conversation when it can resume; a fresh brief carries both the question and answer. Workers change files outside Scope that the change needs and name them in the handoff, so answer a Scope question only when the change isn't needed. If the answer needs a choice the item does not settle, get that choice made before sending the note.

When a round ends with changes left uncommitted, forge work continues the same conversation once, telling the worker to commit first. In a local-test repo, the worker runs related tests through forge test once at the end of the turn, reruns only failures and commits any fixes. In a CI-test repo, the worker commits first and leaves the change's related tests, lint and type checks to pull request CI; it runs forge test only for a test it is writing, to watch it fail. Only if changes are still uncommitted after that does it warn, naming them: forge close reviews only what is committed, so look at them before closing.

Continued worker rounds repeat the selected test-place instruction and the commit-first order, replacing any earlier full-suite instruction. Follow the repo's test_place choice when applying the test-audit skill. The nightly full run covers the rest.

Forge trusts its own Codex hooks for each turn it starts, so the guard runs even after a hook changes. Codex runs no project hook in a project it doesn't trust, so Codex workers and readers all refuse there until the human trusts it. Forge needs an exact trusted [projects] entry in Codex's config for the project folder or its main checkout; a parent folder's entry does not count, and the refusal and forge doctor name the entry to set. A project hook that isn't Forge's and that Codex doesn't trust stops every Codex turn; ask the human to review it in Codex's /hooks, then run the command again.

For a quick read-only question about this checkout, hand it to the session's explorer subagent role. It uses the explore entry in forge.toml; no separate Forge command is needed.

Each kind in forge.toml's [models] table may have a codex and a claude entry, such as [models.build.codex] and [models.build.claude]; a single entry counts only for its own model's tool (a gpt model is Codex's, any other Claude's). Workers use their selected tool's entry, and explorers use their host's entry. Workers and Claude plan readers with no entry use Forge's defaults. Codex build, fix and lite default to gpt-6.1-sol at medium effort; design uses its design default. Claude workers use model and effort and ignore the Codex-only subagents and subagent_effort keys.

After forge sync, Codex sessions opened in the repo can use every shipped role, including fixer. Sync keeps the team's other registered roles. The default subagent model and effort come from build's Codex subagents and subagent_effort, falling back to lite's pair when build has none. With neither pair, sync leaves the defaults unset. Hands-on Codex roles use that same pair when set, so their worker model does not override the subagent budget.

Claude implementation (build, fix, lite and design, including frontend) defaults to claude-sonnet-5-5 at xhigh effort. Claude plan reads (grill) default to claude-opus-5-5 at high effort, including when Claude is the only installed tool. forge init writes these Claude entries explicitly; omitted entries use the same built-in defaults. Claude planner and architect roles use the plan-read entry; debugger, security and performance roles use Opus 5.5 at high effort. Autoreview owns every review's model and effort, including prototype sign-off.

Existing model entries stay unchanged on upgrade. To opt in, set each of [models.build.claude], [models.fix.claude], [models.lite.claude], [models.design.claude] to Sonnet at xhigh. Set [models.grill.claude] to Opus at high. For a missing implementation entry, add a top-level line before the first table, replacing build with each kind: models.build.claude = { model = "claude-sonnet-5-5", effort = "xhigh" }. For plan reads, use this line: models.grill.claude = { model = "claude-opus-5-5", effort = "high" }. If build, fix or lite has a single entry, move it to its family's table before adding the Claude entry. Then run forge sync to refresh subagent roles. Legacy review entries stay in existing settings but no longer override Autoreview's defaults.

With tools = "both", forge.toml's workers says who builds each task and fix. forge work prints the worker, model and effort it starts with, and why; forge next names the worker beside each ready task:

  • codex: everything on Codex; user-facing work uses [models.design.codex].
  • claude: everything on Claude; user-facing work uses [models.design.claude].
  • split (what forge init writes): user-facing story tasks on Claude, everything else on Codex.

Split routing also applies in Forge's source repo: a task row's User-facing: yes selects Claude and its design entry. New repos get this at init; existing repos get it on upgrade.

When the worker changes between rounds of one item, the next forge work starts a fresh session on the new worker with the whole brief and the latest review findings.

Each task and fix keeps the same worker chat for each tool until merge. Later rounds resume it after model or effort changes, restarts, missing local records or a fresh worktree. Plan reads keep their reader chat across rounds too. Forge starts a new chat only when the tool reports the old chat gone or archived, another program keeps it held after one retry, or the item changes tools, and says why in one line. A work round starts the new chat with the whole brief, as a cold read does. Other resume errors stop the round and keep its chat. Upgrade's sync commits earlier-release chat bindings in their owning work branches, without accepting reader findings. It also recovers turn logs when local JSON is missing, moved spec worktrees and workers on forge/ branches. A landed spec's binding travels in the upgrade fix's existing notes. Sync never waits for other items' runs: it leaves the chat of an item whose forge work or forge read is running to that run, says so in one line, and refreshes everything else. Sync writes every generated file first, then keeps chats. When it can't keep one (a run on an earlier release holds it, its record has uncommitted edits, several worktrees hold a moved spec, or Codex has no starting commit for a log-only spec read), it prints a warning that names the item, the reason and the next step. It leaves that chat on this machine only, so run forge read <slug> or forge work <item> on its own branch before you remove .git/forge. Keep .git/forge until the upgrade finishes.

Codex app-server turns use the selected model's Fast tier for every fix, from the second worker round onward, or when other planned work waits on the item (the board's waits_for dependencies). Other turns run at normal speed; models without Fast also run normally. Fast uses more of your Codex allowance. The optional codex_fast setting in forge.toml defaults to "needed"; choose "off" to disable Fast or "always" to request it on every Codex turn. forge doctor shows the policy and whether the last Codex turn ran with Fast. When Fast is wanted but the model does not offer it, the run's first line says so. Every turn, including capacity retries, records the requested tier and the tier Codex reports; an unreported tier stays unknown. New repos get the default at init; existing repos get it on upgrade without adding a setting, and sync refreshes both host guides. The upgrade's sync announces the default in one line.

For a side job inside your own session, hand it to one of Forge's subagent roles, which forge sync writes for both hosts from forge.toml's models: explorer to read and trace code; planner and architect for planning and design choices; debugger, security and performance to diagnose; worker, coder, frontend, tester and refactorer to build. The diagnosing and planning roles change no files. Building an item still goes through forge work. To change a building or planning role's model or effort, change forge.toml and run forge sync; Claude diagnostic roles keep Opus at high effort. Codex roles use their host's entry when the kind has per-tool entries. With a single entry, a model from the other tool is omitted so the Codex role uses the session's model. Claude building roles use their implementation entry or Sonnet xhigh; Claude planning uses grill or Opus high, and Claude diagnosing always uses Opus high.

The explorer role uses the read-only explore kind; lite keeps a fix's first build round. New repos get [models.explore.claude] with claude-haiku-5-5 at high effort and [models.explore.codex] with the same settings as the initial lite Codex entry. Existing repos without explore keep using lite for read-only work after upgrading and syncing. To choose separate read-only settings there, add the explore entries in a fix and run forge sync.

Build simple

Git merges the roadmap and spotted list with forge hook merge-roadmap from PATH. Edits to different fields of an item merge; competing edits to the same field need a manual resolution. New repos get this rule at init; existing repos get it with forge sync or forge doctor --fix. The shared rule keeps working after a worktree is removed. Doctor repairs both paths in Git's shared local attributes, including when an older repo's tracked attributes only name the roadmap, without enabling excluded git hooks.

Read standards.md beside this skill for Forge's principles, the client's app rules and the build-simple ladder. Apply its rules in every phase.

Finding forms. Simpler: <cut> → <replacement> is P2, or P1 when a new moving part isn't named in the story. Untraced work: Cut or defer: <item>. An unmet Done-when item: Not done: <item> (P1). A missing rule-11 basic is its own P1, never Simpler:. A Not done finding blocks only when it names a Done-when item the change misses as written. A case the Done-when does not name, such as another language, platform, shell, tool or folder layout, is a P2 Follow-up: <the case> finding that Forge records and the merge doesn't wait for, even when the case is realistic; a functional, security or data-loss defect on the changed code still blocks under its own Raise line.

Dropping a fix

Only the human decides to drop a fix. Ask first and run forge fix drop <fix> --because "<reason>" only on a clear yes. Forge pushes the fix branch, records the reason, who and when on that branch, and closes its pull request with the reason. The fix leaves the board, forge next and the merge queue. Its name stays reserved and its remote branch keeps the work.

Forge removes the clean worktree and local branch unless ignored files outside rebuildable folders keep it. It lists those paths and keeps their data. Commit uncommitted changes first; wait for local Forge runs to finish and drop or finish any open fixes stacked on this fix. A merged fix cannot be dropped.

Run forge fix drop <fix> --undo to bring it back. Forge restores the worktree or reuses the kept one after a fast-forward, records the restore and reopens a pull request the drop closed. A fix that was ready needs forge close again for its new commit. Teammates see a drop or restore after they fetch.

Closing

A fix gets one review round; a story part gets two. At the round limit, open findings below P0 stop the item and ask the owner: one more round, split or accept. Accept makes the open findings follow-ups in Known issues with the owner's reason. An open P0 never stops for the owner; the worker keeps fixing it. When the same P0 stays open two worker rounds in a row, Forge stops with the stuck question.

From the second review round, close checks the previous round's findings and new P0s that happen in normal use. The reviewer says fixed or not fixed for each previous finding. A round-one fix that breaks the main path counts as a P0 (Harm 4 or more). Open findings below P0 need the owner's choice at the limit, whether their lines changed or not. A Case: rare finding does not block unless it names a kind the owner requires. A security hole or data loss that any user can reach is normal use. Reviewers and readers do not raise questions that Review rules or recorded owner decisions (docs/decisions/, Decided: or Ruling:) already settle. A worker can decline a finding in a commit body with Declined: <title> (<file>): not worth the code - <reason>, using its exact title and file. An empty commit is fine. Close applies it even to a reused review and keeps it as a follow-up. A P0 stays open. Later rounds receive the decline as settled. Only the human dismisses findings. Not done blocks only against a numbered owner-approved Done-when item (a fix uses its Done-when line). Builder details, Notes and implementation guidance never block as Not done. New repos get this guide on init; existing repos, including earlier adopted repos, get it on upgrade and sync. Close applies the same boundary in every repo.

The owner can set added_work_min_gain to 3, 4 or 5 and always_block to a list of kinds in forge.toml. Record the owner's settings in a fix. They apply after it merges, from the default branch. Adds work: <what> marks a finding that asks for another check, retry, guard or flag. An unfloored fix-now finding below the minimum becomes a Known issue with its scores and reason. Harm 4 or more, Gain 5 and every score floor still block. Must: <kind>: <gap> holds a listed kind at any score, even for a rare case or in a light review. The worker cannot decline it. Only the owner can accept it. Without these settings, new and existing repos keep today's bar. An upgrade adds no setting. Plan reads and prototype sign-off keep today's bar.

Before building a fix, check its brief for the five-code-file limit, interface globs and any recorded allowance; test files and files whose content is exactly what forge sync writes don't count, so an upgrade fix needs no allowance. You may run forge fix allow-large "<reason>" yourself, without asking the human, when the fix corrects one kind of problem in every place it appears and changes no interface. Name that problem in the reason and record the allowance before editing beyond the limit. Anything else needs the human's allowance or a story.

For a follow-up to an unmerged item's findings, run forge fix start in that item's checkout. The fix starts there, and its size and interface checks count only its own changes, including after the underlying item lands. Close first tries a plain merge of the current default branch without changing the checkout. If it is clean, close keeps the branch's commits and merges when the pull request needs it. A clean merge needs no force-push. If the merge conflicts after the underlying item lands, close replays only the follow-up's own commits from its recorded start onto the current default branch and publishes with a lease. It preserves earlier merge edits, leaves unknown remote commits alone, and stops with a replay command if the fix's own changes conflict. Follow the printed replay and leased publication commands before closing again. Otherwise it starts from the default branch and close keeps merging default updates. Follow-up replay and merging default-branch updates before the underlying item lands need Git 2.38 or newer.

Mark generated files such as migration snapshots linguist-generated in .gitattributes, so reviews show them only as counts of changed lines.

Read the worker's final handoff and resolve its stated blockers before forge close. Before close, the worker puts a Proof list: paragraph in its last commit message: each Done-when item you cover and every detail next to the test or check that proves it, or marked missing. Name each test by its pytest node ID or file and write no test results: close checks each named test against its own run and writes the list with only the tests it ran and their results, and marks every line it cannot match to its run's per-test results not checked. Name any other check with its result. Forge copies the list into the pull request and the review prompt. The first review checks the whole list, not a sample: Check every entry against the code and its named proof, including every Done-when detail. Compare it with the covered items so omitted entries cannot hide gaps; report every missing case in that one round, even when a finding already blocks. Keep the whole list current; later reviews check repairs and newly changed lines under the boundary above. When a Done-when item's named proof is a pull request check that has not finished yet, do not report it as Not done if the check is configured to run on the pull request. Close's checks gate holds the merge until that check passes. A failed check or a proof with no check behind it is still P1 Not done. New repos get these instructions at init; existing repos get them after upgrading Forge and running sync. Test fixtures are plain text files, never archives or other binary files. Build an old repo for an upgrade test in the test from a text fixture folder. Close refuses added binary files under tests/ before the review, naming the file to replace. When every file a change touches is forge.toml, under docs/ or plans/, a Markdown file or under .factory/, close skips forge.toml's test command and says so; the review and every named check still run. When a worker round or close's merge changes the item's Forge pin, land and close say so in one line and continue through uv under that release before reading its new settings. Generated-conflict sync and the merge commit check use the new pin too, so the merge finishes before the original land or close command continues. The board and next use failed pull request checks only when they belong to that pull request's current head, including branches that exist only on the remote or are ahead of the local branch. Close and forge next warn when this item and another open branch add the same migration number under a migrations folder. The warning names both items and the number. It does not stop work or need a setting. Forge refreshes origin once and reads its branch snapshot without a checkout. Completed items do not cause warnings, even when their branches remain on origin. Close brings in the current default branch before it tests or reviews. If only files forge sync writes conflict, close takes the default branch's copies, runs sync and commits the merge. A conflict in any other file stops close for the worker to resolve. While waiting for checks, close stops at once if GitHub reports a conflicting pull request: GitHub runs no checks on it. Rerun close to bring in the default branch. Land retries close once itself, including a conflict during its merge check wait; if the merge needs a person, it stops with close's existing conflict next step. If GitHub refuses a pull request body update, close stops after one attempt, including during a history retry. Wait for a rate limit to reset, or fix the reported problem, then retry the command. Close reruns failed pytest files once after a local test failure. If they pass, close names them as flaky and continues. A second failure still stops close. A failure without named test files still stops close.

In local mode, close's tests and review run at the same time. In CI mode, close runs its review while the pull request's checks run, then waits for both. A red test or a P0/P1 finding still stops the round. When the local test command fails, its test report ends with all failed pytest files and test names, including setup, teardown and collection errors, and the full test log path. Close keeps the output for the worker: run forge work <item>, whose brief carries it; forge land runs that fix round itself. In CI mode, close leaves test execution to the pull request's tests check. In local mode, close runs fast_test when set. Otherwise it uses the shipped pytest picker for changed tests and tests importing changed Python modules, Vitest's --changed <base>, or Jest's --changedSince <base> (including __tests__ files). The pull request's CI runs the related tests; the nightly full run covers the rest. Docs and brand-asset changes get the light review, which blocks only on P0 findings. Close and the generated forge.yml skip the test command when every changed path is Markdown (case-insensitive), or under docs/, plans/, .factory/ or .forge/. They compare against the base with the same rule as the part-size check. No setting is needed. The required tests check passes and says docs only: tests skipped. Images and fonts also count as assets, wherever they live. Files marked linguist-generated and docs or assets referenced by tests keep the full review and tests. Forge resolves literal paths from the repository or test file's folder on both sides of the change, including literal path joins, __tests__/ files and shared conftest.py fixtures. CI checks this once before the tests job and skips that job for a docs-only change. One code file runs tests as usual. New repos get this rule at init, and existing repos get it after upgrade and sync. Bare forge test still runs. For other changes, the generated forge.yml runs one tests job with forge test --change. Set groups = 4 in forge.toml to run four parallel jobs with any test runner. Each job runs the tests related to the pull request. Put {group} and {groups} in the test or fast_test command where the runner expects its shard number and total, for example vitest run --shard={group}/{groups}. Forge fills these values for each job and uses 1 for both outside CI. Without a shard flag, each job repeats the same related tests; this is correct but slower. For a direct pytest command with pytest-split, Forge adds the split options automatically. When groups exceeds 1 and test has no {group} placeholder or automatic pytest split, forge doctor and the generated workflow each show one warning. They do not refuse or change the chosen groups. Set groups to 1 or add the runner's shard options to test. Init writes groups = 1 for new and adopted repos. An upgrade adds no groups setting, so an existing repo with a direct pytest command and pytest-split keeps its six groups. Each group then runs its share of the related tests through forge test --change. The required tests check passes only when every job passes; branch protection keeps the same tests check. In CI mode, forge.yml also runs forge test --full once a day on the default branch and supports workflow_dispatch. To start a full run now, run gh workflow run forge.yml --ref <default>. Local mode has no scheduled job; when forge next says the daily run is due, run forge test --full in the main checkout when the machine is quiet. A green full run on the latest default-branch commit is required before pushing a release tag or accepting client-signoff. Everyday merges do not wait for it. A failed scheduled or manual full run appears in forge next with its failed test names. The required forge-pr-check fails a pull request from a branch Forge did not start. Before the first committed review, this check stays pending. A stale or blocked review still fails. New repos get this rule at init. Existing repos get it after upgrade and sync. Redo the change with forge fix start "<why>" --done "<done when>" and copy the commits there. Forge's generated forge/* branches and doctor's dated fix/forge-files-* branches still use the normal check for a Forge record and a current review. New repos get this check at init; existing repos get it after upgrading Forge. Sync refreshes these instructions for both. Each change runs its related tests, lint and type checks once. Close runs the full related-test selection, even when it covers more than a third of the Python test files. Shared test inputs select every test. Bare forge test keeps worker runs bounded: when the pytest selection would cover more than a third of the Python test files, it runs only the part's Tests column or the fix's touched test files, plus always_test, and says "The nightly full run covers the rest." With no named tests it runs only always_test. An explicit forge test --pytest <base> keeps the full selection. New repos get this bound at init; existing repos get it after upgrade and sync. Setup steps such as uv sync and npm ci keep their arguments; no filenames are appended. Forge's initial optional-package Node command uses the same selection once an app adds package.json; with no package it still runs nothing. Go and other runners run the full test command. Compound npm scripts also keep their full command because arguments cannot select each runner inside them. Set fast_test for a different selection. Supported runners with no related tests run none locally. A red CI still sends the item back to the worker. To include fast checks or a wider selection, set fast_test in forge.toml (in a fix), with {base} standing for the merge base with the default branch. Tests that check the whole repo, such as rules over every test file, name no changed file, so the pytest picker never picks them by default. List them in always_test in forge.toml (in a fix), as test files or pytest node IDs such as tests/test_rules.py::test_1_rules; it is empty by default. With the pytest picker, close runs them on every change that includes code or settings, beside the tests it picks. Docs-only changes skip these tests too. Forge passes each entry to pytest, so it runs even outside the paths test names. In a grouped run, pytest-split assigns each entry to one group; the other groups do not fail because they did not run it. A missing file or node still fails. In a grouped run, a mark or -k filter that removes an entry is not reported as missing. In a one-group run, an entry that runs no test, for example because -k deselects it, still fails the run. For pytest repos, set fast_test to forge test --pytest {base}. Forge's own repo uses this shipped picker too. It selects only changed test files and tests importing or naming a changed Python module by its dotted name or file path, such as shop.prices or src/shop/prices.py, including package-relative imports. Filenames and references to changed non-Python inputs do not select tests. After the run it names each changed Python file that no test names. Tests that run the program from outside miss it locally; the nightly full run covers it. It supports root packages and the src/ layout, and pytest's test_*.py and *_test.py filenames. It excludes unrelated tests even when the full command names them explicitly. It keeps the full test command's setup and options, caps pytest-xdist at half the machine's cores even when pytest configuration supplies the worker count, and runs the full command when conftest.py, requirements or lock files change, except for bare forge test's worker bound above. For pyproject.toml, every change triggers the full command unless all changed lines are known-harmless: project.version and project.description, or Hatch's packaged-file include, exclude and force-include settings under tool.hatch.build and its wheel or sdist targets. Multiline values are supported. Unknown sections, unsupported layouts, comments outside safe values and mixed safe/unsafe edits run the full command. Forge's commands and generated tests job set UV_FROZEN=1, so uv consumes the recorded lockfile without rewriting it, including in older worktrees. If you wrap a Forge command in uv run, use uv run --frozen: the outer uv starts before Forge can set its environment. Update dependency locks deliberately before testing changed dependencies. For uv.lock, poetry.lock, Pipfile.lock and package-lock.json, a change only to the repo's own package version selects related tests instead. Forge identifies the root package from the adjacent manifest and its local source; dependency versions, hashes and other lock data still trigger the full run. Missing or unrecognized lock data also triggers the full run. New pytest repos get this proposal at setup; existing repos get the picker and guidance after upgrade. Upgrade never rewrites their test or fast_test settings. Doctor reports an old python -m forge.fasttest setting with its one-line replacement. Forge needs no installation in the project: it loads a temporary pytest hook to exclude unrelated files, including on pytest before 8.2. Test launchers must preserve PYTHONPATH, forward pytest arguments and expose xdist options in the command or pytest configuration. Workers run tests only through bare forge test. It runs fast_test, or test when none is set (using pytest selection when supported), with {base} as the merge base with origin/<default branch>, in the same fair machine-wide test lane as close. It prints the report close keeps and always runs, including docs-only and uncommitted changes. If the remote default branch is missing it says to fetch it; if forge.toml names no test command it says so and exits successfully. Forge sets PYTEST_XDIST_AUTO_NUM_WORKERS and FORGE_TEST_CPUS to the run's allocation from the shared process budget. A worker's session requests at most two processes, so its own pytest -n auto also stays at two. A fixed -n in its test command is capped with --maxprocesses, also outside a worker session. Direct Vitest and Jest commands use the same budget through --maxWorkers. This includes npx (with optional --no-install) and npm exec, pnpm exec or yarn exec. Forge caps explicit --maxWorkers values to the run's allocation, including percentage values. Lower caps and Jest's serial options --runInBand and -i stay as written. Forge does not change npm, pnpm or yarn scripts to set their worker budget. It keeps existing NODE_OPTIONS and adds a Node preload that caps the actual runner's arguments, so package scripts keep their lifecycle hooks and share the same process budget. Those scripts can read FORGE_TEST_CPUS. If the test command runs Vitest through a script with Vitest projects, pass --maxWorkers=${FORGE_TEST_CPUS:-2} on the command line, for example npm test -- --maxWorkers=${FORGE_TEST_CPUS:-2}, instead of setting the cap in the Vitest config. A config cap can leave projects with different maxWorkers but the same sequence.groupOrder, which Vitest refuses. Other runners stay as written. Direct forge test --pytest <base> runs join the test lane and obey its memory floor and budget. A picker inside close or bare forge test shares that run's single lane entry. pytest -n auto honours the first; other runners may read the second. The lane stays taken until the test command ends, even if Forge is killed. forge stop <item> ends a running test or removes a waiting one. When forge close stops on a finding, open the line it cites, and the code that line calls, before anything else. The review's "What counts" list is the boundary, and the bar decides inside it: every finding at the bar (Proposed "fix now" or "fix this round") gets a fix round with forge work <item>, whatever its priority, unless close waits for the owner's choice or a decline makes it a follow-up. A finding below the bar holds only when it names a required Must: kind or this item's own Done-when. Close lists other findings in the pull request as follow-ups or lived with. Harm 4 or 5, or Gain 5 stops the work. Otherwise, Gain 4, or Gain 3 with Harm 3, holds the work only with Work 1 or 2. Every other Gain 3 or 4 finding becomes a follow-up fix, even at Work 1 or 2 when Gain is 3 and Harm is 1 or 2. Gain 1 or 2 is lived with. Never re-score a finding to change its outcome. Dismiss only a finding outside the boundary or factually wrong, with forge close <item> --dismiss <n> --because "<file:line> <why>", where the why names the Leave out line it falls under or the Raise line it lacks, or the file:line is the code that disproves it. Close fetches the default branch but merges it in only for a trial-merge clash, or when GitHub reports the pull request conflicting or behind under its up-to-date rule. Unrelated main changes add no merge. Close checks GitHub's clash state at every look, even with green checks; a clash stops a hand-run close and says to run close again. An UNKNOWN answer keeps it waiting. When close merges the latest default branch, an unchanged branch diff keeps the last review and its dismissals, including --dismiss given in that close command. A changed diff needs a new review. Before reusing serious findings, close also checks the reviewed files and contract against the current head. A repair brought in from the default branch needs a new review, even if the branch diff stayed the same. Land uses the same check through close. Close pushes and opens the pull request before a new review, so CI runs alongside it, then updates the pull request's review block when the review finishes. Ready still needs a clean review and green checks on the final pushed head. After forge fix amend, close also updates the pull request's Done when line. Use --append "<text>" to add text with a space between the old and new text. --done "<done when>" replaces the whole Done-when. Both options show the old and new text before writing, and need --because "<why>". Upgrade close leaves the repo's local git hooks alone, including when it regenerates conflicted Forge files with the newly pinned release. If conflicted-merge handling fails, close aborts the merge and reports the error before retrying. After upgrading, run forge sync to receive the tests workflow's quick pass: it reuses a successful parent tests workflow only when the commit changes Forge's review or test-state record and its accompanying state under .factory/. Any other change or missing passing parent result runs the suite on the pull request merged into its current base. Reuse also requires the parent to include that base; a parent pull request run must have tested that same base. A changed base or missing proof runs the suite again. Other workflows can use the same rule. Init and forge sync ship .forge/records-only. After checkout, add this one step: - {id: forge-records, uses: ./.forge/records-only}. Give the job contents: read and actions: read permissions, and fetch the full history. The runner needs Python, Bash and the GitHub CLI, as GitHub-hosted runners provide. Add the condition below to each later step that can skip the records commit:

permissions:
  contents: read
  actions: read
steps:
  - uses: actions/checkout@v7
    with:
      fetch-depth: 0
  - {id: forge-records, uses: ./.forge/records-only}
  - if: steps.forge-records.outputs.records_only != 'true'
    run: your-build-command

The output is records_only=true only for a push or pull request whose parent passed this same workflow. The step uses Forge's existing close-record rule. Code changes, documentation changes, a failed parent run or missing proof produce false, so the workflow runs normally. For pull requests, the parent must also include the current base and its run must cover that base. Each job that uses the step skips only its guarded steps.

When forge land stops after its one fix round or on a check it cannot fix, judge them the same way: dismiss with evidence, or forge work <item>, then forge land <item> again. A failed CI job whose log names none of the change's files is re-run once per pushed head instead of costing a fix round. Forge's review check follows review recovery, never this retry. When GitHub has not started that re-run in time, land stops; run forge land <item> again, which closes again and runs a fix round if the check is still red.

Keeping work moving

Use forge land <item> for build, close, fix rounds and merge where agent merges are allowed; otherwise it hands the ready pull request to the human. It replaces private landing and CI-wait loops, with bounded check waiting and fix rounds. Land waits for the pushed head's checks while GitHub shows progress or checks remain queued, retrying unreadable or failed answers. It stops waiting on green or failed checks, or after 30 minutes without a check starting, finishing or being replaced while none are queued, and says which checks are still running, missing, or unreadable. Close on its own still waits at most ten minutes while none are queued. When it stops, follow its refusal and the Closing section above, then run it again. Run it in the background and keep watching it. GitHub reads also retry unreadable answers and server errors three times, pausing for one, two and four seconds. If GitHub still does not answer, rerun the command. Not-found and permission refusals stop immediately; writes are never replayed by these read retries. If the branch already has commits after the item's start, land goes straight to close unless you give --note "<text>" to start a worker round with that guidance. The note applies only to the first round this run starts, not later fix rounds. Close still stops for a pending question and gives open findings or failing tests a worker fix round.

Use forge land --all in place of a hand-made merge lane. It takes every task and fix whose status is reviewing, fixing, waiting for checks or ready, and leaves out, by name, one still with its builder, one already merged and one another command works on. It starts them oldest first, several at a time up to this machine's agent places, and merges each one when it passes. A child that waits only for GitHub checks releases its place so another close can start. It takes a place again before it continues. Queued checks keep their place to limit new CI work. A stacked fix follows its base even when its start date is earlier. Where Forge merges, it waits for its base to merge, including a base whose builder or another command still works on it. If the base stops, the stacked fix stops with it. A base outside the run must merge before the stacked fix can enter close. Among changes whose bases are ready, the oldest starts first. A change not yet ready that changes a file an older change in the run also changes waits until that one merges or stops; Forge's own plans/spotted.json and .factory/ files never count. Only one run works in a repo at a time. When two closes in the run collide on git's ref lock while fetching, the run starts that change again once and says so. It runs no fix rounds: a change that needs a worker or the owner stops alone and the others go on. The run ends with Merged (n): ... · Ready to merge (n): ... · Needs you (n): ..., then each stopped change with its reason and next step: forge land <item> for its fix rounds, or, for a stopped change, close's own question and next step, which only the owner answers. Close waits for the owner's recorded choice. A failed CI job whose log names none of the change's files runs again once; a second failure stops that change. Forge's review check follows review recovery instead. Where a human merges, the run closes every change, holds none and merges none, and lists the ready ones oldest first, with stacked fixes after their bases, with their pull request links: merge them in that order.

Start a part with forge task start <KEY>/<TASK>, or a named fix with forge fix start "<why>" --done "<done when>" --slug <name>, then forge land <item>. To guide a round and continue through close and merge, use forge land <item> --note "<text>". To queue several items, run forge land <item> for each in the background, with --note when needed. Forge admits each worker round by machine capacity. Do not write shell queues or private slot loops. --note takes one item and cannot be used with --all. forge work queues work rounds across the machine, while reads and close reviews skip that line. All runs wait for high load to fall and memory to recover. Busy local CI jobs reserve cores from the budget until they finish. Close and worker tests share the separate test lane, except picker runs of five or fewer test files, with one place per four available cores (at least one), so no private slot loop is needed.

If close refuses a conflicted merge, it has aborted the merge. In the item's worktree:

  1. Rerun the merge command close printed. Resolve team-owned content and forge.toml first. Keep the team's notes outside Forge's blocks in shared files such as AGENTS.md; never take one side of a whole shared file blindly. Resolve the structure of shared Markdown, JSON and TOML files so sync can read them.
  2. With the pinned Forge installed, run forge sync to regenerate Forge's own files and blocks. For merge-driver setup or drift, use forge sync or forge doctor --fix.
  3. Reconcile story docs with their current approval and cold-read evidence, preserving the approved user-facing contract and both parts' builder changes. If that contract must change, use the story amendment and approval flow; do not invent or overwrite approval evidence.
  4. Check the diff and remaining conflicts, stage only resolved paths with git add <paths>, and commit once every conflict is resolved. Then rerun forge close <item> or forge land <item>.

Starting is the claim: forge story new, forge task start and forge fix start push the new branch to GitHub after committing its start. The author of that start commit is the person who started the work; the board page and forge board --json show them next to the plan's approver, refreshing GitHub's branches so existing checkouts see new claims. Git is the one record. Git keeps a start tag pointing at the original commit, so its author survives squash merges and work-branch cleanup. A retained fix start tag reserves its name even after its branch is abandoned; another start says the name is taken and uses the next numbered name. Close also publishes retained start commits when an earlier start push failed. A second checkout's task start names the person who already started that part on GitHub. A failed push says so and leaves the work local: teammates cannot see that claim until its branch is pushed. New repos get this at init; existing repos get it when upgraded and synced, including repos adopted on an earlier release.

Use forge assign <KEY> <login> to give a story to a developer by GitHub username. Use forge assign <KEY/TASK> <login> to give one part to a developer. The command writes the story's Owner: line or the part's Developer cell. It commits the plan on the story branch and pushes that branch. If the story has no plan, the command refuses and says to plan it first.

After git fetch origin, forge next reads assignments from the remote story branch. For that developer, it offers assigned ready parts first, then unassigned parts. A part's Developer cell overrides the story's Owner. Someone else can start an assigned part. Task start names its assigned developer in a warning. The board JSON carries owner for each story and part, including parts not started yet. The lead can change assignments below For the builders without another approval. After fetching, teammates can discover the published story before its first part merges. If publishing its approval failed, the approved plan on the default branch after its first part merges takes precedence over the story's initial published draft. Next, start and both boards reconcile published assignments with local builder edits; a locally changed assignment takes precedence, and conflicting other edits need reconciliation. The plan's Owner line and Developer cells are the assignment records. There is no separate roster.

Serialize start commands. When task start refuses for overlapping work or an unmet dependency, keep other ready work moving while that work finishes. Run forge next after each merge to start what was unblocked. Retry a start only after its dependency or overlap clears; follow other refusals' next actions instead of repeatedly retrying them.

Hotspots

A worker or review notes problems outside its change as spotted items, which Forge keeps in spotted records and nobody edits by hand. A spotted item never widens the change in hand, except a bug that blocks it. When forge next names a file that keeps breaking, start its fix command at once, like any ready item, without asking the owner. When forge next says the latest scheduled run of the default branch failed, it names the failed jobs and tests; start its fix command the same way, or follow the step it gives for the fix already started.

Close stops at review round two for a story part or round one for a fix when findings below P0 remain open. It asks the owner: "One more round, split or accept?" Close, land and work show the same question until the owner records a choice. An open P0 returns to the worker. An older release's unanswered stop also waits for the owner. Land prints the owner's question, open findings and failing checks, then exits with status 3 (needs a choice). It does not wait or send the worker back while the stop is unanswered. Record the choice with forge close <item> --resolve <more|split|accept> --reason "<human's choice>". Then run forge land <item> again. Land also prints open findings and failing checks when it stops for another reason or hands a round to a session subagent. More gives the worker one more round. After that review, close asks again if findings below P0 remain open and no P0 is open. Split creates one follow-up fix with every open finding's title and file:line in its why and Done-when. Close records the fix on the item, prints its name, and lets this item continue without those findings. Accept puts every remaining finding of the latest review in Known issues as a follow-up with the owner's reason. Close carries on without checking whether code or the default branch changed since that review. Close still requires green checks; later work needs another review. A stop that asks the owner needs a recorded choice with forge close --resolve. A merge of the default branch or a clean review does not clear it. Close repeats the question. Land runs at most one fix round, including across session handbacks. If findings, tests or checks still need a worker, land stops.

For a user-facing change, before review round two, run the real flow once, without tests or stubs. Add Live run: <what you ran> - <what you saw> to the commit's Functional check: paragraph. Close copies that paragraph into the pull request and review. This request adds no check or refusal.

The stop rule: the coordinator lists each round's findings and asks the owner, whatever the scores. The coordinator asks. The owner decides. The coordinator records the choice. When a cold read or review loop stops and you must ask, ask the human the question it printed, in its words, one item per question. It names the item and what it delivers, shows the open notes as one table ranked by score (number, the problem and who it hits, Gain, Work, Harm, and what is proposed), and says what each choice does; forge next prints it again for a stopped item. When a note has no scores, as one saved before scores, the question lists the notes as plain lines instead.

Every update to the owner that lists notes or findings uses the same table, in the same order. The coordinator follows the bar's outcome and never re-scores a note: a fix round for a note at the bar, a disposition with the scores in its reason for one below it, and the stop rule above at a stop. Approval and merge steps stay the owner's.

When two rounds in a row leave close's tests failing or the same P0 open, forge work stops the item. Close also stops a story part that grows past its line limit. Either prints a ready-to-ask question: what the item delivers, what is stuck, what the worker tried, and the choices. Ask the owner exactly that question. Record the choice with forge close <item> --resolve <switch|same|narrow|split> --reason "<human's choice>", then forge work <item>. Switch runs this item's next rounds on the other tool, as forge next shows; every choice buys one more round before Forge asks again.

A cold read stops after round two when notes below P0 are still open, or when P0 notes are open in two rounds in a row. This applies to stories, specs and decisions alike. Ask the owner: "One more round, split or accept?" Do not read again until the choice is recorded with forge read <KEY or spec or decision> --resolve <accept|more|split> --reason "<human's choice>". More permits one more read round. The same stop rule applies to that round. Accept keeps the notes with the owner's reason and permits approval or confirmation. Split the plan as agreed before reading again. Forge never starts a third round by itself. For a review loop stopped by forge land, record the choice with the same forge close --resolve command as a close stop.

When GitHub reports a clash during the check wait or refuses a merge because the branch is behind or conflicting, forge merge retries close and merge once. A mergeable pull request keeps its current head when the default branch has not moved. When GitHub refuses a branch that fell behind during the merge, forge merge and forge land say so in one line, run close again to merge the default branch and run the tests, review and checks as close decides, then retry the merge. If that merge selects a different Forge release, close finishes under that release before the original command continues. A real conflict stops with close's existing resolution steps. Other GitHub refusals keep their next action.

Check-back

Once every story from a spec is done and its check date has passed, forge next lists the check. Measure it, record it in a fix with forge spec measure <slug> --result "<result>", then ask the human one question. Target met: "Stop here" (recommended) or "Next problem card". Target missed: "Change the slice", "Remove it" or "Find out why". Adding features is never the default. Ask which parts the client doesn't use; each becomes a removal story on the roadmap.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

agents

免费

Agent rules for Forge client apps with a chat agent or in-app assistant. Use before any agent, chat, voice or in-app assistant work: its shape, turn, memory, tools, output, safety, cost and tests.

中文简介正在准备中。当前显示原文说明。

knacklabs/symphony-forge102026年10月29日 更新

The baseline every Forge client app gets without anyone asking. Use for any frontend or UI work in a Forge client repo, including screens, layout, navigation, forms, tables and prototype demos, before building or changing it.

中文简介正在准备中。当前显示原文说明。

knacklabs/symphony-forge102026年10月29日 更新

database

免费

Database rules for Forge client apps on Postgres. Use before adding or changing a table, column, index, constraint, migration, extension, list query, report or job table, in Prisma or SQLAlchemy.

中文简介正在准备中。当前显示原文说明。

knacklabs/symphony-forge102026年10月29日 更新

Start a named Remote Control Claude Code session in a chosen checkout for an action the human must take from the Claude mobile app. Stories in every Forge repo can be approved from the one main chat; use this when another action needs its own session.

中文简介正在准备中。当前显示原文说明。

knacklabs/symphony-forge102026年10月29日 更新

Writes and rewrites text in Simplified Technical English (ASD-STE100), a controlled language for clear technical documentation. Use when the user asks for STE, Simplified Technical English, controlled language, or plain technical writing, and when the user asks to write, rewrite, review, or check technical documentation, procedures, manuals, instructions, warnings, or reports.

中文简介正在准备中。当前显示原文说明。

knacklabs/symphony-forge102026年10月29日 更新

Use whenever writing, changing, reviewing, or auditing tests. Gates new and changed tests and guides focused audits of low-value, implementation-coupled, or duplicative coverage and the test-only production seams it demands.

中文简介正在准备中。当前显示原文说明。

knacklabs/symphony-forge102026年10月29日 更新

查看 knacklabs 的全部技能

报告此技能的问题