f0rge schema v1 (frozen)
Single source of truth for every .forge/ format. No byte changes
without a schema-version bump in the contract (f0rge --skill).
0. Global rules
- Identity:
idis uuid4, lowercase, dashes kept. Never changes. - Timestamps: RFC3339 UTC with fractional seconds:
2025-09-08T02:00:00.123456Z(parser accepts plain...00Ztoo). Lexicographic order == chronological order. Field names:created,updated,closed,due(milestones:YYYY-MM-DDor full RFC3339). - Files: YAML frontmatter between
---fences + markdown body. No frontmatter (exceptci.env,labels.md,milestones.md, scripts) = error. Unknown frontmatter field = warning, never error. Missing required field = error naming file + field. - Parse caps (SEC-01): file > 256 KiB rejected; nesting depth > 32 rejected; > 100 YAML aliases rejected. All before full parse.
- Numbers: derived, never stored. Issue/PR
number= 1-based rank ofidin total order(created, id)at list time. - Archive:
state: archived+ move to.forge/archive/<type>/<uuid>/. Nothing is deleted. Indexes skip archived by default. - CI verdict vocabulary:
pass | fail | running | cancelled. - Gitignored:
.forge/ci/runs/,.forge/releases/out/only. - States: issue
open|in-progress|closed|archived; propen|closed|merged(+draft:bool); taskstatus: todo|doing|blocked|done; milestoneopen|closed. - address: issue <N> | task|pr <uuid> | milestone|label <name> | wiki <path>
1. Layout (OUTLINE §5)
.forge/issues/<uuid>/issue.md + comments/<uuid>.md
.forge/prs/<uuid>/pr.md + patch.diff + comments/<uuid>.md + reviews/<uuid>.md
.forge/tasks/<uuid>.md
.forge/labels.md · milestones.md
.forge/wiki/ (index derived: tree + first H1)
.forge/ci/ci.env + steps/NN_name.sh + runs/<ts>/summary.md
.forge/releases/<version>.md + build/ + out/ (ignored)
.forge/merges/<ts>.md
.forge/archive/<type>/<uuid>/
2. Entities
issue — .forge/issues/<uuid>/issue.md
Required: id, title, state, created.
Optional: labels: [str] ([]), assignees: [str] ([]),
milestone: str, updated. Body = issue text.
Golden: spec/fixtures/issue.md.
issue-comment — comments/<uuid>.md
Required: id, created. Optional: author. Body = comment.
Golden: spec/fixtures/issue-comment.md.
pr — .forge/prs/<uuid>/pr.md
Required: id, title, state, base, head, created.
Optional: draft: bool (false), issues: [uuid] ([]),
ci: verdict, updated. Body = description; sidecar patch.diff.
Golden: spec/fixtures/pr.md.
pr-review — reviews/<uuid>.md
Required: id, created.
Optional: author, state: approved|changes-requested|commented.
Body = review text. Golden: spec/fixtures/pr-review.md.
task — .forge/tasks/<uuid>.md
Required: id, title, status, created.
Optional: assignee, updated, closed.
Column derived from status (todo|doing|blocked|done).
Create: --title T [--body B] [--column C] (default todo); body = markdown after frontmatter. Golden: spec/fixtures/task.md.
label — entries in .forge/labels.md (YAML list)
Required: name. Optional: color, description.
Golden: spec/fixtures/labels.md.
milestone — entries in .forge/milestones.md (YAML list)
Required: name. Optional: state (open),
due, description. Golden: spec/fixtures/milestones.md.
release — .forge/releases/<version>.md
Required: version, created. Optional: title, sha (HEAD short sha at create time; absent on old records).
Body = notes. Verbs: create <ver> --notes <file|-> [--title S],
install [ver|main] [--dry-run] (list/view unchanged). Golden: spec/fixtures/release.md.
merge-record — .forge/merges/<ts>.md
Required: pr (uuid), verdict: pass|fail, created.
Optional: applied: bool (false), resolved: [str] ([]).
Body = notes. Golden: spec/fixtures/merge-record.md.
Gate only: CI-gates patch in scratch worktree; never touches main/branches; prints conflicts, never resolves; you integrate (ff) + delete branch.
ci-env — .forge/ci/ci.env (YAML mapping, no fences)
Known: runner: local|docker (default local).
Any extra KEY: value string knob allowed, never secret.
Golden: spec/fixtures/ci-env.
ci-step — .forge/ci/steps/NN_name.sh
Executable script, filename order, exit 0 = step pass,
first nonzero exit = run fail. Golden: spec/fixtures/ci-step.sh.
ci-run — .forge/ci/runs/<ts>/summary.md (gitignored)
Required: run, verdict, created.
Optional: steps: [{name, exit, duration_ms}].
Golden: spec/fixtures/ci-run.md.
3. Derived indexes (pure, deterministic)
- Issue/PR numbers: sort
(created, id), 1-based rank. - Kanban: group task ids by
status(todo|doing|blocked|done). - Wiki index: walk tree, title = first
# H1else filename. --json(compact, fixed order): issuenumber,title,state,labels,assignees,milestone,body,createdAt,comments(comments = count); prnumber,title,state,base,head,draft, databaseId,status,conclusion,createdAt(databaseId= uuid,status/conclusionfromci); wiki listpath,title,size; release create/viewversion,title,created,body,status, release listversion,title,created,status(status:built|none, derived fromout/<ver>/).
4. Procedures (wiki, release, init)
wiki — files are the API; index derived, never stored
wiki get <path>,wiki put <path> -(stdin only),wiki list.- Index: walk tree (symlinks skipped, files > 256 KiB skipped), index title = first H1, else filename; sorted by path.
- Paths are wiki-relative: absolute,
.,..rejected (escapes wiki, exit 1); missing page (not found, exit 1); directories are not pages (is a directory, exit 1). - Put/get are exact bytes; stdin capped at 256 KiB.
- wiki pages are plain files in git; edit or remove with rm + git (git is the undo log); 'never delete' governs entity history (issues/prs/tasks), not doc files.
release — notes are truth, build is a side effect
release create <ver> --notes <file|-> [--title <t>]writesreleases/<ver>.md(upsert: last write wins), then runs the build.<ver>becomes<ver>.md; separators/escapes rejected (escapes releases, exit 1); missing--notesis usage (exit 2).- Build: executable scripts in
releases/build/(filename order, argv-only,VERSION/OUTenv) run at the<ver>tag in a temp worktree, else against the current tree; outputs land inreleases/out/<ver>/(gitignored); sha256 of artifacts is appended to the notes body. Empty build dir = no-op. release listorder:(created, version);release view <ver>prints the notes body. No delete verb (nothing is deleted).- Install (local machine op; never forge state, never CI):
release install(latest record),release install <ver>(pinned: record must exist elseno such release: <ver>, exit 2),release install main(current checkout). Resolve record sha > git tag > current checkout for pin + stagereleases/build/<ver>/; if CWD repo-rootinstall.shexists run it argv-only as./install.sh <ver>(F0RGE_RELEASE_SHA env carries the pin; it owns placement); else if CWDCargo.tomlexistscargo install --lockedfrom the resolved tree; elseno installer for this project (install.sh or Cargo.toml)(exit 1).--dry-runprints the path it would take (one line) without touching the machine. - Build artifact: CI copies
target/release/f0rgetoreleases/build/<ver>/f0rgeper release record (gitignored, never committed);status: built|nonestill derives fromout/<ver>/.
init — bootstrap skeleton, idempotent
init [dir](default cwd) creates dirsissues prs tasks wiki/docs wiki/research wiki/experiments ci/steps releases/build merges, fileslabels.md([]),milestones.md([]),ci/ci.env(runner: local), and appends any missing.gitignorelines (.forge/ci/runs/,.forge/releases/out/,*.tmp).- Idempotent: existing files kept (never clobbered), gitignore lines never duplicated. No git requirement (works in any dir).