forge-orkes 0.42.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bin/create-forge.js +138 -1
  2. package/package.json +1 -1
  3. package/template/.claude/agents/doc-reviewer.md +115 -0
  4. package/template/.claude/agents/performance-reviewer.md +138 -0
  5. package/template/.claude/agents/security-reviewer.md +163 -0
  6. package/template/.claude/hooks/README.md +39 -0
  7. package/template/.claude/hooks/block-dangerous-commands.sh +158 -0
  8. package/template/.claude/hooks/forge-active-skill-guard.sh +44 -0
  9. package/template/.claude/hooks/forge-reserve.sh +162 -0
  10. package/template/.claude/hooks/format-on-save.sh +95 -0
  11. package/template/.claude/hooks/protect-files.sh +101 -0
  12. package/template/.claude/hooks/scan-secrets.sh +87 -0
  13. package/template/.claude/hooks/tests/README.md +76 -0
  14. package/template/.claude/hooks/tests/cases/block-dangerous-commands.cases.json +376 -0
  15. package/template/.claude/hooks/tests/cases/protect-files.cases.json +222 -0
  16. package/template/.claude/hooks/tests/cases/scan-secrets.cases.json +218 -0
  17. package/template/.claude/hooks/tests/cases/warn-large-files.cases.json +146 -0
  18. package/template/.claude/hooks/tests/forge-reserve.test.sh +121 -0
  19. package/template/.claude/hooks/tests/run.sh +118 -0
  20. package/template/.claude/hooks/warn-large-files.sh +71 -0
  21. package/template/.claude/rules/README.md +63 -0
  22. package/template/.claude/rules/agent-discipline.md +14 -0
  23. package/template/.claude/settings.json +69 -1
  24. package/template/.claude/skills/architecting/SKILL.md +1 -1
  25. package/template/.claude/skills/chief-of-staff/SKILL.md +14 -0
  26. package/template/.claude/skills/executing/SKILL.md +16 -0
  27. package/template/.claude/skills/forge/SKILL.md +12 -1
  28. package/template/.claude/skills/initializing/SKILL.md +4 -0
  29. package/template/.claude/skills/planning/SKILL.md +14 -2
  30. package/template/.claude/skills/reviewing/SKILL.md +2 -0
  31. package/template/.claude/skills/verifying/SKILL.md +19 -8
  32. package/template/.forge/FORGE.md +32 -15
  33. package/template/.forge/migrations/0.43.0-safety-policy.md +60 -0
  34. package/template/.forge/migrations/0.44.0-desire-paths.md +57 -0
  35. package/template/.forge/migrations/0.46.0-id-reservation-ledger.md +44 -0
@@ -0,0 +1,63 @@
1
+ # Rules
2
+
3
+ Rules are short, ambient reminders that load passively based on which files are being edited. They are the lightweight counterpart to skills — cheap, file-path-triggered convention reminders that cost no context until you touch the relevant code. See [ADR-018](../../docs/decisions/ADR-018-lightweight-policy-layer.md).
4
+
5
+ ## The three weight tiers
6
+
7
+ Forge governs agent behaviour at three different weights. Rules fill the gap between the always-loaded heavy constitution and the opt-in heavy skills.
8
+
9
+ | Tier | Where | Weight | Loads when |
10
+ |---|---|---|---|
11
+ | **Constitution** | `.forge/constitution.md` | Heavy, project-wide hard rules | Always loaded — architectural **gates** that must pass |
12
+ | **Rules** | `.claude/rules/` | Lightweight ambient guidance | Always-on (no frontmatter) **or** path-scoped (auto-attaches only when editing matching files) |
13
+ | **Skills** | `.claude/skills/` | Heavy, full checklists/decision trees | Opt-in — loaded when a task description matches the skill |
14
+
15
+ - **Constitution** = the non-negotiable architectural gates that gate shipping. Heavy, always paid, project-specific.
16
+ - **Rules** = "don't add a phantom dependency", "re-read conventions on long sessions" — one-line bullets that fire ambiently. Path-scoped rules cost **zero context** until you touch a file matching their glob.
17
+ - **Skills** = "how we run a security review" (50–200 lines, invoked deliberately by task description).
18
+
19
+ The gap rules fill: the constitution is too heavy and too project-architectural for everyday convention reminders, and skills are opt-in so they miss ambient failure modes. Rules are the cheap, file-path-triggered middle layer.
20
+
21
+ Think of it this way:
22
+
23
+ - Constitution = "every API route enforces tenant isolation" (a gate, always checked)
24
+ - Skill = "how we run a security review" (50–200 lines, invoked deliberately)
25
+ - Rule = "don't call an API before grepping that it exists" (1-line bullet, fires ambiently)
26
+
27
+ If a rule grows past 25 lines, it is a skill in disguise. Split it or promote it.
28
+
29
+ ## File format
30
+
31
+ Every rule is a markdown file. Two modes:
32
+
33
+ **Always-on:** no frontmatter. Claude Code loads rules without a `paths:` field unconditionally (same priority as `.claude/CLAUDE.md`). Use for universal discipline (agent behaviour).
34
+
35
+ **Path-scoped:** YAML frontmatter with a `paths:` array of globs.
36
+ ```yaml
37
+ ---
38
+ paths:
39
+ - "src/**/*.ts"
40
+ - "app/**/Http/**"
41
+ ---
42
+ ```
43
+ Loaded only when Claude is reading or editing a file whose path matches any glob. Use for stack-specific or layer-specific guidance.
44
+
45
+ Do not use `alwaysApply: true`. That is Cursor syntax; Claude Code ignores it. Absence of `paths:` is the always-on signal.
46
+
47
+ ## Stack-specific rules ship opt-in
48
+
49
+ Forge core is **stack-agnostic**, so core ships only the always-on `agent-discipline.md`. Stack-specific path-scoped rules (Laravel, Vue, Python, etc.) live in the opt-in convention packs under `experimental/conventions/` — install them when your project's stack matches, and they auto-attach via their `paths:` globs. Keeping them out of core is what keeps core portable across any stack.
50
+
51
+ ## Current rules
52
+
53
+ | File | Scope | Topic |
54
+ |---|---|---|
55
+ | `agent-discipline.md` | always | Scope creep, phantom deps, hallucinated APIs, silent assumptions |
56
+
57
+ ## Authoring rules
58
+
59
+ - Keep each rule under ~20 lines of actual guidance.
60
+ - Lead with the imperative ("Never use ...", "Always declare ..."), not justification.
61
+ - No code blocks unless the exact syntax is the point.
62
+ - One file per concern. Do not bundle unrelated rules into a single file just because they share a path.
63
+ - If a rule needs a long explanation, it belongs in a skill. Rules state the rule; skills teach it. Past 25 lines, it's a skill in disguise.
@@ -0,0 +1,14 @@
1
+ # Agent Discipline
2
+
3
+ Always-on rule. These are the failure modes AI agents fall into most often. They apply to every session, regardless of stack. This restates the everyday floor; the executor's Deviation Rules (Rule 4 — STOP and ask before a new DB table, service layer, or library swap) in `.claude/agents/executor.md` are the hard stops.
4
+
5
+ - **Scope creep.** Do not modify files outside the stated scope. If a change requires touching something adjacent, ask first.
6
+ - **No phantom dependencies.** Do not add packages (`npm install`, `pip install`, `composer require`, `uv add`, etc.) without explicit approval. Name the package and version you need and wait. (A library swap is a Rule 4 stop.)
7
+ - **No over-engineering.** No abstractions, patterns, factories, registries, or base classes unless the plan calls for them. One concrete implementation does not need an interface.
8
+ - **No happy-path-only code.** Handle the unhappy paths the plan named. If the plan didn't name them, list what could go wrong before coding.
9
+ - **No hallucinated APIs.** Before calling a function or method, verify it exists with grep or by reading the source. If unsure, say so.
10
+ - **No silent assumptions.** Surface every non-obvious decision: "I assumed X because Y — confirm or redirect."
11
+ - **No "while I was here" extras.** No added logging, metrics, renames, or cleanup unless the task requires them.
12
+ - **Ask when unclear.** One good clarifying question beats an hour of wrong work.
13
+ - **Re-read conventions when the session gets long.** Style drifts over multi-hour sessions. The constitution and the relevant skill are the source of truth.
14
+ - **Test what you wrote.** Don't declare done without running the specific test or equivalent verification for the changed behaviour.
@@ -23,6 +23,39 @@
23
23
  "max_retries": 2
