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.
- package/bin/create-forge.js +138 -1
- package/package.json +1 -1
- package/template/.claude/agents/doc-reviewer.md +115 -0
- package/template/.claude/agents/performance-reviewer.md +138 -0
- package/template/.claude/agents/security-reviewer.md +163 -0
- package/template/.claude/hooks/README.md +39 -0
- package/template/.claude/hooks/block-dangerous-commands.sh +158 -0
- package/template/.claude/hooks/forge-active-skill-guard.sh +44 -0
- package/template/.claude/hooks/forge-reserve.sh +162 -0
- package/template/.claude/hooks/format-on-save.sh +95 -0
- package/template/.claude/hooks/protect-files.sh +101 -0
- package/template/.claude/hooks/scan-secrets.sh +87 -0
- package/template/.claude/hooks/tests/README.md +76 -0
- package/template/.claude/hooks/tests/cases/block-dangerous-commands.cases.json +376 -0
- package/template/.claude/hooks/tests/cases/protect-files.cases.json +222 -0
- package/template/.claude/hooks/tests/cases/scan-secrets.cases.json +218 -0
- package/template/.claude/hooks/tests/cases/warn-large-files.cases.json +146 -0
- package/template/.claude/hooks/tests/forge-reserve.test.sh +121 -0
- package/template/.claude/hooks/tests/run.sh +118 -0
- package/template/.claude/hooks/warn-large-files.sh +71 -0
- package/template/.claude/rules/README.md +63 -0
- package/template/.claude/rules/agent-discipline.md +14 -0
- package/template/.claude/settings.json +69 -1
- package/template/.claude/skills/architecting/SKILL.md +1 -1
- package/template/.claude/skills/chief-of-staff/SKILL.md +14 -0
- package/template/.claude/skills/executing/SKILL.md +16 -0
- package/template/.claude/skills/forge/SKILL.md +12 -1
- package/template/.claude/skills/initializing/SKILL.md +4 -0
- package/template/.claude/skills/planning/SKILL.md +14 -2
- package/template/.claude/skills/reviewing/SKILL.md +2 -0
- package/template/.claude/skills/verifying/SKILL.md +19 -8
- package/template/.forge/FORGE.md +32 -15
- package/template/.forge/migrations/0.43.0-safety-policy.md +60 -0
- package/template/.forge/migrations/0.44.0-desire-paths.md +57 -0
- 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": "
|
|
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
|
|
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
|
-
|
|
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
|
|
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**
|
|
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
|
-
|
|
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
|
-
|
|
312
|
-
#
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
- **
|
|
318
|
-
|
|
319
|
-
|
|
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.
|
package/template/.forge/FORGE.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
140
|
-
|
|
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** (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
293
|
-
- **Next number = `max(
|
|
294
|
-
-
|
|
295
|
-
- **Reserve points.** `architecting` reserves an `adr` before authoring it; `planning` reserves `fr`/`nfr`/`def` before writing them into requirements
|
|
296
|
-
- **
|
|
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,
|
|
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
|
|