All notable changes to forge are documented here. The format follows Keep a Changelog, and forge adheres to Semantic Versioning.
- Project spec auto-loads on clone (cross-machine portability). The agent-root marker block now imports the committed
spec/CONTEXT.md(the/ingest-specproject synthesis) alongside.forge/CONTEXT.md(methodology) — Claude via@import, Codex/Gemini/Cursor via a prose pointer.forge initscaffolds aspec/CONTEXT.mdstub, andforge upgrade/--migrate-claudemdcreate it if missing (symlink-guarded, never overwriting/ingest-specoutput). A freshly cloned repo +forge upgradenow reproduces the origin machine's project context instead of seeing only the methodology. - Release automation. A merge to
mainthat bumpspackage.jsonauto-creates and pushes the matchingv<version>tag, which drives the existing npm publish (with SLSA provenance) and GitHub Release.release.yml/release-draft.ymlare now reusable (workflow_call); the tag is created with the default token (no PAT) and the publish is invoked directly, sidestepping GitHub's same-token workflow-trigger block.
- Compound learnings are tracked. Removed
/docs/learnings/**from forge's.gitignoreso accumulated learnings travel with clones. The npm pack-gate (docs/is a forbidden prefix) plus thepackage.json#filesallowlist still keep them out of the published tarball.
- Model routing / catalog subsystem (BREAKING). Removed the
forge orchestrate routeandforge orchestrate modelsverbs, the/modelsskill, the model catalog (templates/models-catalog.seed.json+ schema), tier arithmetic/escalation, the per-host model pin allow-list, and all per-subagentmodel:frontmatter from the shipped agents. Model selection is no longer Forge's concern: interactive subagents inherit the session model and Forge-owned codex spawns use the host default — the host owns model choice, so nothing goes stale. Schema fieldstask.model_tier,agents.default_model_tier, and themodels:settings block are gone; stale values in existingphases.yaml/settings.yamlare silently ignored (no.strict()), so no adopter migration is required. The host-reachability preflight routing relied on is kept and now surfaced viaforge orchestrate doctor --scope hosts(non-blocking). Note: Claude Code already ignored subagentmodel:frontmatter (anthropics/claude-code#44385), so removing those pins changes no runtime behavior. Supersedes the unshipped FORGE-192 model-threading work.
/audit— read-only repo audit → work-order → issue specs (FORGE-178 P1–P3).forge orchestrate audit planfans out N read-only subagents (one prompt per scope × dimension, injecting path-free principles + the resolved protected globs);audit collectvalidates findings and writes a filteredwork-order.{md,json}under.forge/audits/<ts>/(drops absolute / traversal / out-of-scope / protected paths in code);audit create-issuesrenders one tracker-issue spec per finding for the/auditskill to file out-of-band with classification labels. Scope is auto-discovered from the git tree (never hardcoded), the protected set comes fromCRITICAL.md+ config, and the shipped feature carries zero forge-specific paths — forge dogfoods it via its ownCRITICAL.mdDO-NOT-SIMPLIFY entries +settings.verify.
forge orchestrate <verb> --helpprints real help (FORGE-134) — synopsis + reality-matched flag tables for every verb; value-aware interception;--help --jsonemits an envelope. Previously--helpRAN the verb.- Auto-gc warnings reach machine consumers (FORGE-149) —
phases --ready --json --include-warningsand the newstatus --json [--include-warnings]. reconcile --task <id>scoping (FORGE-119) andreconcile --pull --checklive drift probe (FORGE-123) —Tracker.getCurrentRevision()mints opaque provider-native tokens (source.tracker_revision); a matching check refreshes the freshness stamp and exits without fetching the issue list.- JSONL soft rotation (FORGE-85) —
events.jsonl/claim-history.jsonlrotate atagents.log_rotate_max_bytes(default 10 MiB, single.1generation, interprocess lock-or-skip); readers merge; claim-generation continuity preserved. second_opinionnaming (FORGE-150) —second_opinion.auto_enabled,FORGE_AUTO_SECOND_OPINION,forge second-opinion suggest; every legacy codex-prefixed name still works with deprecation warnings (removal in v0.6);forge migraterenames the block keeping an old-CLI compat mirror.- Tracked
methodology_versionpin (FORGE-161) — upgrade stamps it comment-preservingly; the CLI warns on pin↔installed mismatch. - Doctor symbol-mention drift (FORGE-131) — backtick identifiers in SPEC/PRD/ORCHESTRATOR are checked against the source tree (shape filter + allowlists).
- Ship-time SPEC-change signal (FORGE-164) —
spec-diff --all-activelists active claims predating spec/ changes;/shipadds an informational PR section. OrchestratorError.safeDetails()(FORGE-86) and theoverwriteAtomicLinkENOENT contract comment (FORGE-84).
- Cursor host (FORGE-160).
cursoris now a first-classAgentKind. Its managed artifact is.cursor/rules/forge-context.mdc— YAML frontmatter (alwaysApply: true) FIRST, then a forge marker block that INLINES the rendered methodology context verbatim (Cursor.mdcrules cannot reliably@file-import, so the breadcrumb is self-contained). The file is gitignored and regenerated per machine byforge upgrade(same determinism rationale as.forge/CONTEXT.md), and — unlike the product-owned root files of the other hosts — it is materialized when enabled (created if absent). Skill farm:.agents/skills(Cursor's native cross-tool skill root) +.cursor/agents.forge init's root-file checkbox gains a Cursor option;settings.yamlenabled_root_filesandprimary_host_cliacceptcursor. Cursor as the PRIMARY dispatch host is beta-gated behindagents.cursor_host_beta_opt_in: true(selecting it without the flag is a parse error naming the flag); under opt-in a subprocess-backedCursorHarness(agent -p --force --output-format json,CURSOR_API_KEYpassthrough) drives primary dispatch —runReviewis NOT_SUPPORTED (review hosts stay codex | gemini).forge upgrade --add-agent cursor/--remove-agent cursor,.gitignoreblock entries, and the eject path all handle the nested artifact and the shared.agents/skillsfarm root (prune-safe when another enabled host shares it). - Doctor symbol-mention drift (FORGE-131).
forge orchestrate doctor --scope spec-codenow flags identifier-shaped backtick spans in the spec files that appear nowhere insrc/**/*.ts(missing_symboldrift entries, exit-2 semantics unchanged). This is a bounded mention check, not export analysis: a symbol in a comment or test counts as present, and a renamed export with stale prose is caught only when the old name vanishes fromsrcentirely. A code-shape filter (CamelCase ≥2 humps / camelCase /ALL_CAPS_SNAKE, len ≥4) plus a built-inBASE_SYMBOL_ALLOWLISTand the new adopter-declaredsettings.doctor.symbol_allowlistkeep prose nouns and external names out of the drift list. spec-diff --all-active+ ship-time SPEC signal (FORGE-164).forge orchestrate spec-diff --all-activeenumerates every active task (dispatched|running|blocked_on_question) whose claim predates aspec/change, emitting{ task_id, commit_count, files_affected, lease_expired }[](corrupt/missing state or lease → skip with a stderr note; expired leases still listed withlease_expired: true; no-diff tasks omitted; always exits 0)./shipsurfaces the single-task signal as an informational### ⚠ SPEC changes since this task was claimedPR-body section — it never blocks the ship. Together with the on-resume block this closes both halves of the FORGE-114 SPEC-drift mitigation.
- Symlink-class closure — parent/component guards (one shared
src/core/symlink-guard.ts) now cover the nested Cursor artifact, init scaffold promotion, every host's skill/agent farm dirs (apply/prune/eject — a pre-existing gap for claude/codex/gemini), and.forgeitself (init/upgrade/migrate-claudemd refuse upfront). - Tracker body-mutation hardening (FORGE-118) —
withRetryonupdateIssueBody/setBlockedBy(+ GitHubsetClaimFence); footer parsing unified to the trailing region (fence-aware; mid-body/fenced examples are literal text, never promoted or stripped); duplicate footers dedup last-wins; opt-in claim-token CAS (CLAIM_MISMATCH).
forge upgradedestroyed symlinked agent root files (FORGE-208).writeAtomic's rename replaced the symlink inode itself, so a CLAUDE.md → AGENTS.md parity link became a divergent regular file on every upgrade. The primitive is now default-deny: an lstat preflight throws typedFsWriteError('SYMLINK_TARGET_REFUSED')on symlinked targets (only ENOENT is treated as absent; other lstat failures propagate). Per-surface policy: the recurring upgrade refresh skips symlinked root files / .gitignore with a notice (exit 0, identical in--dry-run); a symlinked.forge/settings.yamlis refused upfront before anything is written;--add-agent/--remove-agentrefuse explicitly;migrate-claudemd's preconditions now cover CONTEXT.md/.version/.gitignore (no partial-state writes);ejectandinitskip gracefully — including eject's forge-createdunlinkSyncpaths and init's staged-promotion bypass, so a user-converted symlink is never deleted. Covered by property tests (lstat file type before == after for every touched path; upgrade twice == once; dry-run parity). Known documented gap: hardlinks still break silently on rename (nlink > 1preflight is follow-up scope).
/wrap-upskill +forge orchestrate gc --remove-worktrees(FORGE-116). End-of-task housekeeping: confirm the merge, close the tracker issue, and remove the task's worktree through the gc planner (eligible/refused/absent classification, lease-health gate, exit 1 on any refusal).gc --prune-merged-branchesopt-in (FORGE-139).workspace.cleanup()can now delete the task branch after merge: leading--pre-screen,git check-ref-format --branchas the authoritative ref predicate, thengit branch -d --(never-D); a refusal is reported asbranchRetainedReason, never an error.owner_type: 'human'for manual bootstrap tasks (FORGE-177). Provision accounts / paste keys / OAuth-consent work can now be modeled faithfully. Human-owned tasks are never auto-dispatched:phases --readyexcludes them from the dispatchable set and surfaces them ashuman_checkpoints(with a⏸ human checkpointline in human output); plainphasesstill lists them.PHASES_PARSE_ERRORnow surfaces the zodissues[](FORGE-176). Human stderr gets a bulleted<path>: <message>list (capped at 20,+N more);--jsongets structurederror.details.issues.
- Unified task-id schema (FORGE-130). All task-id validation now flows through
one primitive (
src/schemas/task-id.ts,/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/): alphanumeric start, then letters/digits/./_/-, max 64 chars, no#,/,\, or whitespace. The CLI args schema, the questions/paths segment validator,workspace.sanitizeIssueId, and the gc worktree-id check all wrap this single source of truth (each keeps its own error codes). This widens the prior Linear-only CLI shape — lowercase ids, no-hyphen ids, phases ids (P2.5-T07), normalized GitHub ids (GH-42), and UUIDs now pass. Two narrowings vs the prior per-site validators: a leading.(oldworkspacepattern) and a leading_/-(oldquestions/pathspattern) are now rejected — both are leading-punctuation path hazards no live id namespace used. - GitHub issue identifier normalized to
GH-<n>(FORGE-130). The GitHub adapter now emits the path-safeGH-42identifier instead of the legacy#42(which the unified shape rejects).parseIssueNumber()reverse-mapsGH-42,#42, bare42, and issue URLs back to the bare number across every native call site (claim, releaseClaim, updateState, comment, createIssue, setBlockedBy, updateIssueBody, setClaimFence), so the legacy#42shape is still accepted on read — existingplans/phases.yamlbindings keep working.setBlockedBy's blocker input is widened the same way (footer still stores the bare number).reconcileseeds legacy aliases (#<n>+ bare<n>) for GitHub issues so--pullnever false-removes a task bound by an old shape and--pushresolves body targets through the same aliases. - UTF-8 byte caps on attempt/verdict string fields (FORGE-83). Byte-budget
fields (output excerpts, summaries,
save_point, finding messages, reasons) now enforce true UTF-8 byte limits viabyteBoundedString(the old.max(N)counted UTF-16 code units — multibyte text could overshoot up to 3×). New code-point-safetruncateUtf8helper;verdict-parserand thecancel/questionproducers truncate byte-safely. Stricter: previously stored multibyte strings over the byte cap now fail re-parse (by design; forge's own producers emit ASCII excerpts). codex.auto_codex_token_capremoved (FORGE-124). The field was RESERVED with no enforcement (a relic of dropped host-level hooks). Legacy keys in existingsettings.yamlfiles parse cleanly and are silently ignored.
forge migrate/upgraderename codemod no longer corrupts deprecation aliases and historical docs (FORGE-207).docs/retros/,docs/plans/, andskills/push-to-linear/are detection-only (warning findings, never rewritten), and a line-level self-replace guard skips any line that already contains the replacement name — eliminating the self-referential "/push-to-tracker is deprecated — use /push-to-tracker" class. Skipped lines are surfaced, untouched content stays byte-identical.- Worktree-lifecycle hardening (FORGE-139/140/142/70).
ensure-worktreeanchors a relativegit rev-parse --git-common-diron the git command's cwd (notprocess.cwd()); hydration skips git submodule boundaries (including a submodule hydration root itself);/pickup-taskinstalls dependencies in the fresh worktree (npm/pnpm/yarn/bun by lockfile). /decomposeemitted invalidphases.yaml(FORGE-175). The skill and the product-decomposer agent now emitacceptance(wasacceptance_criteria, which failed validation on every fresh run) and the skill'stypelist matches the real TaskSchema enum.TaskSchema.statusgarbage-rejection test (FORGE-120) closing the deferred security-review AC (enum itself shipped earlier).
- SPEC §Module layout gains the
src/harnesses/subtree (FORGE-137); the §phases.yamlschema snippet is refreshed field-for-field fromsrc/schemas/phases.ts(FORGE-129); the methodology context template gains the ambiguous-field authority conflict table (FORGE-162); CONTRIBUTING gains the clean-room pre-push smoke recipe incl. the chalk-render check viamigrate --dry-run(FORGE-68).
v0.4.0 ships the closed-loop / ephemeral-ADR workflow: the apply-decision
verb (FORGE-95), the /update-spec --draft|--apply skill (FORGE-93), and the
/amend-roadmap verb + skill (FORGE-101) are all production-ready. Only worker
drift events and the precedence engine remain scheduled for v0.5; the
worktree-drift-guard was canceled.
Also in this release: Notion tracker support via the ntn CLI incl.
updateIssueBody (setClaimFence remains stubbed — FORGE-167) (replacing
the removed MCP transport), forge migrate for v0.2.x project paths, a
greenfield e2e fixture matrix, phases-write lock, and a range of orchestrator
robustness improvements.
- Pre-v0.4 combined
CLAUDE.md— from any repo with a pre-v0.4-shape combinedCLAUDE.md:npm i -g @firatcand/forge@0.4.0 && forge upgrade --migrate-claudemd - v0.2.x project drift — settings blocks,
@inheritinDESIGN.md,/push-to-linearreferences, renamed/dropped orchestrate verbs, missing ADR template, and legacy v1 orchestrator state:forge migrateforge migratepreviews all detected drift, requests confirmation, backs up before applying, and reports complex conversions as manual follow-ups — never invoking the owning tool itself (missing settings →forge init;@inheritstrips →/draft-design; legacy orchestrator state →forge orchestrate gc). - Notion — MCP transport has been removed;
@modelcontextprotocol/sdkis no longer a dependency. The officialntnCLI is now required — install it and authenticate (credentials live in your system keychain; no API-key env var needed):Thecurl -fsSL https://ntn.dev | bash && ntn loginmcp_commandandmcp_envfields in.forge/settings.yamlare now deprecated-ignored (accepted by the schema but not read; will be removed in v0.5).
/amend-roadmapskill +forge orchestrate amend-roadmapverb (FORGE-101) — tracker-first mid-flight task creation with a journaled, resumable amend flow. Features O_EXCL task-id reservation, adoption-on-resume (exactly-one footer match, refuses truncated views), payload-hash mismatch refusal, and an inline drift warning that replaces the dropped worktree-drift-guard (FORGE-103). The new phases-write lock (per-attempt ownership tokens, steal-mutex-serialized takeover, release validity window,assertFreshbefore every write) closes lost-update races — includingapply-decision's previously unlockedphases.yamlwrite path.reconcilegainsstagedAdditions+insertTaskIntoDocumentto keepphases.yamlsingle-writer for mid-flight additions.forge migrate— v0.2.x → v0.4 project migration (FORGE-109) — detects the v0.2.x drift signatures (stale settings blocks,@inheritinDESIGN.md,/push-to-linearrefs, renamed/dropped orchestrate verbs, missing ADR template, legacy v1 orchestrator state), shows a chalk diff preview, requests hybrid confirmation (TTYy/N,--yesfor CI,--dry-run), writes verified backups to.forge/backup-<ts>/before applying, and re-verifies after each write. Complex conversions are REPORTED as manual follow-ups — migrate never invokes the owning tool itself (missing settings → runforge init;@inheritstrips + follow-up marker; legacy state →forge orchestrate gc)./update-spec --draft|--applyskill (FORGE-93) — two-mode skill wrapping theapply-decisionverb.--draft: active-ADR preflight (malformed ADR-shaped files refuse; companions never block), discovery interview, template instantiation, codex-suggest hook.--apply: completed-archive recovery branch before ADR resolution, canonical-file + journal-trust + clean-target gates (tracker scope bounded byaffected_tasks), payload-complete journal authoring with a schema-validated worked example, per-artifact diff confirmation, envelope-driven error handling, exact-footprint staged-patch audit, and a--cleanup=verbatimpropagation commit carrying the full ADR rationale. Clears 36 stale "FORGE-93 still v0.5/pending" qualifiers across SPEC/PRD/CONTEXT template.- NotionTracker via the
ntnCLI (FORGE-117) —McpCallseam replaced withNtnExec(aGhExecmirror; execareject:falsewith resolved-ENOENT re-throw). Every tracker call translated tontn api v1/...— exceptsetClaimFence, which remains a NOT_IMPLEMENTED stub on Notion (claim-fence storage needs a dedicated design; FORGE-167 follow-up) — (stdin bodies, query-arg pagination,NOTION_API_VERSIONpinned to 2026-03-11).updateIssueBodyimplemented (validation contract parity,forge_task_idprecondition, list → delete → append children with documented non-atomic idempotently-re-runnable semantics, sharedbodyToParagraphBlockschunking).classifyNotionExecErrorwith the full official code table including transient codes.@modelcontextprotocol/sdkremoved frompackage.json(user-approved soft deprecation;mcp_command/mcp_envstay schema-accepted but ignored, removed in v0.5).BaseTracker.withRetrynow honors providerRetry-Afterhints (details.retryAfterMs) for all adapters — previously stored, never read.runProbe(init): central fix for execa@9 resolved-ENOENT spawn failures — every probe (git/gh/claude/codex/ntn/op) now reports install guidance instead of a bogus exit message;ntnprobe verifies install + keychain auth in one call (ntn api v1/users/me). - Greenfield e2e fixtures + lifecycle drive-through + CI matrix (FORGE-110)
—
examples/greenfield-{github,linear,notion}: frozen mini-projects (deterministicphases.yaml, tracker-typed settings, spec stubs with resolvable anchors, ADR template) serving as both test fixtures and living documentation.test/integration/cli/orchestrate/lifecycle.e2e.test.ts: per-fixture drive-through gated behindFORGE_E2E_FIXTURE(self-skips under plainnpm test): fixture sanity, claim → dispatch → heartbeat withstate.json/lease assertions, amend-roadmap drift warning, multi-main CAS race (winner +ALREADY_CLAIMEDloser envelopes), fullupdate-specclosed loop (+--resumecrash recovery),reconcile --pull/--pushdrift, migrate smoke..github/workflows/ci.ymlgains an e2e job (matrix Node 22/24 × 3 fixtures = 6 cells).docs/release-checklist.mddocuments the manual pre-release ritual. forge statusverb (FORGE-159) — a read-only, top-level command that reports a forge-managed project's state in one round-trip: methodology version drift (bundled vs on-disk), agent root files (CLAUDE.md/AGENTS.md/GEMINI.mdmarker presence + user-content byte size), symlink-farm provenance counts (forge-owned vs user-owned vs broken under.claude/,.codex/,.gemini/), spec placeholder-section counts,plans/phases.yamlphase/task counts, and tracker/secrets config. Supports--jsonfor a machine-parseable{ ok, data }envelope. Distinct fromforge orchestrate status(which reports orchestrator run-state). Never writes; a non-forge directory returnsmanagedByForge: falseand exits 0.forge orchestrate apply-decisionverb (FORGE-95) — the mechanical applier behind/update-spec --apply. Given an accepted ephemeral ADR and a payload-complete journal at.forge/orchestrator/global/update-spec-apply-journal/<slug>.json, propagates the decision across SPEC §sections + PRD §sections (marker-block replacement),phases.yamltask fields, and tracker issue bodies — journaling each mutation so a partial failure is resumable with--resume. On full success writes a durable rationale (spec/decisions/INDEX.mdline +<slug>.commit-msg.txt), deletes the ephemeral ADR, and archives the journal. Trackers that cannot update issue bodies fail a preflight before any local mutation. Folds in FORGE-163 (durable decision rationale viaspec/decisions/INDEX.md).forge eject— reversible clean uninstall (FORGE-158) — strips the forge-managed marker block from each agent root file, reverses the.gitignore/.eslintignore/.prettierignoreblocks, removes the host skill/agent farms and the.forge/directory.spec/,plans/,CRITICAL.md, and your source are left untouched. Dry-run by default; takes a restorable backup snapshot (.forge.eject-backup-<ISO>/) before deleting; refuses while an active worktree or a non-terminal task state exists, or when a forge-managed file has uncommitted git changes.settings.verify+ real verification runner (FORGE-168) — optionalverifyblock in.forge/settings.yamldeclares the commands that prove an attempt is good (npm test,npm run lint, etc.). Commands run sequentially viashell: true; all run even if one fails; aggregate pass/fail reported. Optional: unset ⇒ verification skipped with a warning.- Release automation (FORGE-157) —
.github/workflows/release.yml(verify-tag → full CI gate →npm publish --provenance) and.github/workflows/release-draft.yml(slice matching## [X.Y.Z]section → create DRAFT GitHub Release). Adopter generic copies attemplates/github-workflows/release.ymlandrelease-draft.yml. forge orchestrate dashboardverb +/status-checkskill (FORGE-90) — cross-run cockpit: active sessions (runs owning ≥1 in-flight task), open questions, ready/blocked tasks, file-overlap warnings, lease health (alive/expiring_soon/stale). ExtractscollectActiveAttempts+isTrackerIdDoneintosrc/orchestrator/readiness.tsshared bydashboardandphases --ready.- Decision classifier + preflight wrapper + question budget (FORGE-65) —
forge orchestrate questionnow runsgateQuestionbefore writing: reuse a prior answer → block on an open question → force an autonomous decision at the per-task hard cap → else write (carrying a soft-cap warning).recommended_option_idwhat_happens_if_unansweredrequired at the verb. Newdecision-classifier.tsandpreflight.ts(extracted fromguardrail-check).{{BUDGET_WARNING}}placeholder wired for FORGE-98 dispatcher integration.
- Claim-time overlap gate (FORGE-170) —
claim.tspre-flight refuses a task whose declaredwrite_globshard-overlap an already-active attempt (retriableOVERLAP_CONFLICT) unless--force.DEFAULT_HARD_LOCK_GLOBSretuned: dropsplans/phases.yaml(generated cache, byte-stable via FORGE-121), addsspec/**+skills/**. - Claim-metadata footer storage + read (FORGE-145 / FORGE-167) —
src/trackers/claim-fence.ts(parse/upsert/strip the<!-- forge:claim={json} -->footer);setClaimFencewrite side on Linear + GitHub adapters;toIssueon both adapters populatesIssue.claimId/claimGeneration/claimOwnerRunIdfrom the body fence. Foundation for gc tracker-divergence rows. Notion write stubbed (lands with FORGE-117, delivered in this release). - Per-repo
.forge/.envauto-load for tracker credentials (#259) —src/core/forge-env.tsloads an allowlisted set of tracker-auth keys from.forge/.envintoprocess.envat CLI startup (no-override, absent file is a no-op, malformed file warns but never crashes).forge initscaffolds a.forge/.envcreate-once. Linear AUTH errors now name.forge/.env.
npm run lintnow runs ESLint (#264, #265) — minimal non-blocking ESLint setup (flat config:@typescript-eslint/no-unused-vars,no-unreachable,no-constant-condition, all as warnings). Thelintscript previously aliased totypecheck. Warnings-only; CI runs viacontinue-on-error; all 23 pre-existing warnings were cleared. Contributor-facing dev tooling only.forge orchestrate reconcile --pullformatting preserved (FORGE-121) — serializes with{ lineWidth: 0, flowCollectionPadding: false }so untouched nodes round-trip byte-stable. A 53-title-sync pull dropped from 1835 changed lines to 112.depends_onlists now edited in-place (preserving block-style formatting and inline comments). Deduplication of trackerblockerIdsat source prevents spurious depends_on diffs on every --pull.forge orchestrate reconcile --pullfalse-pruning fixed (FORGE-165) — matchedtracker_issue_idagainst both trackeridandidentifier; addedTracker.listAllIssues()(terminal states now seen, so done/cancelled tasks are never mistaken for deleted ones). Fail-closed on truncation: orphan detection skipped when the adapter hits its page/limit cap.- Codex review harness: diff via stdin instead of argv (FORGE-166) — avoids
SPAWN_FAILEDwhen the diff exceeds the OS exec arg-size limit. - SPEC/PRD
deferred to v0.5language corrected — the ephemeral-ADR and closed-loop workflow was re-scoped into v0.4 on 2026-06-11. Worker drift events, the precedence engine, and the canceled worktree-drift-guard remain correctly deferred/dropped.
- Scaffolded lint/e2e gate steps no longer fail when the script is absent
(#267, #268) — templates now use
npm run lint --if-present/npm run e2e --if-present;typecheckstays the hard requirement. runProbeENOENT crash fix (FORGE-117) — execa@9 resolved-ENOENT spawn failures now surface as install-guidance messages instead of bogus exit codes for every probe (git/gh/claude/codex/ntn/op).- Orchestration audit umbrella (#262) — dead import cleanup, stale comment
refresh (
DECISION_KEY_EXHAUSTEDis no longer dormant), missing verb-level test for therender-worker-promptquestion-budget soft-cap path, Linear claim retry classifier guard, and stale-dist e2e test restoration.
notion-mcp-transport.ts—src/trackers/notion-mcp-transport.tsdeleted;@modelcontextprotocol/sdkremoved frompackage.json. The Notion MCP transport has been replaced by thentnCLI adapter (FORGE-117).mcp_command/mcp_envsettings fields are deprecated-ignored (accepted, never read; will be removed in v0.5).- Dead internal helpers + unused test fixtures (#249) —
flags.ts: nthPositional,core/errors.ts: isOrchestratorError,scaffold.ts: toSettingsObject,orchestrator/overlap.ts: globsOverlap. Associated dead test fixtures removed.
First release of the post-v0.2.2 line. Contains breaking changes to the CLI verb surface, plans/phases.yaml schema, and CLAUDE.md layout. Existing v0.2.x adopters should run forge upgrade after upgrading, and forge upgrade --migrate-claudemd if they have a v0.2.x-shape combined CLAUDE.md.
plans/phases.yaml— replacelinear_*keys with canonicaltracker_*keys (seeRemovedbelow).forge orchestrate next | suggest-next | session-check | intent-detect— removed without alias; useforge orchestrate phases --readyandforge orchestrate statusinstead.CLAUDE.md— methodology has moved to.forge/CONTEXT.md. Runforge upgrade --migrate-claudemdfor an automatic strict-match migration, or follow the manual recipe printed on drift.- Worktrees — new default location is
.forge/worktrees/<id>/(was../<project>-worktrees/<id>/). Existing sibling worktrees keep working; consolidation is optional.
.forge/CONTEXT.mdmethodology split + multi-agent root files (FORGE-152 / Phase A, #216) —forge initnow writes a slim per-agent root file (CLAUDE.md/AGENTS.md/GEMINI.md, user-selected via the init prompt) plus a Forge-managed.forge/CONTEXT.mdcontaining the methodology. Methodology no longer pollutes the product'sCLAUDE.md..forge/CONTEXT.mdis gitignored and regenerated from the bundled npm package.forge upgradeverb (FORGE-153 / Phase B, #217) — explicit re-sync verb. Refreshes.forge/CONTEXT.mdfrom the bundled template, manages enabled agents via--add-agent/--remove-agent, supports--dry-run, writes.forge/.version. Strict edit detection refuses to overwrite user edits;--forceoverrides and writes.bak. Exit codes 1/3/4 implemented (exit 2 — dirty-worktree + lease guard — tracked as FORGE-155).forge upgrade --migrate-claudemd(FORGE-154 / Phase C, #219) — one-shot migration for existing v0.2.x-shapeCLAUDE.mdfiles. Strict heading-by-heading SHA-256 match against a pinned v0.2.x fixture; bails to a manual recipe on drift.forge initGitHub-connected prompt +gh authprobe (FORGE-108 / P3-T01, #220) — standalone yes/no prompt with non-blockinggh auth statusvalidation. Scaffolded.forge/settings.yamlnow includescodex/decisions/doctorblocks per the extended SettingsSchema.- Multi-host project-local skill + agent farm (FORGE-156) —
forge initandforge upgradenow materialize per-host directories for every enabled agent, so host-side slash-command and subagent discovery resolves in a freshly-initialized project. Layout (one per enabled host):- Claude Code →
.claude/skills/<name>/SKILL.md,.claude/agents/<name>.md - Codex CLI →
.codex/skills/<name>/SKILL.md,.codex/agents/<name>.md - Gemini CLI →
.gemini/skills/<name>/SKILL.md,.gemini/agents/<name>.mdEntries are symlinks pointing into the bundlednode_modules/@firatcand/forge/skillsandagents/on POSIX, and recursive copies on Windows (where unprivileged symlinks aren't reliable). Idempotency is replace-if-mismatched: an existing-and-correct entry is left alone; a mismatched entry (different target, real file/dir, broken link) is backed up to<entry>.bakand replaced. The farm directories are gitignored by the forge-managed.gitignoreblock — targets are per-machine and rotate on everyforge upgrade, so committing them would break teammates with a different npm layout. Adopters who want to track their own non-forge skills/agents in these dirs can add!override lines below the marker block.
- Claude Code →
forge orchestratev2 CLI verb suite (FORGE-96 / P2.5-T05) — replaces the v0.2.x flat surface with the read-only / mutating split per spec/ORCHESTRATOR.md §CLI surface (rewritten 2026-05-17):- Read-only (no lease, no tracker mutation):
phases [--ready --phase implement|review|ship --blocked-by --limit],status,questions,doctor [--scope spec-code],attach,spec-diff,run list [--active]. - User-approved mutating:
claim,dispatch,heartbeat,question(worker writer; accepts--decision-key,--question,--options-file, optional--drift-event-id,--routing-hint apply-decision|amend-roadmap),answer,event(worker;--type drift --data <json>),complete,cancel,gc,run start. - Every verb returns a stable JSON envelope on
--json:{ ok, data? | error: { code, message, retriable, details? } }. - Verb-table dispatcher (
Map<string, VerbHandler | Map<string, VerbHandler>>) sorun start | listroutes correctly;--helpwalks the registry so the read/mutate classification table cannot drift from the implementation (guarded by a unit test). - Centralized zod arg schemas at
src/schemas/cli-args.ts(one per verb plus shared primitives:TaskIdSchema, UUIDv7 schemas for run/claim/attempt/question,DecisionKeySchema,PhaseSchema,RoutingHintSchema). - All 6 pre-existing verb files migrated from flat
src/cli/orchestrate-*.tsintosrc/cli/orchestrate/{verb}.tsviagit mvso blame follows.
- Read-only (no lease, no tracker mutation):
src/orchestrator/overlap.ts(FORGE-79 / P2-T19, absorbed into FORGE-96) — pure file-glob overlap classifier consumed byphases --readyto tag candidates asno-overlap/soft-overlap/hard-overlapagainst the active-attempts set. Tasks without declaredwrite_globsdefault to worst-case-assume-overlap-on-hard-lock-only.DEFAULT_HARD_LOCK_GLOBSmatches the spec list (package.json, lockfiles,tsconfig.json,plans/phases.yaml,src/index.ts,migrations/**,prisma/schema.prisma).TaskSchema.status+TaskSchema.write_globs— optional fields on phases.yaml task entries (TASK_STATUSES = active|paused|done|deferred-v0.5|dropped) so the loader stops silently dropping production data and so the overlap library can read declared globs from the task graph.- 3-task e2e fixture + integration test (
test/fixtures/orchestrator/3-task-phases.yaml,test/integration/cli/orchestrate/e2e.test.ts) — spawnsdist/bin/forge.cjsas a subprocess and drives the full v2 lifecycle (run start→phases --ready→claim→dispatch→heartbeat→question→cancel | complete) against three fixture tasks.FORGE_NOOP_TRACKER=1keeps the test hermetic. /push-to-trackerskill — canonical, tracker-agnostic push. Reads.forge/settings.yamltracker.type(linear | github | notion) and dispatches to the matchingTrackeradapter (FORGE-23). Writes canonicaltracker_project_id/tracker_url(top-level) andtracker_issue_id(per task) intophases.yaml.tracker-syncersubagent — replaceslinear-syncer. Documents per-tracker dispatch (Linear MCP /GitHubTracker/ Notion MCP) over the sharedTrackerinterface from FORGE-14.docs/trackers/directory — split out the Linear deep-dive (nowdocs/trackers/linear.md) and added an indexREADME.mdplus short stubs forgithub.mdandnotion.md.
forge orchestrate next | suggest-next | session-check | intent-detect(FORGE-96) — dropped from the v2 surface per spec/ORCHESTRATOR.md §CLI surface (simplified 2026-05-17). No deprecation alias; sole-user decision. Replacements:- Listing ready tasks →
forge orchestrate phases --ready. - Session re-grounding →
forge orchestrate status(or the/status-checkskill). - "I had an idea" intent → user explicitly invokes
/amend-roadmap(no automatic detection).
- Listing ready tasks →
- Legacy
linear_*keys inplans/phases.yaml—linear_project_id,linear_team_id, per-phaselinear_milestone_id, per-tasklinear_id, and top-levelgithub_repoare gone from the schema. The canonical tracker-agnostic keys (tracker_project_id,tracker_url, per-phasetracker_milestone_id, per-tasktracker_issue_id) are now the only supported names. Tracker-specific config (Linearteam_id, GitHubrepo, Notiondatabase_id) lives only in.forge/settings.yaml::tracker.config, no longer duplicated intophases.yaml. Originally scheduled for v0.3.0; accelerated because there are no external adopters with stored legacy keys.
/push-to-linearis now an alias for/push-to-trackerand prints a deprecation warning before forwarding.
- Worktree location standardized to
.forge/worktrees/<sanitized-id>/(FORGE-115 / P2.5-T19) —/pickup-tasknow creates worktrees inside the project at.forge/worktrees/, matching the orchestrator's existing convention. Previously the skill used../${PROJECT}-worktrees/${TICKET}/(sibling directory outside the repo). Migration: existing sibling worktrees keep working — git tracks them by absolute path. To consolidate, rungit worktree listto find them, then eithergit worktree move <old-path> .forge/worktrees/<id>to relocate orgit worktree remove <old-path>if the branch is already merged.forge initnow also writes tooling-exclude entries (.eslintignore,.prettierignoreget a one-line append;tsconfig.jsonandvitest.config.*get a copy-paste snippet in.forge/init-warnings.md) so lint / typecheck / test runs don't recurse into worktrees.
LinearTracker.claim+releaseClaim(FORGE-76):- Claim label prefix migrated
claimed:agent-*→forge:claimed-by:*(hard cut, matchesGitHubTrackerper FORGE-77). - Verify-on-readback now enforces our label is present on reread; previously a recheck that returned only another agent's label (or no labels) could return
{ ok: true }(false positive). Now returns{ ok: false, reason: 'version_conflict', detail: 'claim-label-missing-on-recheck' }. releaseClaimis now strict-scope — removes onlyforge:claimed-by:<runId>(the caller's exact label), not all claim labels on the issue. Includes stale-cached-id retry (evict + refresh + retry once onVALIDATION).- Legacy
claimed:agent-*labels left on issues by pre-FORGE-76 forge are now invisible to claim logic. Tracker-side stale-claim reconciliation lands in FORGE-22 (forge orchestrate gc).
- Claim label prefix migrated
/sync-status— preflight and body generalized to readtracker.typefirst; usestracker-syncersubagent./pickup-task— step 1 reads "Query the configured tracker (via Tracker interface)" instead of "Query Linear (via MCP)". Behavior unchanged for v0.3.x (still uses Linear MCP today); full behavioral generalization is a follow-up once FORGE-16/17 adapters land./decompose— final-print message updated from/push-to-linear unlockedto/push-to-tracker unlocked.docs/LINEAR-INTEGRATION.md→docs/trackers/linear.md(verbatim move; old path deleted). Internal links updated.templates/phases.template.yamlandexamples/time-logger/plans/phases.yaml— now use only the canonicaltracker_*keys.
- The scaffolded
CLAUDE.mdtemplate (per-project) does not yet exist intemplates/. When it lands in a future ticket, it should adopt/push-to-trackeras the canonical name; until then this CHANGELOG entry is the source of truth.
Initial public release.
- 21 skills covering the full product lifecycle:
- Ideation:
/forge,/draft-prd,/draft-spec,/draft-design,/ingest-spec,/decompose - Setup:
/setup-repo,/push-to-linear,/sync-status - Per-task loop:
/pickup-task,/plan-task,/implement,/investigate,/fix - Quality gates:
/review,/qa,/codex,/ship - Compound:
/learn,/phase-gate,/retro
- Ideation:
- 12 subagents: product-decomposer, linear-syncer, frontend-dev, backend-dev, db-architect, qa-engineer, code-reviewer, security-auditor, phase-gatekeeper, learning-curator, devops-engineer, design-reviewer
- 13 templates: BRIEF, PRD, SPEC, DESIGN, phases.yaml, CLAUDE.project, CRITICAL, learning, retro, plus 4 GitHub Actions workflows
- 8 enforced principles (see ETHOS.md): Boil the Lake, Iron Law of Investigation, Confusion Protocol, Test-or-die, Compound Learning, Multi-model Second Opinion, Plan Mode Mandatory, 12-Factor Env Discipline
- Linear ↔ GitHub native sync via branch-name and PR-title conventions
- Git worktree-based parallelism for multiple Claude Code sessions on one repo
@inheritpattern for brand-book / design-system reuse across projectsforgeCLI withinit,templates,upgrade,versionsubcommandssetup.shinstaller that symlinks skills/agents into~/.claude/- time-logger example demonstrating end-to-end lifecycle (BRIEF → PRD → SPEC → phases.yaml)
- Stack-agnostic: Next.js, Django, Rails, Go, anything.
- macOS-targeted; Linux compatibility likely but not yet validated.
- Codex CLI integration assumes Codex is installed and authenticated separately.