24
24
  }
25
25
  },
26
+ "permissions": {
27
+ "deny": [
28
+ "Read(./.env)",
29
+ "Read(./.env.*)",
30
+ "Read(**/.env)",
31
+ "Read(**/.env.*)",
32
+ "Read(**/secrets/**)",
33
+ "Read(**/.secrets/**)",
34
+ "Read(**/*.pem)",
35
+ "Read(**/*.key)",
36
+ "Read(**/*.p12)",
37
+ "Read(**/*.pfx)",
38
+ "Read(**/id_rsa)",
39
+ "Read(**/id_ed25519)",
40
+ "Read(**/credentials.json)",
41
+ "Read(**/auth.json)",
42
+ "Read(**/.composer-auth.json)",
43
+ "Read(**/.npmrc)",
44
+ "Read(**/.pypirc)",
45
+ "Edit(**/.env)",
46
+ "Edit(**/.env.*)",
47
+ "Edit(**/secrets/**)",
48
+ "Edit(**/.secrets/**)",
49
+ "Edit(**/*.pem)",
50
+ "Edit(**/*.key)",
51
+ "Write(**/.env)",
52
+ "Write(**/.env.*)",
53
+ "Write(**/secrets/**)",
54
+ "Write(**/.secrets/**)",
55
+ "Write(**/*.pem)",
56
+ "Write(**/*.key)"
57
+ ]
58
+ },
26
59
  "hooks": {
27
60
  "PostToolUse": [
28
61
  {
@@ -43,6 +76,15 @@
43
76
  "command": "mkdir -p .forge && echo \"$TOOL_INPUT\" > .forge/.active-skill"
44
77
  }
45
78
  ]
79
+ },
80
+ {
81
+ "matcher": "Edit|Write|MultiEdit",
82
+ "hooks": [
83
+ {
84
+ "type": "command",
85
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-save.sh\""
86
+ }
87
+ ]
46
88
  }
47
89
  ],
48
90
  "PreToolUse": [
@@ -51,7 +93,24 @@
51
93
  "hooks": [
52
94
  {
53
95
  "type": "command",
54
- "command": "if [ ! -f \"$(git rev-parse --show-toplevel 2>/dev/null)/.forge/.active-skill\" ]; then echo \"[Forge] No active skill. Invoke /forge or /quick-tasking before editing code. To bypass: touch .forge/.active-skill\" >&2; exit 2; fi"
96
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/forge-active-skill-guard.sh\""
97
+ }
98
+ ]
99
+ },
100
+ {
101
+ "matcher": "Edit|Write|MultiEdit",
102
+ "hooks": [
103
+ {
104
+ "type": "command",
105
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.sh\""
106
+ },
107
+ {
108
+ "type": "command",
109
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/warn-large-files.sh\""
110
+ },
111
+ {
112
+ "type": "command",
113
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scan-secrets.sh\""
55
114
  }
56
115
  ]
57
116
  },
@@ -63,6 +122,15 @@
63
122
  "prompt": "Verify the git commit message follows Forge format: {type}({scope}): {description}. Types allowed: feat, fix, test, refactor, chore, docs. If the message doesn't match, suggest the correct format."
64
123
  }
65
124
  ]
125
+ },
126
+ {
127
+ "matcher": "Bash",
128
+ "hooks": [
129
+ {
130
+ "type": "command",
131
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous-commands.sh\""
132
+ }
133
+ ]
66
134
  }
67
135
  ]
68
136
  }
@@ -28,7 +28,7 @@ When an architectural decision conflicts with vertical slicing (e.g., a framewor
28
28
 
29
29
  ## Architecture Decision Record (ADR)
30
30
 
31
- **Reserve the ADR number first (cross-worktree safety).** Before authoring a new ADR, allocate its number via the **ID Reservation Protocol** (FORGE.md): the next number is `max(highest `kind: adr` in `.forge/reservations.yml`, highest `ADR-NNN` in `.forge/decisions/`) + 1; append one `{kind: adr, id, milestone, reserved_at, summary}` entry to `.forge/reservations.yml`, **commit + push it**, *then* write the ADR file. Bare "scan `decisions/` for the highest + increment" only sees committed state, so two worktrees architecting in parallel both pick the same `ADR-NNN` and collide silently until merge (a multi-file renumber). See FORGE.md **ID Reservation Protocol** for the full rule. No `reservations.yml` yet scan as before and the first reservation creates it (lazy migration).
31
+ **Reserve the ADR number first (cross-worktree safety — blocking gate).** Before authoring a new ADR, allocate its number with the `forge-reserve` helper (ADR-021, supersedes ADR-016): run `id=$(.claude/hooks/forge-reserve.sh adr --milestone <id> --summary "<one-line>")`, then author `docs/decisions/$id-*.md` and commit `.forge/reservations.yml` **with** the ADR (unchanged handoff the helper writes `reservations.yml` uncommitted; the caller commits it alongside the work). **This reserve step is mandatory, not advisory: do not create the ADR file until `forge-reserve` has printed its id.** WHY: the number is allocated against a machine-local ledger (`.git/forge/id-reservations`) that every sibling worktree of the repo shares the moment it is written, so two worktrees architecting in parallel can no longer pick the same `ADR-NNN` and collide silently until merge (a multi-file renumber) the exact failure ADR-016's "commit + push before allocating" could not prevent, because reservations sat on unmerged branches nothing else reads. The helper self-heals a missing ledger (lazy migration). See FORGE.md → **ID Reservation Protocol** and ADR-021 for the mechanism.
32
32
 
33
33
  Record every significant decision in `.forge/decisions/`:
34
34
 
@@ -164,6 +164,7 @@ existing M10 state.
164
164
  pre-convention `git worktree add`; `git worktree move` it and update the
165
165
  record."* Adopt the recorded path as-is regardless (advisory, never a block;
166
166
  honors `orchestration.worktree_root`).
167
+ - **Submodules:** if the worktree was created manually (`git worktree add`, not via `orchestrating`) and the repo has submodules, they are empty until inited. Don't build from it blind — `executing`'s Workspace Prerequisites step inits them before the first build; surface this when handing the stream off so it isn't discovered as a mid-build failure.
167
168
  - `runtime.worker_sessions` with the M10 session / claim ids
168
169
  - `ownership.owned_surfaces`, `shared_surfaces`, and `read_only_surfaces`
169
170
  as empty lists if unknown
@@ -238,11 +239,24 @@ Compare all `active`, `delegated`, and `ready` streams:
238
239
  reservation, contract, or explicit merge order
239
240
  - overlapping `shared_surfaces` requires a named contract owner
240
241
  - overlapping `read_only_surfaces` is safe
242
+ - two streams holding the same `runtime.lease.resource` is an exclusive-resource conflict — serialize (see **Exclusive Resource Leases**)
241
243
  - missing ownership is a conflict until clarified
242
244
 
243
245
  Record conflicts in both the registry and the affected stream files. Do not let
244
246
  workers resolve cross-stream conflicts directly.
245
247
 
248
+ ## Exclusive Resource Leases
249
+
250
+ Surface conflict detection covers **files**. It does not cover a **single exclusive non-file resource** that two concurrent streams both need — a physical test device, a hardware port, a singleton bundle ID, a shared local DB/emulator. Two streams each `xcrun devicectl device install` to the same device, or deploy the same bundle ID, and each **clobbers the other's build**: a stream's device-verify gate opens the app and finds a *sibling's* build, the block under test absent (forge#15, canvaz m-AW01). Files don't collide here, so the surface check passes — and the clobber only shows up at device UAT.
251
+
252
+ Two complementary remedies; prefer (1), fall back to (2):
253
+
254
+ 1. **Isolate the resource per stream (no lease needed).** When the resource *can* be made per-stream, do that instead of serializing. The canonical case: derive a **per-worktree bundle-ID suffix** from the worktree anchor (`co.example.app` → `co.example.app.m-AW01`) so every stream's build coexists on one device. This is **project-level config** (the iOS/CMake build reads the anchor from `git branch --show-current`), not Forge core — record it as a constitution/`project.yml` convention for the stack. Isolated → the streams never contend; skip the lease.
255
+
256
+ 2. **Lease the resource (when it genuinely can't be split).** Declare exclusive resources in `project.yml` (`orchestration.exclusive_resources: [ios_device, …]`). A stream that needs one records a lease in **its own** stream file — `runtime.lease: {resource, acquired_at, operation}` (single-owner-mutable, the stream's driver writes it; no cross-stream contention). The owning skill (e.g. `verifying`'s device-verify gate) **acquires before** the deploy-and-verify, **releases after** (clears the field). The Chief detects double-claims the same way it detects surface overlap: scan stream files for two holding the same `runtime.lease.resource` → **serialize** them (one waits, or skips its device gate until the resource frees). Until a stream releases, siblings needing that resource queue.
257
+
258
+ Add the lease scan to **Detect Conflicts** (above) and the **Merge Cadence Check**: a held lease is a soft block on the holder's siblings' device gates, never on merge.
259
+
246
260
  ## Merge Safe Flow
247
261
 
248
262
  Use "merge safe" to identify streams that can land:
@@ -10,9 +10,25 @@ description: "Build to plan with atomic commits, deviation rules, and context en
10
10
  - [ ] Context.md locked decisions noted
11
11
  - [ ] Constitution.md gates satisfied
12
12
  - [ ] Milestone state updated to `status: executing`
13
+ - [ ] Workspace prerequisites met — submodules populated (see below)
13
14
  - [ ] Baseline snapshot captured (see below)
14
15
  - [ ] Plan anchors re-derived against HEAD (see below)
15
16
 
17
+ ## Workspace Prerequisites
18
+
19
+ Run **first, before the baseline snapshot** — a missing prerequisite makes the baseline's build/test commands fail for reasons that have nothing to do with the plan.
20
+
21
+ **Submodules.** A freshly-created worktree carries submodule gitlinks but leaves the directories **empty** until `git submodule update --init` runs — the first build then fails partway through with a confusing error (a missing third-party target, an empty `external/` dir), not an obvious "submodules uninitialised". `orchestrating` inits submodules when *it* creates the worktree, but a worktree from any other origin (a manual `git worktree add`, a `chief-of-staff` stream, a fresh clone) does not. So assert it here, regardless of how the workspace was created:
22
+
23
+ ```bash
24
+ # No-op when the repo has no submodules; a fast checkout when objects are already in the shared common git dir.
25
+ if [ -f .gitmodules ] && git submodule status | grep -q '^-'; then
26
+ git submodule update --init --recursive
27
+ fi
28
+ ```
29
+
30
+ Surface the init if it ran (*"populated N uninitialised submodule(s) before baseline"*). A `+`/`U` line (divergent/conflicted submodule) is **not** an init case — STOP under Rule 4; it signals real submodule movement. (Desire-path: canvaz m-TIDES01 — empty `external/JUCE` failed the first CMake configure mid-task.)
31
+
16
32
  ## Baseline Snapshot
17
33
 
18
34
  Run **before the first task begins**. Makes failure causality mechanical — no self-assessment.
@@ -45,7 +45,14 @@ git worktree list --porcelain 2>/dev/null \
45
45
  - `behind > 0` and the worktree has **no divergent local commits** (`git rev-list --count main..HEAD` is 0) → **fast-forward automatically**: `git merge --ff-only main`. A fast-forward is conflict-free by definition, so do it without asking; report *"pulled {N} commit(s) from main (checkpoint integration)."* Mark the sha seen.
46
46
  - `behind > 0` **and** the worktree has diverged → **surface, do not auto-rebase**: *"main advanced on {surfaces} since this worktree branched — rebase onto main? (`git rebase main`)"* — rebase can conflict; the operator decides.
47
47
 
48
- Cross-machine note: the flag file is machine-local, so a worktree on another laptop relies on the opt-in `forge.worktree_rebase_check` / a periodic fetch instead eventual, not instant.
48
+ **Integration flag check (consumer — main checkout).** When `in_main_checkout: true`, the same flag tells the operator's **canonical** checkout that a checkpoint pushed work to `origin/main` it hasn't pulled — the other half of the producer's strand-safe publish (FORGE.md Stream Integration Checkpoints; forge#13). Without this, local main sits behind origin after every checkpoint and the next local commit silently diverges it. Same event-driven gate:
49
+
50
+ - Read `.git/forge/integration-pending`. Absent, or its sha already seen by this checkout → **skip** (no fetch). No flag → no work.
51
+ - Flag set with an unseen sha → `git fetch origin main` (if a remote), then `behind=$(git rev-list --count main..origin/main 2>/dev/null)` (local `main` vs the freshly-fetched `origin/main`). `behind` is 0 → mark seen, skip.
52
+ - `behind > 0` and local main has **not** diverged (`git rev-list --count origin/main..main` is 0) → **fast-forward automatically** (the producer's strand guard guarantees this is a true ff): `git merge --ff-only origin/main` on `main`. Report *"fast-forwarded local main +{N} from origin (checkpoint integration)."* Mark seen.
53
+ - `behind > 0` **and** local main has diverged → **surface, never auto-merge**: *"local main and origin/main have diverged ({A} local / {B} remote commits) — this shouldn't happen if checkpoints used the strand guard; reconcile with `git fetch` then `git merge origin/main` (inspect first). Do NOT `git pull --ff-only` (it fails on divergence)."* The operator integrates.
54
+
55
+ Cross-machine note: the flag file is machine-local, so a worktree (or the primary checkout) on another laptop relies on the opt-in `forge.worktree_rebase_check` / a periodic fetch instead — eventual, not instant.
49
56
 
50
57
  **Worktree path convention check (advisory — main or worktree).** A recorded worktree path is trusted *verbatim* everywhere (FORGE.md → "Recorded path is authoritative"), so a path created **outside** the convention — a manual `git worktree add ../forge-worktrees/m115`, or pre-convention state — is honored unchallenged by the 1.1a gate and by `chief-of-staff` adoption. This check surfaces that drift. It never blocks and never moves anything.
51
58
 
@@ -54,6 +61,10 @@ Cross-machine note: the flag file is machine-local, so a worktree on another lap
54
61
  - Mismatch → one advisory line: *"recorded worktree path `{path}` for m{id} is not under the convention root `{root}` — likely a manual `git worktree add` or pre-convention state. Fix: `git worktree move {path} {root}/{anchor}` then update the recorded path. (Advisory; honors `orchestration.worktree_root`.)"*
55
62
  - Parent dir under the root, or no recorded path → silent. An explicit `orchestration.worktree_root` is honored by construction (the override **is** the root compared against), so a deliberately relocated root never false-warns.
56
63
 
64
+ **ID reservation preflight (advisory — main or worktree).** The cross-worktree ID Reservation Protocol (FORGE.md, ADR-016) only protects numbers if `.forge/reservations.yml` actually carries them. Two cheap checks, both advisory — never block:
65
+ - **Absent registry, but IDs already exist.** `.forge/reservations.yml` missing *and* the tree already holds allocated IDs (`requirements/*.yml` has `FR-`/`NFR-`/`DEF-`, or `.forge/decisions/` has `ADR-NNN`) → one-time nudge: *"No `reservations.yml` yet, but this project already allocates IDs. Concurrent worktrees will collide on the next number. Create it by reserving your next ID via the protocol (`planning`/`architecting` do this), or backfill existing IDs now."* (No IDs anywhere → silent; the first reservation creates the file lazily.)
66
+ - **Backfill scan (un-reserved landed IDs).** When `reservations.yml` exists, diff the IDs actually landed in the tree (`FR-`/`NFR-`/`DEF-` across `requirements/*.yml`; `ADR-NNN` across `.forge/decisions/`) against the ids recorded in `reservations.yml`. Any landed ID with **no** reservation entry → list them once: *"{N} ID(s) are in the tree but not in `reservations.yml` ({ids}) — landed before the protocol or via an un-reserved path. They are still respected (next = max(reserved, in-tree)+1), but appending backfill entries keeps the registry the single audit trail."* Advisory: surface, don't auto-write — backfilling is the operator's call.
67
+
57
68
  **Stream Rollup (active.yml is derived).** If `.forge/streams/` has any stream files, regenerate `.forge/streams/active.yml` from them + active milestones per FORGE.md → Stream Rollup, the same way step 1.0 regenerates `index.yml`. **Run the 1.0 State Rollup (below) first** so `index.yml` is fresh — the Stream Rollup reads active milestones from it for implicit-row coverage, so on a boot where a just-merged milestone promotion lands, a stale `index.yml` would silently drop that milestone's coverage row. `active.yml` is derived — never read it as authoritative without rolling up first, and never hand-edit it. Runs in the main checkout (worktrees never write `active.yml`). No `streams/` dir → skip.
58
69
 
59
70
  **Ready-to-merge nudge (main checkout / streams without checkpoints).** From the just-regenerated `active.yml`, scan for streams with `coordination: ready` or a non-empty `merge_queue` **that are not auto-publishing via checkpoints**. Any present → one line: *"{N} stream(s) ready to merge — run `/chief-of-staff` (merge safe) to integrate."* Pointer only — the cadence logic lives in the Chief's Merge Cadence Check; `forge` never merges. Nothing pending → silent.
@@ -270,6 +270,10 @@ No library + UI files → *"No component library. Design system or custom?"*
270
270
 
271
271
  Present grouped. User confirms/adds/removes.
272
272
 
273
+ ### Step 6.5: Rules Layer
274
+
275
+ The project ships with `.claude/rules/agent-discipline.md` (always-on AI-discipline floor). Point the user at the **rules layer** (`.claude/rules/README.md`) for codifying conventions that should auto-load by file path — cheaper than the constitution, lighter than a skill (ADR-018). If a stack was detected (Laravel/PHP, Python, Vue), offer the **opt-in convention pack**: *"Detected {stack}. Install the convention skills + path-scoped rules? `bash experimental/conventions/install.sh {stack}`"* (skip if the pack isn't present in this install). Stack conventions are opt-in — never auto-installed into the stack-agnostic core.
276
+
273
277
  ## Greenfield
274
278
 
275
279
  ### Step 1: Basics
@@ -76,11 +76,11 @@ Resolve current milestone ID from `.forge/state/index.yml` (active milestone) or
76
76
 
77
77
  If missing, create from `.forge/templates/requirements.yml`:
78
78
  1. Extract from user description + research
79
- 2. IDs: FR-001, FR-002... **Globally unique across milestones.** Allocate via the **ID Reservation Protocol** (FORGE.md): next = `max(highest `kind: fr` in `.forge/reservations.yml`, highest FR in `.forge/requirements/*.yml`) + 1; append `{kind: fr, id, milestone, reserved_at, summary}` to `.forge/reservations.yml`, **commit + push**, then write the FR. The reservation is what keeps concurrent worktrees from silently claiming the same FR number (bare scan-and-increment collidessee FORGE.md / ADR-016). No `reservations.yml` scan as before; first reservation creates it.
79
+ 2. IDs: FR-001, FR-002... **Globally unique across milestones.** Allocate with the `forge-reserve` helper (ADR-021, supersedes ADR-016). **This is a blocking gate, not advisory: do not write an `FR-`/`NFR-`/`DEF-` ID into any artifact until `forge-reserve` has printed it.** Per ID: run `id=$(.claude/hooks/forge-reserve.sh fr --milestone <id> --summary "<one-line>")` and use its stdout, *then* write the FR; `.forge/reservations.yml` commits with the plan (the helper writes it uncommitted — the caller commits). WHY: the id is allocated against a machine-local ledger (`.git/forge/id-reservations`) every sibling worktree shares the moment it is written, so two worktrees branched from one baseline can no longer claim the same number and collide silently until merge (a multi-file renumber)the exact failure ADR-016's "commit + push" could not prevent, because reservations sat on unmerged branches nothing else reads. The helper self-heals a missing ledger (lazy migration).
80
80
  3. Acceptance: Given/When/Then
81
81
  4. Uncertain: `[NEEDS CLARIFICATION]`
82
82
  5. P1 (must) / P2 (should) / P3 (nice)
83
- 6. Deferred: DEF-001..., and NFR-001... — **also globally unique; reserve the same way** (`kind: def` / `kind: nfr`) before writing.
83
+ 6. Deferred: DEF-001..., and NFR-001... — **also globally unique; reserve the same way** via `forge-reserve def` / `forge-reserve nfr` before writing.
84
84
 
85
85
  **E2E gate (M9):** For each functional requirement being added or refined:
86
86
  1. Decide `e2e: true|false` -- does this story need a post-validation e2e test?
@@ -207,6 +207,18 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
207
207
  </task>
208
208
  ```
209
209
 
210
+ #### Overlay / Popover must_haves (when a plan adds a floating surface)
211
+
212
+ When a plan introduces an **overlay, popover, sheet, anchored panel, or any floating surface** (especially one anchored to a moving element like a canvas block), specifying its *content* (rows, keys, payload) is **not enough** — the *behavior* questions below only surface when a human touches the running UI, each costing a full build → deploy → UAT round. Add a `must_haves.truths` entry for every item that applies, phrased as a user-observable behavior:
213
+
214
+ 1. **Placement + edge clamp** — where does it appear relative to its anchor, and does it stay fully on-screen when the anchor is near a viewport/canvas edge?
215
+ 2. **Drag-tracking** — if the anchor can move (drag, scroll, reflow), does the overlay follow it?
216
+ 3. **Live param re-sync** — while open, does it re-read the anchor's state so it reflects live changes (not a stale snapshot from open time)?
217
+ 4. **Z-order + tap-order** — draw order *and* hit-test order versus every other overlay/popover that can be open in the same surface.
218
+ 5. **Open/dismiss lifecycle** — what opens it, what dismisses it, and what happens if another overlay opens while it is already open?
219
+
220
+ Treat this as a checklist, not a template: drop the items that don't apply (a static centered modal has no anchor, so 1–3 collapse to "centered + on-screen"). The point is to decide each at plan time rather than discover it at device UAT. (Desire-path: canvaz m-SEQ01 — six overlay-behavior gaps, 5+ deploy-and-retest rounds.)
221
+
210
222
  #### Integration Checkpoints (merge often and early)
211
223
 
212
224
  Mark the phases where a verified slice should **land on main**, so a stream integrates continuously instead of batching at milestone end (FORGE.md → Stream Integration Checkpoints). Set `integration_checkpoint: true` in the plan frontmatter for those phases.
@@ -56,6 +56,8 @@ File lists per subagent (paths, not globs).
56
56
 
57
57
  Fresh-context subagents with file list + tech stack. **Pass `model` param** from `project.yml` model routing (`models.skills.reviewing` → `models.default` → parent). Display: *"Spawning reviewers with model: {model} (from {source})"*
58
58
 
59
+ The `reviewer` agent (3 modes below) is the **breadth pass** that gates the health report. For high-risk diffs (auth, payments, data-access, hot paths, public-facing docs) you may **additionally** spawn the confidence-gated specialists — `security-reviewer`, `performance-reviewer`, `doc-reviewer` (ADR-018) — for defensible single-axis depth. They report only Confidence ≥ 8 (security requires a concrete exploit scenario), so their output folds into the breadth findings without noise. Optional and additive — skip for low-risk changes.
60
+
59
61
  ### Part 1: Security Audit
60
62
 
61
63
  | # | Category | Checks |
@@ -110,6 +110,8 @@ Runs AFTER code-level verification commands pass. Skipped if no `e2e:true` stori
110
110
  5. **Hash drift check** (run BEFORE prompting, every gate invocation): for each `e2e:true` FR with `validated: true`, recompute hash from current `observable_outcome`. If it differs from stored `observable_outcome_hash` → set `validated: false`, clear hash. Note auto-reset in verification report. Then prompt that FR as unvalidated.
111
111
  6. Write per-FR validation outcomes into the verification report under section "E2E Validation".
112
112
 
113
+ **Exclusive-resource lease (concurrent streams + a shared physical device).** If this gate's manual walk needs an exclusive resource declared in `project.yml` `orchestration.exclusive_resources` (a physical device, a singleton emulator) **and** other streams are active, acquire the lease before deploying and release it after — otherwise a sibling stream's `device install` clobbers this build mid-walk and you verify the wrong binary (forge#15). Acquire = write `runtime.lease: {resource, acquired_at, operation: e2e-validation}` to this stream's file; the resource is busy → queue (or skip the device gate with a recorded reason) until it frees. Release = clear the field once the walk completes. Single checkout / no declared exclusive resources → no lease, proceed. See chief-of-staff → **Exclusive Resource Leases**; prefer per-stream isolation (bundle-ID suffix) over leasing when the resource can be split.
114
+
113
115
  ### Gate behavior
114
116
 
115
117
  - **Advisory, not blocking.** Verifying still passes even if no stories validated — the hard gate is in `testing` skill author-mode (phase 16). This gate's job is to surface + record, not block.
@@ -305,15 +307,24 @@ Realizes producer-side continuous integration (FORGE.md → Stream Integration C
305
307
  - the verdict is PASSED — never publish unverified work. (`current.human_verified` gates milestone **close**, *not* per-checkpoint publish: a non-final checkpoint phase publishes on PASS alone; the milestone's **final** checkpoint coincides with close, where the Human Verification Gate applies — so in practice only the last checkpoint also requires `human_verified`.)
306
308
  - the session is a **worktree** (`git rev-parse --git-dir` ≠ `--git-common-dir`). In a single checkout, skip — there is no main to fast-forward into.
307
309
 
308
- Then publish **fast-forward only**:
310
+ **Strand guard — never push past the operator's local main (forge#13).** `git push origin HEAD:main` is fast-forward-only *relative to `origin/main`* — but `origin/main` is **not** the operator's canonical checkout. The local `main` ref (shared across this machine's worktrees via the common git dir) can carry commits that were never pushed — another stream's merge, a framework bump, the state-sync commits from step 4. If you push the worktree tip onto an `origin/main` that is *behind* local main, origin fast-forwards but **local main is left diverged** (it has commits origin's new tip lacks, and vice versa). The closing report then mis-reads this as "local main is behind — `git pull`", and `git pull --ff-only` *fails* on divergence while a bare `git pull` makes a surprise merge commit — the exact failure the merge discipline exists to prevent. So gate the push on local main being in the publish's direct line:
309
311
 
310
312
  ```bash
311
- # Push this worktree's verified commits to main. FF-only is the safety:
312
- # git refuses a non-ff push, so a diverged main is surfaced, never forced.
313
- git push origin HEAD:main # remote present
314
- # no remote fast-forward the local main ref where possible, else report ready-to-ff
313
+ git fetch origin main 2>/dev/null || true # refresh origin/main (no-op without a remote)
314
+ # Publish is strand-safe only when local main is an ANCESTOR of this worktree's HEAD —
315
+ # i.e. local main has no commits this checkpoint doesn't already contain.
316
+ if git rev-parse --verify -q main >/dev/null 2>&1 \
317
+ && ! git merge-base --is-ancestor main HEAD; then
318
+ : "STRANDED — local main carries commits this checkpoint omits. Do NOT push."
319
+ fi
315
320
  ```
316
321
 
317
- - **Success** → set the integration flag for sibling worktrees: write `.git/forge/integration-pending` (in the git common dir) with the new main sha, this stream/milestone, and the shared surfaces this phase changed. Report: *"Checkpoint published — phase {N} fast-forwarded onto main; siblings will pick it up on next boot."*
318
- - **Rejected (non-ff — main diverged)** → **do not force.** Surface: *"main has advanced since this worktree branched — publish needs a manual merge/rebase first."* Leave the work on the branch; the Chief's Merge Cadence Check / a human resolves it.
319
- - Never auto-resolve a conflict, never force-push.
322
+ - **Strand-safe** (local main is an ancestor of HEAD, or no local `main` ref exists) publish **fast-forward only**:
323
+ ```bash
324
+ git push origin HEAD:main # remote present; git refuses a non-ff push (diverged origin surfaced, never forced)
325
+ # no remote → fast-forward the local main ref where possible, else report ready-to-ff
326
+ ```
327
+ - **Success** → set the integration flag for **all** of this machine's checkouts (sibling worktrees *and* the primary main checkout): write `.git/forge/integration-pending` (in the git common dir) with the new main sha, this stream/milestone, and the shared surfaces this phase changed. Because local main was an ancestor, advancing it is a **true fast-forward** — report: *"Checkpoint published — phase {N} fast-forwarded onto main. Local main is now behind origin by this slice; it auto-fast-forwards on the next `/forge` boot, or `git merge --ff-only origin/main` from the primary checkout now."* (Here, and only here, is local main genuinely *behind* — the ff advice is correct.)
328
+ - **Rejected (non-ff — `origin/main` itself diverged)** → **do not force.** Surface: *"origin/main has advanced since this worktree branched — publish needs a manual merge/rebase first."* Leave the work on the branch; the Chief's Merge Cadence Check / a human resolves it.
329
+ - **Stranded** (local main has commits this checkpoint omits) → **do not push.** Integrating through origin would diverge local main. Surface: *"main has advanced locally ({N} commit(s) not in this checkpoint) since this worktree branched. Publishing direct to origin would strand your local main (diverged, not behind). Integrate through local main instead: from the primary checkout `git merge {branch}` (or rebase this worktree onto main, then publish). Do NOT `git pull --ff-only` — it fails on divergence."* Leave the work on the branch; a human integrates.
330
+ - Never auto-resolve a conflict, never force-push, never auto-rebase.
@@ -44,9 +44,9 @@ Use ordinary Quick/Standard/Full tiers inside a stream. Use Project Chief / Chie
44
44
  Integration is **event-driven, not polled**: a stream publishes its work to main at plan-marked phase boundaries, and a lightweight flag tells sibling worktrees there is something to pull. No flag set → boot does no integration work.
45
45
 
46
46
  - **Checkpoints (plan-marked).** `planning` marks selected phases as integration checkpoints (`integration_checkpoint: true` in the plan frontmatter) — the points where a verified slice should land on main. Default heuristic: the last phase, plus any phase another stream/phase depends on. Advisory — planning proposes, the operator confirms. **The mark is the opt-in:** a plan with no marked checkpoint never auto-publishes, so existing/unmarked work is unaffected.
47
- - **Producer — publish on verified checkpoint (fast-forward only).** When `verifying` returns PASS for a checkpoint phase *and the session is a worktree*, it integrates that phase to main with a **fast-forward-only** push: `git push origin HEAD:main`. Git rejects a non-ff push — that *is* the safety: a diverged main is surfaced for a human merge, never force-pushed. On success it sets the **integration flag**. (No remote → fast-forward local `main` where possible, else record ready-to-ff for the primary checkout.)
47
+ - **Producer — publish on verified checkpoint (fast-forward only, strand-guarded).** When `verifying` returns PASS for a checkpoint phase *and the session is a worktree*, it integrates that phase to main with a **fast-forward-only** push: `git push origin HEAD:main`. Git rejects a non-ff push (vs `origin/main`) — that surfaces a diverged *origin*, never force-pushed. But ff-vs-origin is not enough on its own: `origin/main` is not the operator's canonical checkout, and the local `main` ref can carry commits origin never saw (another stream's merge, a framework bump, state-sync commits). Pushing past a *behind* origin then leaves **local main diverged, not behind** — and the naive "you're behind, `git pull --ff-only`" advice *fails* on divergence (forge#13). So the producer **strand-guards** first: it publishes only when local `main` is an **ancestor of the worktree HEAD** (no local commits this checkpoint omits). Stranded (local main has such commits) → it does **not** push; it surfaces "integrate through local main first" and leaves the work on the branch. On a strand-safe success it sets the **integration flag**. (No remote → fast-forward local `main` where possible, else record ready-to-ff for the primary checkout.)
48
48
  - **Flag (cheap, set by the producer).** A marker in the shared git common dir — `.git/forge/integration-pending` — records the new main sha, the publishing stream, and the shared surfaces that changed. It is shared across all of a repo's worktrees on a machine, so any boot reads it for free; absent or already-seen → skip. The `main` ref is the source of truth the marker points at. Cross-machine (the marker is machine-local) falls back to the opt-in `forge.worktree_rebase_check` / a periodic fetch — eventual, not instant.
49
- - **Consumer — auto fast-forward, else prompt.** `forge` boot in a worktree checks the flag. Flag set (main advanced past this worktree's merge-base) and the worktree has **no divergent local commits** → **fast-forward it automatically** (`git merge --ff-only main`) and report a fast-forward is conflict-free by definition, so it is safe without asking. Worktree has **diverged** **surface a rebase prompt** (never auto-rebase; rebase can conflict and needs human judgment). Flag not set → no fetch, no scan, continue boot.
49
+ - **Consumer — auto fast-forward, else prompt (worktrees *and* the primary main checkout).** `forge` boot checks the flag in **both** checkout kinds. In a **worktree**: flag set (main advanced past this worktree's merge-base) and no divergent local commits → **fast-forward automatically** (`git merge --ff-only main`); diverged **surface a rebase prompt** (never auto-rebase). In the **primary main checkout**: the strand guard guarantees local main is a clean ff of the new `origin/main`, so boot **fast-forwards local main automatically** (`git merge --ff-only origin/main`) this is what keeps the operator's canonical checkout in lockstep with origin after every checkpoint, instead of drifting behind until the next local commit diverges it. Flag not set → no fetch, no scan, continue boot.
50
50
 
51
51
  ### Worktree Convention (base)
52
52
 
@@ -106,6 +106,7 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
106
106
  | Systematic debugging | `debugging` | When stuck |
107
107
  | Upgrade Forge files | `upgrading` | On-demand |
108
108
  | Cross-session memory | `beads-integration` | When Beads installed |
109
+ | Project/read Forge work in Notion | `notion-integration` | When Notion configured |
109
110
  | Multi-agent backend (experimental) | `orchestrating` | Optional, usually selected by Chief/Streams |
110
111
 
111
112
  > Experimental skills require opt-in install — see `packages/create-forge/experimental/m10/README.md`.
@@ -136,8 +137,9 @@ Every new accumulating artifact needs: owner, size gate, archive/compaction rule
136
137
  source-of-truth; load scoped slices, not whole history.
137
138
  - `phases/`, `audits/`, `research/`, `archive/`, and `context-archive.md` are
138
139
  historical; load only by milestone/stream/package/version.
139
- - Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs) are excluded
140
- from framework context and packages unless debugging them.
140
+ - Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs, and the
141
+ `.git/forge/*` machine-local files the id-reservation ledger + integration
142
+ flag) are excluded from framework context and packages unless debugging them.
141
143
  - Template copies sync mechanically; do not treat them as separate concepts.
142
144
 
143
145
  ### Fresh Agent Pattern
@@ -151,6 +153,14 @@ Skills load only when invoked. Active context is `.forge/context.md`; consult
151
153
  `.forge/context-archive.md` only when older decisions matter. Skill details stay
152
154
  on demand. Base context ~300 lines.
153
155
 
156
+ ### Rules Layer (`.claude/rules/`)
157
+ Three policy weights, lightest-first (ADR-018):
158
+ - **`constitution.md`** — always-loaded architectural **gates** (heavy, project-wide, hard rules; size-gated 10 KB).
159
+ - **`.claude/rules/`** — lightweight ambient guidance. Always-on (no frontmatter, loads every session) or path-scoped (`paths:` glob frontmatter, auto-attaches only when editing a matching file — **zero context cost until you touch that code**).
160
+ - **Skills** — heavy, opt-in, loaded by task description.
161
+
162
+ Core ships one always-on rule: **`agent-discipline.md`** (the stack-agnostic AI failure-mode floor; restates the everyday version of the executor's Deviation Rules). **Stack-specific** path-scoped rules (Laravel/Vue/Python) and convention skills ship in the **opt-in `experimental/conventions/` pack**, not core — keeping the base stack-neutral. Authoring: keep each rule under ~20 lines, imperative-first; if it grows past ~25 lines it is a skill in disguise. See `.claude/rules/README.md`.
163
+
154
164
  ## Model Routing
155
165
 
156
166
  Configure in `project.yml`:
@@ -179,8 +189,13 @@ models:
179
189
  | `planner` | Planning + constitutional gates | Read + Write (plan files) | Planning |
180
190
  | `executor` | Building + deviation rules | All dev tools | Execution |
181
191
  | `verifier` | Goal-backward verification | Read + Bash (tests) | Verification |
182
- | `reviewer` | Security + architecture + refactoring | Read, Bash, Grep, Glob | Reviewing |
192
+ | `reviewer` | Security + architecture + refactoring (breadth pass, gates the health report) | Read, Bash, Grep, Glob | Reviewing |
183
193
  | `tester` | E2E/integration authoring + suite analysis | Dual-mode: author (Write/Edit/Bash/git) · analyst (Read/Glob/Grep/Bash read-only) | Testing skill |
194
+ | `security-reviewer` | Confidence-gated security depth (≥8, exploit-scenario required) | Read, Grep, Glob, Bash | Optional deep-dive (from `reviewing` or direct) |
195
+ | `performance-reviewer` | Real bottlenecks only (Impact = frequency × cost; ≥8 + ≥Medium) | Read, Grep, Glob, Bash | Optional deep-dive |
196
+ | `doc-reviewer` | Docs-vs-source accuracy (≥8; Blocker/Major/Minor) | Read, Grep, Glob, Bash | Optional deep-dive |
197
+
198
+ The three specialist reviewers **complement** the generalist `reviewer` — single-axis, defensible depth, read-only. They are pulled in deliberately (the `reviewing` skill can spawn them, or invoke directly); the 3-mode `reviewer` remains the breadth pass that gates the milestone health report.
184
199
 
185
200
  ## Project Init (First Run)
186
201
 
@@ -198,7 +213,7 @@ State lives in `.forge/`:
198
213
  - `project.yml` — Vision, stack, design system, verification, constraints (<5KB)
199
214
  - `constitution.md` — Active architectural gates
200
215
  - `design-system.md` — Component mapping table
201
- - `requirements/m{N}.yml` — Per-milestone structured requirements with `[NEEDS CLARIFICATION]` markers. **FR-IDs, DEF-IDs, and NFR-IDs are globally unique across all milestone files** — `FR-001` may exist in exactly one `m{N}.yml`. Before adding a new ID, **reserve it via the ID Reservation Protocol** (append to `.forge/reservations.yml`, commit+push, then write) the next number is `max(highest reserved, highest in `.forge/requirements/*.yml`) + 1`. That reservation is what makes the shared ID space safe across **concurrent worktrees**: bare scan-and-increment sees only committed state, so two worktrees branched from one baseline claim the same number and collide silently until merge (see ADR-016). On a residual collision (legacy/un-reserved, e.g. during a migration), keep the older milestone's ID and renumber the newer. Concurrent milestones each own their file — no cross-stream contention on file writes, but ID space is shared. Functional requirements may carry M9 e2e gate fields (`e2e`, `observable_outcome`, `observable_outcome_hash`, `validated`) — lazy migration, absent fields default to `e2e:false`/`validated:false`.
216
+ - `requirements/m{N}.yml` — Per-milestone structured requirements with `[NEEDS CLARIFICATION]` markers. **FR-IDs, DEF-IDs, and NFR-IDs are globally unique across all milestone files** — `FR-001` may exist in exactly one `m{N}.yml`. Before adding a new ID, **reserve it via the ID Reservation Protocol** — run `forge-reserve fr|nfr|def --milestone <id>` and use the id it prints (the helper dual-writes the machine-local ledger + `.forge/reservations.yml`; you commit `reservations.yml` with the work). The next number is `max(ledger in-tree ∪ main:reservations.yml) + 1`. The machine-local ledger is what makes the shared ID space safe across **concurrent worktrees**: bare scan-and-increment (or a branch-local `reservations.yml`) sees only committed/merged state, so two worktrees branched from one baseline claim the same number and collide silently until merge (see ADR-021, supersedes ADR-016). On a residual collision (legacy/un-reserved, e.g. during a migration), keep the older milestone's ID and renumber the newer. Concurrent milestones each own their file — no cross-stream contention on file writes, but ID space is shared. Functional requirements may carry M9 e2e gate fields (`e2e`, `observable_outcome`, `observable_outcome_hash`, `validated`) — lazy migration, absent fields default to `e2e:false`/`validated:false`.
202
217
  - `roadmap.yml` — Phases, milestones, dependencies
203
218
  - `state/index.yml` — DERIVED registry rolled up from milestone files (id, name, status, last_updated). Never hand-edited; never written by worktree agents.
204
219
  - `state/milestone-{id}.yml` — Per-milestone cursor (single source of truth): position, progress, decisions, blockers. One owner at a time.
@@ -214,7 +229,7 @@ State lives in `.forge/`:
214
229
  - `refactor-backlog.yml` — Refactoring catalog (actionable items only), worked via quick-tasking. Canonical status vocab: `pending | in_progress | done | dismissed | deferred`. Terminal items auto-archive on the next reviewing/quick-tasking write (compaction-on-write).
215
230
  - `refactor-backlog-archive.yml` — Append-only terminal-item archive (`done`/`dismissed`); keeps the audit trail out of the working backlog.
216
231
  - `releases.yml` — Append-only version-reservation registry (cross-session coordination point; see Version Reservation Protocol)
217
- - `reservations.yml` — Append-only ID-reservation registry for ADR/DEF/FR/NFR numbers (cross-worktree coordination point; see ID Reservation Protocol)
232
+ - `reservations.yml` — Append-only ID-reservation audit trail + cross-machine floor for ADR/DEF/FR/NFR numbers, written by `forge-reserve` (ADR-021). The same-machine cross-worktree coordination point is the machine-local ledger `.git/forge/id-reservations`; see ID Reservation Protocol.
218
233
  - `archive/milestone-{id}/` — Archived milestone artifacts (archive-delete)
219
234
 
220
235
  **Milestones** group phases into concurrent streams. Own state file — no conflicts across sessions. Can be deferred (frozen in place) or archive-deleted.
@@ -252,7 +267,8 @@ with the work it describes. Every artifact has a sharing class:
252
267
  | **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md` | Exactly one writer at a time — the worktree (or main session) currently driving that milestone. **Other worktrees never touch it**, not even to read-then-write — they pull from main if they need it. |
253
268
  | **Single-owner mutable (per stream)** | `streams/{stream}.yml`, `streams/{stream}/brief.md`, `streams/{stream}/packages/*.yml` | The stream's **current driver** writes them — one writer at a time, like the milestone file. Distinct streams own distinct files (no cross-stream contention). `active.yml` is **derived** from them: a worktree driving a stream may write its own stream file but **never** `active.yml` or a peer stream's file. |
254
269
  | **Derived** | `state/index.yml`, `streams/active.yml` | Never hand-edited. Regenerated by rollup from their sources (`index.yml` ← `milestone-*.yml` via Rollup 1.0; `active.yml` ← per-stream files + active milestones via the Stream Rollup). Only the main/orchestrator session (and `chief-of-staff` for `active.yml`) runs rollup. |
255
- | **Append-only shared** | `releases.yml` (version reservations), `reservations.yml` (ADR/DEF/FR/NFR id reservations), `state/desire-paths/*` (one file per observation) | Many writers OK; structure prevents collision. `releases.yml` + `reservations.yml` use the reservation protocols (append + commit + push before depending on the number); desire-paths use distinct filenames so two writers never touch the same file. |
270
+ | **Append-only shared** | `releases.yml` (version reservations), `reservations.yml` (ADR/DEF/FR/NFR id **audit trail + cross-machine floor**), `state/desire-paths/*` (one file per observation) | Many writers OK; structure prevents collision. `releases.yml` uses the Version Reservation Protocol (append + commit + push). `reservations.yml` is written by `forge-reserve` (ADR-021) and committed with the caller's work it is no longer the id *coordination* point (the machine-local ledger is), just the durable committed shadow. Desire-paths use distinct filenames so two writers never touch the same file. |
271
+ | **Machine-local runtime** | `.git/forge/id-reservations` (id-reservation ledger), `.git/forge/integration-pending` (integration flag) | In the git common dir, shared across a repo's worktrees on one machine, **not** git-tracked. `forge-reserve` writes the ledger under a `mkdir` lock; excluded from framework context. Machine-local by design (cross-machine falls back to the `main:reservations.yml` floor). |
256
272
  | **Shared mutable** | `context.md`, `refactor-backlog.yml`, `requirements/m{N}.yml` (ID space `FR-`/`DEF-`/`NFR-` is globally shared even though each milestone owns its file) | Write only to your milestone/stream block. Within a block, append-only; strike through instead of rewriting. |
257
273
  | **Stable shared** | `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `FORGE.md`, `.claude/skills/`, `.claude/agents/` | Rarely changes except via `/upgrading`. Canonical on main; worktrees lag until they rebase. An upgrade run touches **only the checkout it runs in**, so after upgrading in main, live worktrees keep their branch-point framework files — `upgrading` **Step 8** enumerates live `forge/m-*` worktrees and offers to rebase each (never auto), and the opt-in `forge.worktree_rebase_check` watches these files as a boot-time safety net. Gitignored carve-outs (`.mcp.json`, experimental hooks) don't rebase — re-run the experimental installer if the upgrade changed them. |
258
274
 
@@ -287,15 +303,16 @@ The project version + CHANGELOG slot are **shared resources** — when two miles
287
303
 
288
304
  ### ID Reservation Protocol
289
305
 
290
- Sequential IDs — `ADR-NNN` (decision records), `DEF-NNN` (deferred issues), `FR-NNN`/`NFR-NNN` (requirements) — are **shared cross-worktree resources** the same way the version number is. "Scan the tree for the highest and increment" only sees *committed* state, so two streams branched from the same baseline each claim the same next number and collide **silently until merge** — a costly multi-file renumber pass (see [ADR-016](../docs/decisions/ADR-016-id-reservation-protocol.md)). `.forge/reservations.yml` removes the number from per-stream scope, exactly as `releases.yml` does for versions.
306
+ Sequential IDs — `ADR-NNN` (decision records), `DEF-NNN` (deferred issues), `FR-NNN`/`NFR-NNN` (requirements) — are **shared cross-worktree resources** the same way the version number is. "Scan the tree for the highest and increment" only sees *committed* state, so two worktrees branched from the same baseline each claim the same next number and collide **silently until merge** — a costly multi-file renumber pass. [ADR-016](../docs/decisions/ADR-016-id-reservation-protocol.md) tried to fix this by reserving in the branch-tracked `reservations.yml` and *committing + pushing before allocating*, but a reservation on an unmerged branch is invisible to every sibling worktree (they share `.git`, not branches) — so collisions continued. [ADR-021](../docs/decisions/ADR-021-id-reservation-ledger-git-common-dir.md) (**supersedes ADR-016**) moves the coordination point to a **machine-local ledger in the git common dir**, the same primitive as the integration flag: every sibling worktree of the repo on a machine sees it the moment it is written — no push, no pull, no merge.
291
307
 
292
- - **Reserve before allocating.** Before authoring a new ADR or writing a new DEF/FR/NFR ID, append ONE entry to `.forge/reservations.yml` `{kind, id, milestone, reserved_at, summary}`, `kind {adr, def, fr, nfr}`then **commit + push it BEFORE creating the artifact**. A parallel worktree pulls and sees the number taken.
293
- - **Next number = `max(reserved, in-tree) + 1` per kind.** The higher of (a) the highest reservation for that kind in `reservations.yml` and (b) the highest ID of that kind actually landed in the tree. Taking both keeps it backward-compatible landed IDs that were never reserved are still respected.
294
- - **Append-only never edit prior entries.** Distinct lines = no contention. Race resolution: the second of two near-simultaneous reservations rebases onto the first (append-only → clean), re-reads `max()`, takes the next number.
295
- - **Reserve points.** `architecting` reserves an `adr` before authoring it; `planning` reserves `fr`/`nfr`/`def` before writing them into requirements. (Out of scope: `executing`'s `DI-NNN` deferred-*issue* ids in `deferred-issues.md` are a separate, more local namespace — not reserved here.)
296
- - **Lazy migration.** No `reservations.yml` allocate as before (scan in-tree max); the first reservation creates the file. Existing projects are unaffected until they next allocate an ID.
308
+ - **Reserve with the helper.** `forge-reserve <kind> --milestone <id> [--summary <text>]` (`.claude/hooks/forge-reserve.sh`, `kind {adr, def, fr, nfr}`) allocates the next number under a portable `mkdir` lock and prints it on stdout. It **dual-writes**: one TSV line to `.git/forge/id-reservations` (the machine-local coordination ledger) and one `{kind, id, milestone, reserved_at, summary}` block to the tracked `.forge/reservations.yml`. Neither is committed by the helper **the caller commits `reservations.yml` with its work** (unchanged handoff). Reserve *before* creating the artifact; the printed id is the one to use.
309
+ - **Next number = `max(ledger in-tree ∪ main:reservations.yml) + 1` per kind.** Three inputs, unioned: the **ledger** (same-machine siblings, *including uncommitted* the input ADR-016 could not see), the **in-tree** landed ids (`docs/decisions/ADR-NNN*`; `.forge/requirements/*.yml` for fr/nfr/def), and **`main:reservations.yml`** (the cross-machine / cold-start floor). Taking all three keeps it backward-compatible (landed & pre-ledger IDs respected) while giving the ledger authority for the same-machine hot path.
310
+ - **`reservations.yml` is now the durable audit trail + cross-machine floor**, not the coordination mechanism. Append-only, distinct lines it unions cleanly at merge (it stops being a merge-conflict source). An optional `forge-reserve --verify` can diff ledger vs. `reservations.yml`.
311
+ - **Reserve points.** `architecting` reserves an `adr` before authoring it; `planning` reserves `fr`/`nfr`/`def` before writing them into requirements — both via `forge-reserve`. (Out of scope: `executing`'s `DI-NNN` deferred-*issue* ids in `deferred-issues.md` are a separate, more local namespace — not reserved here.)
312
+ - **Cross-machine gap (accepted).** The ledger is machine-local (like `integration-pending`). Two *concurrent* worktrees on *different* laptops, both reserving before either merges, still fall back to merge-time renumber bounded by the `main:reservations.yml` floor, so no worse than ADR-016.
313
+ - **Lazy migration.** No ledger ⇒ the helper creates it on first reserve, seeded by the `max()` (which reads in-tree + `main:reservations.yml`). Existing `reservations.yml` is untouched and still respected. Projects that never run concurrent worktrees see no behavior change.
297
314
 
298
- This is the cross-worktree mechanism the FR/DEF/NFR "globally unique" rule (State Management, above) previously lacked, and the first reservation rule ADR numbers have ever had. Versions stay in `releases.yml` (separate semver/CHANGELOG semantics); everything else reserves here.
315
+ This is the cross-worktree mechanism the FR/DEF/NFR "globally unique" rule (State Management, above) previously lacked, now made collision-safe for same-machine worktrees. Versions stay in `releases.yml` (separate semver/CHANGELOG semantics); everything else reserves through `forge-reserve`.
299
316
 
300
317
  ## Deviation Rules
301
318