forge-orkes 0.77.0 → 0.80.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 (41) hide show
  1. package/bin/create-forge.js +27 -0
  2. package/experimental/m10/README.md +4 -4
  3. package/experimental/m10/install.sh +10 -9
  4. package/experimental/m10/source/hooks/protect-primary-checkout.sh +149 -0
  5. package/package.json +1 -1
  6. package/template/.claude/hooks/forge-context-migrate.sh +297 -0
  7. package/template/.claude/hooks/forge-size-gate.sh +175 -0
  8. package/template/.claude/hooks/forge-state-rollup.sh +135 -2
  9. package/template/.claude/hooks/protect-primary-checkout.sh +149 -0
  10. package/template/.claude/hooks/skill-gate-tiers.txt +6 -0
  11. package/template/.claude/hooks/tests/forge-context-migrate.test.sh +166 -0
  12. package/template/.claude/hooks/tests/forge-size-gate.test.sh +132 -0
  13. package/template/.claude/hooks/tests/protect-primary-checkout.test.sh +147 -0
  14. package/template/.claude/settings.json +14 -1
  15. package/template/.claude/skills/discussing/SKILL.md +30 -31
  16. package/template/.claude/skills/executing/SKILL.md +3 -3
  17. package/template/.claude/skills/forge/SKILL.md +112 -170
  18. package/template/.claude/skills/forge/references/manual-fallback-procedures.md +54 -0
  19. package/template/.claude/skills/initializing/SKILL.md +20 -1
  20. package/template/.claude/skills/planning/SKILL.md +11 -9
  21. package/template/.claude/skills/researching/SKILL.md +2 -0
  22. package/template/.claude/skills/reviewing/SKILL.md +17 -0
  23. package/template/.claude/skills/upgrading/SKILL.md +22 -2
  24. package/template/.claude/skills/verifying/SKILL.md +2 -2
  25. package/template/.forge/FORGE.md +81 -90
  26. package/template/.forge/bin/new-worktree.sh +61 -0
  27. package/template/.forge/bin/tests/new-worktree.test.sh +70 -0
  28. package/template/.forge/checks/forge-jarvis-awareness.sh +46 -2
  29. package/template/.forge/checks/forge-jarvis-relay.sh +64 -16
  30. package/template/.forge/checks/tests/forge-jarvis-awareness.test.sh +58 -0
  31. package/template/.forge/checks/tests/forge-jarvis-relay.test.sh +50 -0
  32. package/template/.forge/git-hooks/pre-commit +57 -0
  33. package/template/.forge/git-hooks/tests/pre-commit.test.sh +97 -0
  34. package/template/.forge/migrations/0.78.0-weld-primary-checkout.md +66 -0
  35. package/template/.forge/migrations/0.80.0-context-md-sharding.md +158 -0
  36. package/template/.forge/migrations/0.80.0-size-gate-wiring.md +145 -0
  37. package/template/.forge/templates/constitution.md +1 -1
  38. package/template/.forge/templates/context-milestone.md +30 -0
  39. package/template/.forge/templates/slice-runner/config.yml +7 -1
  40. package/template/.forge/templates/state/desire-path.yml +3 -0
  41. package/experimental/m10/source/hooks/forge-branch-guard.sh +0 -61
@@ -1,17 +1,15 @@
1
1
  # Forge
2
2
 
3
- Lean meta-prompting framework. Context engineering + constitutional governance on agent-native primitives. This file is the always-loaded **routing/index layer**: tables, invariants, and pointers. Procedures live in the skills that execute them; rationale lives in ADRs — both load only on demand.
3
+ Lean meta-prompting framework. Context engineering + constitutional governance on agent-native primitives. This file is the always-loaded **routing/index layer**: tables, invariants, pointers. Procedures live in the skills that execute them; rationale lives in ADRs — both load on demand only.
4
4
 
5
5
  ## Critical: No Native Plan Mode
6
6
 
7
- **NEVER use `EnterPlanMode` when Forge active.** Forge writes plans to `.forge/phases/` — native plan mode conflicts.
8
-
9
- All phases: **invoke via `Skill` tool**, never native behavior. `planning` → `Skill(planning)`, not `EnterPlanMode`.
7
+ **NEVER use `EnterPlanMode` when Forge active.** Forge writes plans to `.forge/phases/` — native plan mode conflicts. All phases: invoke via `Skill` tool, e.g. `planning` → `Skill(planning)`, not `EnterPlanMode`.
10
8
 
11
9
  ## Core Principles
12
10
 
13
11
  1. **Lean default, powerful when needed.** Quick → skip ceremony. Complex → full governance.
14
- 2. **Adapter-native.** Use each platform's built-ins through adapters. Avoid custom runtime unless the substrate truly needs it.
12
+ 2. **Adapter-native.** Use each platform's built-ins through adapters; avoid custom runtime unless the substrate needs it.
15
13
  3. **Context is sacred.** Size-gate artifacts, lazy-load skills, fresh agents for isolated work.
16
14
  4. **Decisions are contracts.** Lock before building. Honor or flag — never silently override.
17
15
  5. **Verify goals, not tasks.** "Does it work?" > "Did we finish checklist?"
@@ -21,7 +19,7 @@ All phases: **invoke via `Skill` tool**, never native behavior. `planning` → `
21
19
 
22
20
  ## Core Protocol And Adapters
23
21
 
24
- **Core protocol** lives in `.forge/`: project/constitution state, milestones, requirements, roadmap, context, streams, work packages, plans, verification, audits, and state ownership rules — durable behavior, not one platform's tool names. **Platform adapters** translate the protocol into a runtime (Claude Code skills/hooks, Codex/AGENTS behavior, manual multi-session handoffs). Adapter mechanics stay in adapter files and skills; they do not become core requirements.
22
+ **Core protocol** lives in `.forge/`: project/constitution state, milestones, requirements, roadmap, context, streams, work packages, plans, verification, audits, state ownership rules — durable behavior, not one platform's tool names. **Platform adapters** translate the protocol into a runtime (Claude Code skills/hooks, Codex/AGENTS behavior, manual handoffs) and stay out of core requirements.
25
23
 
26
24
  ## Project Streams
27
25
 
@@ -33,18 +31,16 @@ Three runtime coordination layers: Project, Stream, Work Package.
33
31
  | **Stream** | One active line of work: goal, branch/worktree, state, blockers, next action | `.forge/streams/{stream}.yml` + `brief.md` |
34
32
  | **Work Package** | Delegated bounded task inside a stream | `.forge/streams/{stream}/packages/{id}.yml` |
35
33
 
36
- Ordinary Quick/Standard/Full tiers run inside a stream. Project Chief / Chief of Staff coordinates multiple active streams or pivots without dropping context. Workers never coordinate laterally; they report to the stream or project chief. M10 worktrees are an optional isolation backend, not the primary UX.
34
+ Ordinary Quick/Standard/Full tiers run inside a stream; Project Chief / Chief of Staff coordinates multiple active streams or pivots without dropping context. Workers report to the stream or project chief, never laterally. M10 worktrees are an optional isolation backend, not the primary UX.
37
35
 
38
- **Merge often and early.** Integrate ready work **as soon as it is ready**, not at milestone end long-lived stream branches drift and the merge grows riskier the longer it waits. Realized producer-side (checkpoint publishing, below) and project-side (the Chief's Merge Cadence Check). Forge never force-pushes, never auto-resolves a conflict, and never auto-rebases.
36
+ **Merge often and early** integrate ready work as soon as it is ready, not at milestone end (long-lived branches drift, merges grow riskier). Realized producer-side (checkpoint publishing, below) + project-side (the Chief's Merge Cadence Check). Forge never force-pushes, auto-resolves a conflict, or auto-rebases.
39
37
 
40
38
  ### Stream Integration Checkpoints
41
39
 
42
- Event-driven continuous integration. **Mechanism + rationale: [ADR-027](../docs/decisions/ADR-027-integration-checkpoints-declaration-split.md).** Producer implementation: `verifying` (Integration Checkpoint Publish). Consumer: `forge` boot (Step 1 preflight). In brief:
40
+ Event-driven continuous integration ([ADR-027](../docs/decisions/ADR-027-integration-checkpoints-declaration-split.md)). Producer: `verifying` (Integration Checkpoint Publish). Consumer: `forge` boot (Step 1 preflight).
43
41
 
44
- - **Checkpoints are plan-marked** (`integration_checkpoint: true` in plan frontmatter; planning proposes last phase + depended-on phases the operator confirms). **The mark is the opt-in**: unmarked plans never auto-publish.
45
- - **Producer:** on a checkpoint phase's verified PASS in a worktree session, fast-forward-only publish (`git push origin HEAD:main`), **strand-guarded** — publish only when local `main` is an ancestor of the worktree HEAD; stranded do not push, surface "integrate through local main first" (forge#13). On success, set the **integration flag** — `.git/forge/integration-pending` in the machine-local git common dir.
46
- - **Intake (declaration split, B3'):** a new milestone/stream's **first** worktree commit is a declaration — milestone id (+ reservation), one-line intent, stream registration with `ownership` claims — ff-pushed to main immediately; content stays worktree-only until the milestone's own checkpoint. One-breath rule: main knows THAT work exists; the worktree knows WHAT it is.
47
- - **Consumer:** boot reads the flag (absent or already-seen → skip; no fetch, no scan). Clean fast-forwards run automatically in worktrees **and** the primary main checkout; any divergence surfaces a prompt — never an auto-rebase. Cross-machine, the flag doesn't exist — the opt-in `forge.worktree_rebase_check` / periodic fetch covers it, eventually.
42
+ - **Producer:** plan-marked checkpoints (`integration_checkpoint: true`, operator-confirmed) fast-forward-push on a worktree's verified PASS, strand-guarded (push only if local `main` is an ancestor of HEAD; stranded "integrate through local main first", forge#13), then set the machine-local integration flag. A stream's first worktree commit is a lighter declaration-only push (id + intent) so main knows work exists before content lands.
43
+ - **Consumer:** boot reads the flag (absent/seen skip, no fetch); clean fast-forwards run automatically, any divergence prompts never auto-rebase. Cross-machine: opt-in `forge.worktree_rebase_check`.
48
44
 
49
45
  ### Worktree Convention (base)
50
46
 
@@ -59,6 +55,8 @@ Where worktrees live and what they're named — single source of truth, base con
59
55
 
60
56
  **Recorded path is authoritative once set** — `lifecycle.worktree_path` (milestone) / `runtime.worktree` (stream) is read verbatim, never re-derived; changing `worktree_root` affects only new worktrees; move a live worktree only with `git worktree move` + update the record. No recorded path → resolve from the convention. `forge` boot and `chief-of-staff` adoption **validate advisorily**: recorded path outside the resolved root → warn (never block, never move); an explicit `worktree_root` is honored.
61
57
 
58
+ **Primary checkout is welded to the default branch — all tiers** ([ADR-031](../docs/decisions/ADR-031-weld-primary-checkout-to-default-branch.md)). Its HEAD is shared across concurrent sessions; a `checkout -b` there silently redirects others' commits. Enforced (`git-dir == git-common-dir` scoped, never touches worktrees): `.claude/hooks/protect-primary-checkout.sh` (PreToolUse — denies branch create/switch/detach + hard-reset) + `.forge/git-hooks/pre-commit` (off-default-commit backstop; wired per-clone by `initializing`/`upgrading` via repo-relative `core.hooksPath`, detect+warn+never-clobber). Branch work: `.forge/bin/new-worktree.sh <slug>`. Escape: `ALLOW_MAIN_BRANCHING=1`.
59
+
62
60
  **`orchestration.worktree_hydrate`** (optional, `project.yml`; forge#18) lists superproject-relative paths to required-but-gitignored artifacts (e.g. `.mcp.json`) a fresh worktree's tracked-only checkout lacks. Copy is guarded `source-exists && dest-absent` — never overwrites, never `git add`s, so the secret stays untracked in both trees. Applied by `orchestrating` at creation and re-asserted idempotently in `executing`'s Workspace Prerequisites, covering worktrees of any origin. Omit ⇒ no propagation. The per-worktree runtime counterpart of the install-time carve-out propagation below (State Ownership § Stable shared).
63
61
 
64
62
  ## Workflow Tiers
@@ -66,9 +64,9 @@ Where worktrees live and what they're named — single source of truth, base con
66
64
  Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
67
65
 
68
66
  ### Prototyping (minutes–hours, disposable)
69
- **Triggers:** "mock up"/"sketch"/"prototype"/"draw up"/"iterate on the UI"/"explore how X could look" — disposable in-app visual exploration behind an alpha/feature flag, no requirements locked yet
67
+ **Triggers:** disposable in-app visual exploration ("mock up", "sketch", "prototype", "explore how X could look") behind an alpha/feature flag, no requirements locked yet
70
68
  **Flow:** `prototyping` — establish flag → iterate ⟲ (render → react → adjust) → resolve (graduate→milestone | park | discard)
71
- **No verify/review gate by design** — the deliverable is a thing to look at and react to, not shippable code. Real UI work with known requirements is Standard + `designing` (which *does* gate on quality). See [ADR-022](../docs/decisions/ADR-022-prototyping-tier.md).
69
+ **No verify/review gate by design** — a thing to look at and react to, not shippable code. Real UI work with known requirements is Standard + `designing` (which *does* gate on quality). See [ADR-022](../docs/decisions/ADR-022-prototyping-tier.md).
72
70
 
73
71
  ### Quick (minutes)
74
72
  **Triggers:** bug fix, typo, config, dep bump, <50 lines
@@ -89,29 +87,27 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
89
87
 
90
88
  ## Skill Routing
91
89
 
92
- | Need to... | Skill | Tier |
93
- |------------|-------|------|
94
- | Start any task | `forge` | All |
95
- | Set up new project | `initializing` | First run |
96
- | Investigate codebase/tech/requirements | `researching` | Standard, Full |
97
- | Talk through approach/trade-offs | `discussing` | Standard, Full (on-demand) |
98
- | Architectural decisions | `architecting` | Full |
99
- | Break work into tasks with gates | `planning` | Standard, Full |
100
- | Build with deviation rules + atomic commits | `executing` | All |
101
- | Prove work delivers on goals (+ M9 e2e validation gate; captures the Human Verification Gate sign-off) | `verifying` | Standard, Full |
102
- | Audit health + catalog refactoring (+ M9 e2e audits; hard-blocks completion on the Human Verification Gate) | `reviewing` | Standard, Full |
103
- | Small scoped fix | `quick-tasking` | Quick |
104
- | Rapid disposable UI mockup behind an alpha flag (no verify/review gate) | `prototyping` | Prototyping |
105
- | UI with design system | `designing` | When UI |
106
- | Security review | `securing` | When auth/data/API |
107
- | E2E/integration tests + suite audit (+ M9 author-mode gate) | `testing` | When UI/flows or flaky suite |
108
- | Systematic debugging | `debugging` | When stuck |
109
- | Upgrade Forge files from the npm registry (default; local dev sync only via explicit `/upgrading dev`) | `upgrading` | On-demand |
110
- | Cross-session memory | `beads-integration` | When Beads installed |
111
- | Project/read Forge work in Notion | `notion-integration` | When Notion configured |
112
- | Multi-agent backend (experimental) | `orchestrating` | Optional, usually selected by Chief/Streams |
113
-
114
- > Experimental skills require opt-in install — see `packages/create-forge/experimental/m10/README.md`.
90
+ | Need to... | Skill |
91
+ |------------|-------|
92
+ | Start any task | `forge` |
93
+ | First-run project setup | `initializing` |
94
+ | Investigate before building | `researching` |
95
+ | Talk through trade-offs | `discussing` |
96
+ | Architectural decisions | `architecting` |
97
+ | Break work into gated tasks | `planning` |
98
+ | Build with deviation rules + atomic commits | `executing` |
99
+ | Prove goals delivered (+ M9 e2e gate; see Human Verification Gate) | `verifying` |
100
+ | Health/security/refactor gate + close-the-loop | `reviewing` |
101
+ | Small scoped fix | `quick-tasking` |
102
+ | Disposable UI mockup behind an alpha flag | `prototyping` |
103
+ | UI with design system | `designing` |
104
+ | Security review | `securing` |
105
+ | E2E/integration tests + suite audit (+ M9 author-mode gate) | `testing` |
106
+ | Systematic debugging | `debugging` |
107
+ | Upgrade Forge files (registry default; `/upgrading dev` for local sync) | `upgrading` |
108
+ | Cross-session memory | `beads-integration` |
109
+ | Project/read Forge work in Notion | `notion-integration` |
110
+ | Multi-agent backend (experimental — see `packages/create-forge/experimental/m10/README.md`) | `orchestrating` |
115
111
 
116
112
  ## Context Engineering
117
113
 
@@ -123,7 +119,7 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
123
119
  | `requirements/m{N}.yml` | 50 KB/file | Prevents scope creep |
124
120
  | `plan.md` | 30 KB | Keeps executor context <50% |
125
121
  | `constitution.md` | 10 KB | Gates must be scannable |
126
- | `context.md` | 12 KB | Active decisions only; history archives to `context-archive.md` |
122
+ | `context.md` | 12 KB | Derived index, rendered fresh every boot — one row per milestone, pointing at `context/{id}.md` (full decisions live there, single-owner-mutable) |
127
123
  | `refactor-backlog.yml` | 150 KB | Terminal items archive to `refactor-backlog-archive.yml` (advisory) |
128
124
  | `streams/active.yml` | 20 KB | Traffic map must stay cheap to load |
129
125
  | `streams/{stream}.yml` | 15 KB | Stream state stays resumable without becoming history |
@@ -134,89 +130,84 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
134
130
 
135
131
  ### Accumulating Artifacts
136
132
 
137
- Every new accumulating artifact declares: owner, size gate, archive/compaction rule, `derived` vs source-of-truth status, and migration behavior. Defaults: `roadmap.yml`, `requirements/*.yml`, `releases.yml`, `reservations.yml` are source-of-truth — load scoped slices, not whole history. `phases/`, `audits/`, `research/`, `archive/`, `context-archive.md`, `reservations-archive.yml` are historical — load only by milestone/stream/package/version (R104: `reservations-archive.yml` holds every row `forge-reserve compact` has dropped from `reservations.yml`, verbatim; the working file alone still carries every kind's floor). `prototypes/{slug}.md` is single-owner mutable while `exploring|parked`; terminal statuses archive to `prototypes/archive/`. Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs, `.git/forge/*`) are excluded from framework context unless debugging them. The board (ADR-025): `.forge/notion/map.yml` is derived, git-ignored, rebuildable; board config is the `board:` block in `project.yml` (absent ⇒ inert). Template copies sync mechanically — not separate concepts.
133
+ Every new accumulating artifact declares owner, size gate, archive/compaction rule, `derived` vs source-of-truth status, migration behavior. `roadmap.yml`, `requirements/*.yml`, `releases.yml`, `reservations.yml` are source-of-truth — load scoped slices, not whole history. `phases/`, `audits/`, `research/`, `archive/`, `context-archive.md`, `reservations-archive.yml` are historical — load only by milestone/stream/package/version (R104: `reservations-archive.yml` holds every row `compact` dropped; the working file keeps each kind's floor). `prototypes/{slug}.md` is single-owner while `exploring|parked`; terminal `prototypes/archive/`. Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs, `.git/forge/*`) are excluded unless debugging. Board (ADR-025): `.forge/notion/map.yml` is derived; config is `board:` in `project.yml` (absent ⇒ inert). Template copies sync mechanically.
138
134
 
139
135
  ### Fresh Agent Pattern
140
- Task touches 20+ files or complex subsystem → spawn fresh executor with isolated context. Prevents context rot.
136
+ 20+ files or a complex subsystem → spawn a fresh executor with isolated context. Prevents context rot.
141
137
 
142
138
  ### Context Handoff
143
- Each phase writes outputs to `.forge/` before completing. At phase boundaries, recommend `/clear`; the next phase loads from disk. Advisory — skip for short phases under 40% context.
139
+ Each phase writes outputs to `.forge/` before completing; at boundaries recommend `/clear`, next phase loads from disk. Advisory — skip under 40% context.
144
140
 
145
141
  ### Lazy Loading
146
- Skills load only when invoked. Active context is `.forge/context.md`; consult `.forge/context-archive.md` only when older decisions matter.
142
+ Skills load only when invoked. `.forge/context.md` is a derived index (id/name/status/path per milestone) — consult it for orientation, then read the specific `context/{id}.md` when a milestone's actual decisions are needed. `context-archive.md` predates this model and is superseded (its content lives in `context/{id}.md` now); consult it only for pre-0.80.0 history not yet folded in.
147
143
 
148
144
  ### Rules Layer (`.claude/rules/`)
149
- Three policy weights, lightest-first (ADR-018): **`constitution.md`** (always-loaded hard gates, 10 KB) → **`.claude/rules/`** (lightweight ambient guidance — always-on, or path-scoped via `paths:` glob at zero cost until a matching file is touched) → **skills** (heavy, opt-in). Core ships one always-on rule, `agent-discipline.md`; stack-specific rules are opt-in (`experimental/conventions/`). Format + authoring: `.claude/rules/README.md` (itself always loaded).
145
+ Three policy weights, lightest-first (ADR-018): `constitution.md` (always-loaded hard gates, 10 KB) → `.claude/rules/` (ambient guidance — always-on, or path-scoped via `paths:` glob at zero cost until a matching file is touched) → skills (heavy, opt-in). Core ships one always-on rule, `agent-discipline.md`; stack-specific rules are opt-in (`experimental/conventions/`). Format + authoring: `.claude/rules/README.md`.
150
146
 
151
147
  ## Model Routing
152
148
 
153
- Configured in `project.yml` under `models:` — `default`, `parent_session` (advisory), `by_complexity.{trivial|standard|complex}` (per-task, M24), `skills.{name}` (per-skill).
149
+ Configured in `project.yml` under `models:` — `default`, `parent_session` (advisory), `by_complexity.{trivial|standard|complex}` (per-task), `skills.{name}` (per-skill).
154
150
 
155
- **Precedence:** `by_complexity.{task.complexity}` → `skills.X` → `default` → parent model. **Complexity overrides skill** (LOCKED, context.md § M24): a task's `by_complexity` resolution wins over `models.skills.{skill}` the per-task signal is the granular one; a task with no complexity resolves via the unchanged chain, zero behavior change untagged.
151
+ **Precedence:** `by_complexity.{task.complexity}` → `skills.X` → `default` → parent model. **Complexity overrides skill** (LOCKED, `context.md` § M24) — a task with no complexity resolves via the unchanged chain, zero behavior change untagged.
156
152
 
157
- **All advisory** — forge displays the expected model at each transition and suggests `/model` on mismatch; the review gate logs the model used. Forge cannot auto-switch models.
153
+ **All advisory** — forge displays the expected model at each transition, suggests `/model` on mismatch; the review gate logs the model used. Forge cannot auto-switch models.
158
154
 
159
- **Subagent inheritance.** A skill spawning subagents MUST pass the resolved `model` param explicitly — subagents do NOT inherit skill-level routing (without the param they run on the parent model, defeating the routing intent). Resolve **per task** where the task declares `complexity`, else the skill chain; surface *"Spawning {role} with model: {model} (from {source})"*. An inline task inherits the session model and cannot be re-routed.
155
+ **Subagent inheritance.** A skill spawning subagents MUST pass the resolved `model` param explicitly — subagents do NOT inherit skill-level routing. Resolve per task where it declares `complexity`, else the skill chain; surface *"Spawning {role} with model: {model} (from {source})"*. An inline task inherits the session model.
160
156
 
161
- **Spawn gate — predicate A (LOCKED, context.md § M24; refined m-33).** Executing's mandatory spawn on a complexity-tagged task fires **only when the resolved model differs from the live session model**. Resolved == live always the case with no `models:` block makes the spawn a pure no-op, so the tag becomes a context signal only (spawn iff a context trigger independently fires). WHY: an unconditional spawn burned ~315k subagent tokens on m-30 for zero routing benefit.
157
+ **Spawn gate — predicate A (LOCKED, `context.md` § M24; refined m-33).** Executing's mandatory spawn on a complexity-tagged task fires only when the resolved model differs from the live session model resolved == live (always true with no `models:` block) makes the spawn a no-op, so the tag becomes a context signal only.
162
158
 
163
159
  ## Agents
164
160
 
165
161
  | Agent | Role | Tools | When |
166
162
  |-------|------|-------|------|
167
- | `researcher` | Investigation | Read-only (Read, Glob, Grep, WebFetch) | Research |
168
- | `planner` | Planning + constitutional gates | Read + Write (plan files) | Planning |
163
+ | `researcher` | Investigation | Read-only | Research |
164
+ | `planner` | Planning + constitutional gates | Read + Write (plans) | Planning |
169
165
  | `executor` | Building + deviation rules | All dev tools | Execution |
170
166
  | `verifier` | Goal-backward verification | Read + Bash (tests) | Verification |
171
- | `reviewer` | Security + architecture + refactoring (breadth pass, gates the health report) | Read, Bash, Grep, Glob | Reviewing |
172
- | `tester` | E2E/integration authoring + suite analysis | Dual-mode: author (Write/Edit/Bash/git) · analyst (read-only) | Testing skill |
173
- | `security-reviewer` | Confidence-gated security depth (≥8, exploit-scenario required) | Read, Grep, Glob, Bash | Optional deep-dive |
174
- | `performance-reviewer` | Real bottlenecks only (Impact = frequency × cost; ≥8 + ≥Medium) | Read, Grep, Glob, Bash | Optional deep-dive |
175
- | `doc-reviewer` | Docs-vs-source accuracy (≥8; Blocker/Major/Minor) | Read, Grep, Glob, Bash | Optional deep-dive |
167
+ | `reviewer` | Security/architecture/refactoring (breadth, gates the health report) | Read, Bash, Grep, Glob | Reviewing |
168
+ | `tester` | E2E/integration authoring + suite analysis | Author (Write/Edit/Bash/git) or read-only | Testing skill |
169
+ | `security-reviewer` | Security depth (≥8, exploit-scenario required) | Read, Grep, Glob, Bash | Deep-dive |
170
+ | `performance-reviewer` | Real bottlenecks only (frequency × cost; ≥8 + ≥Medium) | Read, Grep, Glob, Bash | Deep-dive |
171
+ | `doc-reviewer` | Docs-vs-source accuracy (≥8; Blocker/Major/Minor) | Read, Grep, Glob, Bash | Deep-dive |
176
172
 
177
- The three specialist reviewers complement the generalist `reviewer` — single-axis, defensible depth, read-only, pulled in deliberately (from `reviewing` or direct). The 3-mode `reviewer` remains the breadth pass that gates the milestone health report.
173
+ The three specialists complement `reviewer` — single-axis, read-only, pulled in deliberately. `reviewer`'s 3-mode pass remains the breadth gate on the milestone health report.
178
174
 
179
175
  ## Project Init (First Run)
180
176
 
181
- No `.forge/project.yml` → auto-detect project type:
182
-
183
- **Brownfield** (has package.json/src/.git): detect frameworks → absorb → verify docs vs code → scan stack → detect design system → analyze patterns → infer constitution → user confirms.
184
-
185
- **Greenfield**: project basics → design system → constitution → state init.
186
-
187
- Quick tier skips init.
177
+ No `.forge/project.yml` → auto-detect project type. **Brownfield** (has package.json/src/.git): frameworks → absorb → verify docs vs code → scan stack → design system → patterns → infer constitution → user confirms. **Greenfield**: project basics → design system → constitution → state init. Quick tier skips init.
188
178
 
189
179
  ## State Management
190
180
 
191
181
  State lives in `.forge/`. One line per artifact; protocol detail lives in the named ADRs and skills:
192
182
 
193
- - `project.yml` — vision, stack, design system, verification, constraints (<5 KB). Optional `jarvis.dev_lane_cmd` (m-48) is the stack-agnostic seam Jarvis runs to spin up a slice's local dev server for operator UAT before a PR opens; absent ⇒ manual lane start. See `docs/jarvis.md`.
183
+ - `project.yml` — vision, stack, design system, verification, constraints (<5 KB). Optional `jarvis.dev_lane_cmd` (m-48) seams Jarvis's dev-server spin-up for UAT; absent ⇒ manual start (`docs/jarvis.md`).
194
184
  - `constitution.md` — active architectural gates
195
185
  - `design-system.md` — component mapping table
196
- - `requirements/m{N}.yml` — per-milestone structured requirements with `[NEEDS CLARIFICATION]` markers. **FR/DEF/NFR ids are globally unique across all milestone files** — reserve via the ID Reservation Protocol before writing. M9 e2e gate fields (`e2e`, `observable_outcome`, `validated`, hash) lazy-migrate; absent ⇒ `false`.
186
+ - `requirements/m{N}.yml` — per-milestone structured requirements, `[NEEDS CLARIFICATION]` markers. **FR/DEF/NFR ids globally unique** — reserve first. M9 e2e gate fields lazy-migrate; absent ⇒ `false`.
197
187
  - `roadmap.yml` — phases, milestones, dependencies
198
- - `state/index.yml` — DERIVED milestone registry, **git-ignored render-on-read cache** (0.53.0): regenerated at every boot by the **`forge-state-rollup.sh` SessionStart hook** (the mechanical port of Rollup 1.0 — issue #22; skill prose only *reads* it) from the milestone files; never committed, never hand-edited; the hook renders it in any checkout (gitignored local write), but worktree agents still never write the *committed* derived-source state; absent on a fresh clone (the project sentinel is `project.yml`). Rows carry the derived `release_state`.
199
- - **Derived `release_state`** ([ADR-024](../docs/decisions/ADR-024-merged-validated-fold.md)) — `{unmerged, merged, validated}` computed at rollup by `.claude/hooks/forge-release-fold.sh` (git-only, offline; the fold binary is the only source — agent prose never re-derives it), never stored. Validation binding via `forge.validation_event`: `in_session_signoff` (default; absent ⇒ this) or `promotion_approval` (CI-signed `promotion/{sha}` tag). Inert until a repo opts in; full spec in ADR-024 + the fold header.
188
+ - `state/index.yml` — DERIVED milestone registry, git-ignored render-on-read cache (0.53.0), regenerated at boot by `forge-state-rollup.sh` (issue #22; read-only for skills); never committed; absent on a fresh clone (sentinel `project.yml`). Rows carry derived `release_state`.
189
+ - **Derived `release_state`** ([ADR-024](../docs/decisions/ADR-024-merged-validated-fold.md)) — `{unmerged, merged, validated}`, computed at rollup (`forge-release-fold.sh`, git-only, offline), never stored. Binding via `forge.validation_event`: `in_session_signoff` (default) or `promotion_approval` (CI-signed tag); inert until opted in.
200
190
  - `state/milestone-{id}.yml` — per-milestone cursor (**single source of truth**): position, decisions, blockers. One owner at a time.
201
- - `state/desire-paths/` — append-only framework-usage observations, one file per observation, `scope: project|framework` (absent ⇒ project); occurrence counts derived by globbing. **`forge` boot is the single review owner** — surfaces groups at 3+, persists declines (no re-nag), routes `framework`-scope signals upstream ([ADR-012](../docs/decisions/ADR-012-upstream-desire-path-feedback-channel.md)); `verifying` captures but never surfaces.
202
- - `context.md` — active locked decisions + deferred ideas (≤12 KB); history archives to `context-archive.md`
203
- - `streams/active.yml` — DERIVED Project Chief traffic map, git-ignored render-on-read cache, rendered by the same `forge-state-rollup.sh` hook — see Stream Rollup below ([ADR-015](../docs/decisions/ADR-015-derived-stream-registry.md))
204
- - `streams/{stream}.yml` (+ `brief.md`, `packages/{id}.yml`) — per-stream **source of truth**: coordination lifecycle, ownership, shared surfaces, blockers, merge readiness, optional `stream.milestone` link (milestone-backed streams omit workflow phase — the rollup reads it from the milestone)
191
+ - `state/desire-paths/` — append-only usage observations, `scope: project|framework` (absent ⇒ project); counts derived by globbing. `forge` boot is the single review owner — surfaces groups at 3+, persists declines, routes `framework`-scope upstream ([ADR-012](../docs/decisions/ADR-012-upstream-desire-path-feedback-channel.md)); `verifying` captures, never surfaces. Issue-sourced entries carry `resolves_issue: N`.
192
+ - `context/{id}.md` (+ derived `context.md` index) — locked decisions + deferred ideas per milestone; history archives to `context-archive.md`
193
+ - `streams/active.yml` — DERIVED traffic map, git-ignored render-on-read cache, same hook — see Stream Rollup ([ADR-015](../docs/decisions/ADR-015-derived-stream-registry.md))
194
+ - `streams/{stream}.yml` (+ `brief.md`, `packages/{id}.yml`) — per-stream **source of truth**: lifecycle, ownership, surfaces, blockers, merge readiness, optional `stream.milestone` link (rollup reads phase from the milestone)
205
195
  - `research/milestone-{id}.md` — dated immutable findings snapshot
206
196
  - `phases/milestone-{id}/{phase}-{name}/plan-{NN}.md` — task plans with must_haves frontmatter (`{id}`=milestone, `{phase}`=phase# verbatim, `{name}`=kebab, `{NN}`=seq)
207
- - `refactor-backlog.yml` — refactoring catalog worked via quick-tasking; status vocab `pending | in_progress | done | dismissed | deferred`; terminal items auto-archive to `refactor-backlog-archive.yml` on the next reviewing/quick-tasking write
197
+ - `refactor-backlog.yml` — quick-tasking catalog; vocab `pending|in_progress|done|dismissed|deferred`; terminal items auto-archive
208
198
  - `releases.yml` — append-only version-reservation registry (Version Reservation Protocol)
209
- - `reservations.yml` — append-only id-reservation audit trail + cross-machine floor (ID Reservation Protocol); `forge-reserve compact` (R104) moves dropped rows verbatim to `reservations-archive.yml`, retaining each kind's max + a 14-day tail
199
+ - `reservations.yml` — append-only id-reservation audit trail + cross-machine floor; `forge-reserve compact` (R104) moves dropped rows to `reservations-archive.yml`, keeping each kind's max + a 14-day tail
200
+ - `state/issue-loop/declined/{N}.yml` — close-the-loop decline markers (no re-nag) from `reviewing`; inert once the issue closes
210
201
  - `archive/milestone-{id}/` — archived milestone artifacts (archive-delete)
211
- - `prototypes/{slug}.md` — resumable disposable-mockup state (`exploring | parked | graduated | discarded`); terminal → `prototypes/archive/`. Single-owner, one driver. See `Skill(prototyping)`.
202
+ - `prototypes/{slug}.md` — disposable-mockup state (`exploring|parked|graduated|discarded`); terminal → `prototypes/archive/`. Single-owner. See `Skill(prototyping)`.
212
203
 
213
- **Milestones** group phases into concurrent streams — own state file, no conflicts across sessions; can be deferred (frozen in place) or archive-deleted. Registry status vocab: `not_started | active | deferred | complete`. **`current.status` is authoritative** — complete only at `current.status == complete`; 100% tasks ≠ done (verifying + reviewing + the recorded human sign-off still gate).
204
+ **Milestones** group phases into concurrent streams — own state file, no cross-session conflicts; deferred (frozen) or archive-deleted. Status vocab: `not_started | active | deferred | complete`. **`current.status` is authoritative** — 100% tasks ≠ done (verifying + reviewing + the recorded human sign-off still gate).
214
205
 
215
206
  **Format:** YAML for machine state, Markdown for human content.
216
207
 
217
208
  ### Stream Rollup
218
209
 
219
- `active.yml` is a pure projection — regenerated, never hand-edited — by a deterministic, idempotent **join**: per-stream files contribute coordination state; active milestones contribute `phase` (any active milestone with no stream file gets a derived `implicit` row — coverage is computed, not maintained); `merge_queue` derives from `merge.readiness: ready`. A **step-0 completed-stream auto-close sweep** runs first: a milestone-backed stream whose milestone is `complete`, not yet `closed`, with no live worktree is auto-closed — a done-and-merged milestone can never leave a zombie "ready to merge" row. Rendered mechanically by the **`forge-state-rollup.sh` SessionStart hook** at boot (issue #22 — an executable, not agent prose), and re-run via that same executable at `chief-of-staff` Show/Sync and after any mid-session milestone CRUD; the hook runs the milestone Rollup 1.0 **first** (the join reads the fresh `index.yml`), and confines the committed-source step-0 sweep to the main checkout. **Numbered procedure: the ADR-015 addendum.** Because each field has exactly one source and coverage is computed, two-registry drift cannot occur — supersedes the old advisory Registry Drift Check.
210
+ `active.yml` is a deterministic **join**: per-stream files contribute coordination state; active milestones contribute `phase` (stream-file-less active milestone derived `implicit` row); `merge_queue` derives from `merge.readiness: ready`. A **step-0 auto-close sweep** closes milestone-backed streams whose milestone is `complete`, not `closed`, with no live worktree. Rendered by the same hook at boot and at `chief-of-staff` Show/Sync/CRUD; Rollup 1.0 runs first, sweep confined to main. Numbered procedure: the ADR-015 addendum each field has one source, so drift cannot occur.
220
211
 
221
212
  ### State Commit Protocol
222
213
 
@@ -227,7 +218,7 @@ git add .forge/ # scoped — respects .gitignore; never `git add .`
227
218
  git commit -m "chore(forge): sync state after {phase} — m{N} {phase-name}"
228
219
  ```
229
220
 
230
- **In a worktree session, pin with the recorded path** — `git -C {worktree_path} add/commit` (`lifecycle.worktree_path` is authoritative over cwd; the process cwd can silently reset to the launch checkout across turns, and a wrong-cwd git op can even exit 0). Guard: compare `git rev-parse --show-toplevel` to the recorded path before the first write — full discipline in `executing` (Worktree cwd discipline) and `verifying` (Worktree cwd guard). State-sync commits are separate from per-task code commits (which stay atomic during executing).
221
+ **In a worktree session, pin with the recorded path** — `git -C {worktree_path} add/commit` (cwd can silently reset to the launch checkout across turns; a wrong-cwd op can even exit 0). Guard: compare `git rev-parse --show-toplevel` to the recorded path before the first write — full discipline in `executing`/`verifying`. State-sync commits are separate from per-task code commits.
231
222
 
232
223
  ### State Ownership (multi-worktree safety)
233
224
 
@@ -235,12 +226,12 @@ git commit -m "chore(forge): sync state after {phase} — m{N} {phase-name}"
235
226
 
236
227
  | Class | Files | Write rule |
237
228
  |---|---|---|
238
- | **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md` | Exactly one writer at a time — the checkout currently driving that milestone. Other worktrees never touch it (not even read-then-write; pull from main instead). |
229
+ | **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md`, `context/{id}.md` | Exactly one writer at a time — the checkout currently driving that milestone. Other worktrees never touch it (not even read-then-write; pull from main instead). |
239
230
  | **Single-owner mutable (per stream)** | `streams/{stream}.yml`, `streams/{stream}/brief.md`, `streams/{stream}/packages/*.yml` | The stream's current driver writes them; distinct streams own distinct files — no cross-stream contention. |
240
- | **Derived (git-ignored render-on-read cache)** | `state/index.yml`, `streams/active.yml`, the rendered `release_state` column | Never hand-edited, **never committed** — regenerated by rollup from sources at every boot. Any session may render its own copy; absence on a fresh clone is normal (render-then-read; the sentinel is `project.yml`). |
231
+ | **Derived (git-ignored render-on-read cache)** | `state/index.yml`, `streams/active.yml`, `context.md`, the rendered `release_state` column | Never hand-edited, **never committed** — regenerated by rollup from sources at every boot. Any session may render its own copy; absence on a fresh clone is normal (render-then-read; the sentinel is `project.yml`). `context.md` specifically joins `context/{id}.md` files with `state/index.yml` (ADR-033). |
241
232
  | **Append-only shared** | `releases.yml`, `reservations.yml`, `state/desire-paths/*`, `deferred-issues/*` | Many writers OK; structure prevents collision (append-only entries / distinct filenames). |
242
233
  | **Machine-local runtime** | `.git/forge/id-reservations`, `.git/forge/integration-pending` | Git common dir — shared across a machine's worktrees, never git-tracked, excluded from framework context. |
243
- | **Shared mutable** | `context.md`, `refactor-backlog.yml`, `requirements/m{N}.yml` | Write only your milestone/stream block; within a block, append-only — strike through instead of rewriting. |
234
+ | **Shared mutable** | `refactor-backlog.yml`, `requirements/m{N}.yml` | Write only your milestone/stream block; within a block, append-only — strike through instead of rewriting. |
244
235
  | **Stable shared** | `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `FORGE.md`, `.claude/skills/`, `.claude/agents/` | Rarely changes except via `/upgrading` (registry default — the npm bin is the sync engine; local dev sync only via explicit `/upgrading dev`). Canonical on main; worktrees lag until they rebase. An upgrade touches only its own checkout — `upgrading` Step 8 offers live-worktree rebases (never auto); opt-in `forge.worktree_rebase_check` is the boot-time net. Gitignored carve-outs (`.mcp.json`, experimental hooks) propagate via the experimental installer, not git. |
245
236
 
246
237
  Ownership rules (quick reference):
@@ -252,11 +243,11 @@ Ownership rules (quick reference):
252
243
 
253
244
  ### Version Reservation Protocol
254
245
 
255
- The project version + CHANGELOG slot are shared resources — `.forge/releases.yml` (append-only) removes the number from per-milestone scope. **Reserve early, at planning**: derive `max() + bump`, **claim it in the ledger first** `forge-reserve version --value <X.Y.Z>` (record-only; a conflict names the sibling milestone holding the number, its claim possibly worktree-only — re-read and re-bump; ADR-029) — then append ONE `{milestone, version, bump, reserved_at, summary}` entry and **commit + push the reservation BEFORE editing the version file** so a parallel session sees the number taken. Never edit prior entries. **Delivery never invents a number** write only the reserved version; no reservation → reserve at delivery (pull, `max() + bump`, append, push, then write). **Race:** the second push rebases onto the first (append-only → clean), re-reads `max()`, takes the next number. **Bump criteria (0.x):** patch = bug fix/doc/sync/refinement; minor = new skill/agent/routing-row/auto-trigger/state-artifact/capability; major = post-1.0 breaking. Default to minor when in doubt — a capability shipped as a patch drifts the version away from truth.
246
+ The version + CHANGELOG slot are shared — `.forge/releases.yml` (append-only). **Reserve at planning**: derive `max() + bump`, claim in the ledger first (`forge-reserve version --value <X.Y.Z>` conflict names the sibling milestone holding it), append ONE `{milestone, version, bump, reserved_at, summary}` entry, **commit + push before editing the version file**. Never edit prior entries; never invent a number at delivery no reservation found → reserve then, same steps. Push race rebase onto the file, re-read `max()`. Bump criteria + race narrative: `planning`'s Reserve-version step + ADR-029.
256
247
 
257
248
  ### ID Reservation Protocol
258
249
 
259
- Sequential ids `ADR-NNN`, `DEF-NNN`, `FR-`/`NFR-NNN`, milestone `M-NN`, refactor `R0NN`, roadmap phase/wave numbers — are shared cross-worktree resources; scan-and-increment sees only committed state, so concurrent worktrees claim the same number and collide silently until merge. **Reserve with the helper before creating the artifact**: `forge-reserve <kind> --milestone <id> [--summary <text>]` (`.claude/hooks/forge-reserve.sh`, kinds `adr|def|fr|nfr|milestone|refactor|phase|wave|version`; id formats per [ADR-023](../docs/decisions/ADR-023-extend-id-reservation-to-milestone-refactor-ids.md) + ADR-029 — phase/wave registry tokens are `P-0NN`/`W-0NN` while roadmap.yml keeps bare ints; `version` is record-only via `--value` (the ledger makes in-flight claims sibling-visible; `releases.yml` stays the durable registry)) prints the id and dual-writes the **machine-local ledger** (`.git/forge/id-reservations`, git common dir — the same-machine coordination point, written under a `mkdir` lock) plus the tracked `.forge/reservations.yml` (durable audit trail + cross-machine floor; the caller commits it with the work). **Next number = `max(ledger ∪ in-tree ∪ main:reservations.yml) + 1`** per kind the in-tree scan includes archives, so a compacted id is never reallocated. Reserve points: `architecting` (adr), `planning` (fr/nfr/def, phase/wave, version), `forge` Rollup / `discussing` / `prototyping` (milestone), `reviewing` (refactor). Deferred issues are counter-free per-issue files (`.forge/deferred-issues/{date}-{milestone}-{slug}.md`) no DI-NNN, nothing to collide on. Accepted gap: concurrent reserves on *different machines* fall back to merge-time renumber, bounded by the floor. On any residual collision (legacy/un-reserved ids), **keep the older milestone's id and renumber the newer**. Lazy migration — the ledger self-creates on first reserve. Full rationale: [ADR-021](../docs/decisions/ADR-021-id-reservation-ledger-git-common-dir.md) (supersedes ADR-016) + ADR-023 + ADR-029. Versions stay in `releases.yml` (separate semver/CHANGELOG semantics); the ledger only carries their claims.
250
+ Sequential ids (`ADR-NNN`, `DEF-NNN`, `FR-`/`NFR-NNN`, milestone `M-NN`, refactor `R0NN`, roadmap phase/wave) are shared cross-worktree resources scan-and-increment collides silently until merge. **Reserve before create**: `forge-reserve <kind> --milestone <id> [--summary <text>]` (`.claude/hooks/forge-reserve.sh`, kinds `adr|def|fr|nfr|milestone|refactor|phase|wave|version`). Next id = **`max(ledger ∪ in-tree ∪ main:reservations.yml) + 1`** per kind; residual collision older milestone keeps the id, newer renumbers. Reserve points: `architecting` (adr), `planning` (fr/nfr/def, phase/wave, version), `forge` Rollup/`discussing`/`prototyping` (milestone), `reviewing` (refactor). Deferred issues are counter-free — nothing to collide on. Full mechanics (ledger + lock, id-format table, cross-machine floor, lazy migration): the helper's header comment + [ADR-021](../docs/decisions/ADR-021-id-reservation-ledger-git-common-dir.md) (supersedes ADR-016) + ADR-023 + ADR-029.
260
251
 
261
252
  ## Deviation Rules
262
253
 
@@ -281,16 +272,16 @@ verification:
281
272
  - cmd: "npm run lint"
282
273
  - cmd: "npm test"
283
274
  - cmd: "npx tsc --noEmit"
284
- advisory: true # pre-existing failures — warn only
285
- auto_fix: true # fix and retry on failure
286
- max_retries: 2 # max attempts per command
275
+ advisory: true # pre-existing failures — warn only
276
+ auto_fix: true # fix and retry on failure
277
+ max_retries: 2 # max attempts per command
287
278
  ```
288
279
 
289
- Auto-detected from `package.json` at init. Advisory mode warns, never blocks. Auto-fix loop: read output → fix → amend → re-run (≤ `max_retries`; retries count toward the 3-strike task limit). Empty commands = no gate (opt-out). `verification.e2e_soft_cap` (default 10) — advisory per-milestone cap on `e2e:true` stories, surfaced by `reviewing`, never blocks.
280
+ Auto-detected from `package.json` at init. Advisory mode warns, never blocks. Auto-fix loop: read output → fix → amend → re-run (≤ `max_retries`; retries count toward the 3-strike limit). Empty commands = opt-out. `verification.e2e_soft_cap` (default 10) — advisory per-milestone cap on `e2e:true` stories, surfaced by `reviewing`, never blocks.
290
281
 
291
282
  ### Truth checks (executable must_haves)
292
283
 
293
- Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exit 0 = pass; authored goal-backward at planning, before code exists; linted by planning Step 8). `executing` runs declared checks once at plan completion (early fix-loop signal, never a verdict); `verifying` re-executes them and applies the **executed-evidence rule**: VERIFIED only with a recorded command + exit code + output excerpt — no executed evidence → UNCERTAIN or human-route, never VERIFIED. Legacy bare-string truths stay valid.
284
+ Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exit 0 = pass; authored goal-backward at planning, before code exists). `executing` runs declared checks once at plan completion (early fix-loop signal, never a verdict); `verifying` re-executes, applying the **executed-evidence rule**: VERIFIED only with a recorded command + exit code + output excerpt — no evidence → UNCERTAIN or human-route. Legacy bare-string truths stay valid.
294
285
 
295
286
  ### Human Verification Gate (close precondition)
296
287
 
@@ -305,8 +296,8 @@ Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exi
305
296
 
306
297
  ## Beads Integration (Optional)
307
298
 
308
- With Beads installed: `bd prime` (session context), `bd ready` (unblocked tasks), `bd complete` (update deps), `bd compact` (summarize old). Without Beads, `.forge/state/` + Session Memory suffice.
299
+ With Beads: `bd prime` (session context), `bd ready` (unblocked tasks), `bd complete` (update deps), `bd compact` (summarize old). Without it, `.forge/state/` + Session Memory suffice.
309
300
 
310
301
  ## Atomic Commits
311
302
 
312
- One commit per task. Format: `{type}({scope}): {description}` — types `feat fix test refactor chore docs`. Never `git add .` or `git add -A` — stage individually. Phase handoffs additionally emit one scoped `chore(forge): sync state …` commit (State Commit Protocol) so `.forge/` state never drifts out of git.
303
+ One commit per task. Format: `{type}({scope}): {description}` — types `feat fix test refactor chore docs`. Never `git add .`/`-A` — stage individually. Phase handoffs additionally emit one scoped `chore(forge): sync state …` commit (State Commit Protocol) so `.forge/` state never drifts out of git.
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # One-command worktree for any commit-producing task (issue #21, ADR-031).
3
+ #
4
+ # bash .forge/bin/new-worktree.sh <slug> [base-ref]
5
+ #
6
+ # Creates {worktree_root}/{slug} on branch forge/{slug} off origin/main (or [base-ref]).
7
+ # The primary checkout's HEAD never moves — that is the whole point of the weld: branch
8
+ # work lives in worktrees, each with its own independent HEAD, so concurrent sessions
9
+ # never clobber each other's commits.
10
+ #
11
+ # worktree_root follows the FORGE.md Worktree Convention: `orchestration.worktree_root`
12
+ # from .forge/project.yml, default `../<repo-basename>-worktrees/`. Relative paths resolve
13
+ # against the repo root; absolute / ~ honored verbatim.
14
+
15
+ set -euo pipefail
16
+
17
+ SLUG=${1:?usage: new-worktree.sh <slug> [base-ref]}
18
+ BASE=${2:-origin/main}
19
+
20
+ REPO_ROOT=$(git rev-parse --show-toplevel)
21
+ REPO_BASE=$(basename "$REPO_ROOT")
22
+
23
+ # Resolve worktree_root from project.yml (light grep — no yaml dependency). The value
24
+ # lives under an `orchestration:` block as `worktree_root: <path>`.
25
+ WT_ROOT_CFG=$(grep -E '^[[:space:]]*worktree_root:' "$REPO_ROOT/.forge/project.yml" 2>/dev/null \
26
+ | head -1 | sed -E 's/^[[:space:]]*worktree_root:[[:space:]]*//; s/^["'\'']//; s/["'\'']$//; s/[[:space:]]*(#.*)?$//' || true)
27
+
28
+ if [ -z "$WT_ROOT_CFG" ]; then
29
+ WORKTREE_ROOT="$(dirname "$REPO_ROOT")/${REPO_BASE}-worktrees"
30
+ else
31
+ case "$WT_ROOT_CFG" in
32
+ /*) WORKTREE_ROOT="$WT_ROOT_CFG" ;; # absolute — verbatim
33
+ "~"*) WORKTREE_ROOT="${WT_ROOT_CFG/#\~/$HOME}" ;; # ~ — expand
34
+ *) WORKTREE_ROOT="$REPO_ROOT/$WT_ROOT_CFG" ;; # relative — against repo root
35
+ esac
36
+ fi
37
+
38
+ # Slashes in the slug become dashes in the directory name; the branch keeps the slash.
39
+ DIR_NAME=$(printf '%s' "$SLUG" | tr '/' '-')
40
+ WT_PATH="$WORKTREE_ROOT/$DIR_NAME"
41
+ BRANCH="forge/$SLUG"
42
+
43
+ if [ -e "$WT_PATH" ]; then
44
+ echo "✋ $WT_PATH already exists. Pick another slug or remove it first (git worktree remove)." >&2
45
+ exit 1
46
+ fi
47
+
48
+ mkdir -p "$WORKTREE_ROOT"
49
+ # Best-effort refresh of origin/main so the worktree starts from current main; offline-safe.
50
+ git fetch origin main --quiet 2>/dev/null || true
51
+ git worktree add -b "$BRANCH" "$WT_PATH" "$BASE"
52
+
53
+ echo ""
54
+ echo "✅ Worktree ready"
55
+ echo " path: $WT_PATH"
56
+ echo " branch: $BRANCH (from $BASE)"
57
+ echo ""
58
+ echo " cd \"$WT_PATH\""
59
+ echo ""
60
+ echo "Reminders: run your install step after entering (worktrees don't share deps);"
61
+ echo "the primary checkout stayed on its default branch — that is intentional."
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # Scratch-repo tests for .forge/bin/new-worktree.sh (issue #21, ADR-031).
3
+ #
4
+ # Builds a throwaway repo with a committed HEAD, runs the helper (base ref = HEAD so no
5
+ # network is needed), and asserts the worktree dir + branch it creates. Self-cleans. Run:
6
+ # ./.forge/bin/tests/new-worktree.test.sh
7
+ # Prints `ALL PASS` and exits 0 iff every case passes; non-zero on any failure.
8
+
9
+ set -uo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ HELPER="$SCRIPT_DIR/../new-worktree.sh"
13
+ [ -x "$HELPER" ] || { printf 'FATAL: helper not executable: %s\n' "$HELPER" >&2; exit 1; }
14
+
15
+ ROOT="$(mktemp -d)"
16
+ trap 'rm -rf "$ROOT"' EXIT INT TERM
17
+
18
+ PASSED=0
19
+ FAILED=0
20
+ pass() { PASSED=$((PASSED + 1)); }
21
+ fail() { FAILED=$((FAILED + 1)); printf ' FAIL: %s\n' "$1" >&2; }
22
+
23
+ # A repo named so its default worktree_root is predictable: <base>-worktrees next to it.
24
+ new_repo() {
25
+ local d="$ROOT/$1"
26
+ mkdir -p "$d"
27
+ git -C "$d" init -q
28
+ git -C "$d" symbolic-ref HEAD refs/heads/main
29
+ git -C "$d" config user.email t@example.com
30
+ git -C "$d" config user.name tester
31
+ mkdir -p "$d/.forge"
32
+ : > "$d/seed"
33
+ git -C "$d" add -A && git -C "$d" commit -qm init
34
+ printf '%s\n' "$d"
35
+ }
36
+
37
+ # ── Case 1: default worktree_root, simple slug ───────────────────────────────────
38
+ R="$(new_repo proj)"
39
+ ( cd "$R" && "$HELPER" myslug HEAD ) >/dev/null 2>&1 || true
40
+ WT="$ROOT/proj-worktrees/myslug"
41
+ if [ -d "$WT" ]; then pass; else fail "case1: worktree dir not created at $WT"; fi
42
+ br=$(git -C "$WT" branch --show-current 2>/dev/null || true)
43
+ if [ "$br" = "forge/myslug" ]; then pass; else fail "case1: branch is '$br', want forge/myslug"; fi
44
+
45
+ # ── Case 2: re-running the same slug errors (dir exists) ─────────────────────────
46
+ if ( cd "$R" && "$HELPER" myslug HEAD ) >/dev/null 2>&1; then
47
+ fail "case2: duplicate slug should have errored (exit non-zero)"
48
+ else
49
+ pass
50
+ fi
51
+
52
+ # ── Case 3: slug with a slash → dashed dir, slashed branch ───────────────────────
53
+ R2="$(new_repo proj2)"
54
+ ( cd "$R2" && "$HELPER" feat/thing HEAD ) >/dev/null 2>&1 || true
55
+ WT2="$ROOT/proj2-worktrees/feat-thing"
56
+ if [ -d "$WT2" ]; then pass; else fail "case3: dashed dir not created at $WT2"; fi
57
+ br2=$(git -C "$WT2" branch --show-current 2>/dev/null || true)
58
+ if [ "$br2" = "forge/feat/thing" ]; then pass; else fail "case3: branch is '$br2', want forge/feat/thing"; fi
59
+
60
+ # ── Case 4: configured relative worktree_root honored ────────────────────────────
61
+ R3="$(new_repo proj3)"
62
+ printf 'orchestration:\n worktree_root: .wt\n' > "$R3/.forge/project.yml"
63
+ git -C "$R3" add -A && git -C "$R3" commit -qm cfg
64
+ ( cd "$R3" && "$HELPER" wtslug HEAD ) >/dev/null 2>&1 || true
65
+ WT3="$R3/.wt/wtslug"
66
+ if [ -d "$WT3" ]; then pass; else fail "case4: configured worktree_root .wt not honored (want $WT3)"; fi
67
+
68
+ printf '\n%d passed, %d failed\n' "$PASSED" "$FAILED"
69
+ if [ "$FAILED" -eq 0 ]; then printf 'ALL PASS\n'; exit 0; fi
70
+ exit 1
@@ -69,6 +69,43 @@ fold_state() { # fold_state <milestone-id>
69
69
  [ -n "$_rs" ] && printf '%s' "$_rs" || printf 'unknown'
70
70
  }
71
71
 
72
+ # ── milestone name lookup (issue #34 — bare ids are useless with 40+ milestones in flight).
73
+ # Reads milestone.name (nested under the top-level `milestone:` key) from the worktree's own
74
+ # state file — a naive `grep -m1 '^name:'` returns empty since the key is indented under
75
+ # `milestone:`, not top-level. Same nested-key awk pattern as yget() in
76
+ # forge-board-render.sh (parent/child scan), specialized to milestone/name. Degrades to empty
77
+ # on any miss (absent file, absent key) — callers degrade to the bare id, never crash. ────────
78
+ milestone_name() { # milestone_name <milestone-id> <worktree-path>
79
+ _mid="${1:-}"; _wt="${2:-}"
80
+ [ -n "$_mid" ] && [ -n "$_wt" ] || { printf ''; return 0; }
81
+ _id="${_mid#m-}"
82
+ _f="$_wt/.forge/state/milestone-$_id.yml"
83
+ [ -f "$_f" ] || { printf ''; return 0; }
84
+ awk '
85
+ /^milestone:/ { inp=1; next }
86
+ inp && /^[A-Za-z]/ { inp=0 }
87
+ inp && /^[[:space:]]+name:/ {
88
+ sub(/^[[:space:]]*[^:]+:[[:space:]]*/, "")
89
+ sub(/[[:space:]]*#.*/, "")
90
+ gsub(/^["'"'"']|["'"'"']$/, "")
91
+ print; exit
92
+ }
93
+ ' "$_f"
94
+ }
95
+
96
+ # ── "m-<id> — <name>", truncated ~60 chars so one slice-list row stays one line; degrades to
97
+ # the bare id (never a blank "m- — " prefix) when the name can't be resolved. ────────────────
98
+ named_milestone() { # named_milestone <milestone-id> <worktree-path>
99
+ _mid="${1:-}"; _wt="${2:-}"
100
+ [ -n "$_mid" ] || { printf ''; return 0; }
101
+ _nm="$(milestone_name "$_mid" "$_wt")"
102
+ if [ -n "$_nm" ]; then
103
+ printf '%s' "$_mid — $_nm" | cut -c1-60
104
+ else
105
+ printf '%s' "$_mid"
106
+ fi
107
+ }
108
+
72
109
  # ── milestone id derivation for a matched branch (issue #25, bare-branch loosening). A
73
110
  # forge/m-* branch keeps its original textual derivation unchanged (strip forge/, strip the
74
111
  # 8-hex session suffix). A bare branch (m-<id>-slug | m<digits>-slug) derives the id from the
@@ -143,7 +180,8 @@ _slices="$(list_slices)"
143
180
  if [ -n "$_slices" ]; then
144
181
  printf '%s\n' "$_slices" | while IFS="$TAB" read -r _m _br _pt; do
145
182
  if [ "$DRY_RUN" -eq 1 ]; then _rs='(dry-run)'; else _rs="$(fold_state "$_m")"; fi
146
- printf ' %-10s %-26s release_state=%-9s %s\n' "$_m" "$_br" "$_rs" "$_pt"
183
+ _disp="$(named_milestone "$_m" "$_pt")"
184
+ printf ' %-40s %-26s release_state=%-9s %s\n' "$_disp" "$_br" "$_rs" "$_pt"
147
185
  done
148
186
  else
149
187
  printf ' (none on this machine)\n'
@@ -165,7 +203,13 @@ elif [ -f "$BOARD" ]; then
165
203
  _id="$(printf '%s' "$_ln" | sed -n 's/.*"forge_id":[[:space:]]*"\([^"]*\)".*/\1/p')"
166
204
  _st="$(printf '%s' "$_ln" | sed -n 's/.*"status":[[:space:]]*"\([^"]*\)".*/\1/p')"
167
205
  _rs="$(printf '%s' "$_ln" | sed -n 's/.*"release_state":[[:space:]]*"\([^"]*\)".*/\1/p')"
168
- printf ' %s status=%s release_state=%s\n' "${_id:-?}" "${_st:-?}" "${_rs:-?}"
206
+ _nm="$(printf '%s' "$_ln" | sed -n 's/.*"name":[[:space:]]*"\([^"]*\)".*/\1/p')"
207
+ if [ -n "$_nm" ]; then
208
+ _disp="$(printf '%s' "${_id:-?} — $_nm" | cut -c1-60)"
209
+ else
210
+ _disp="${_id:-?}"
211
+ fi
212
+ printf ' %s status=%s release_state=%s\n' "$_disp" "${_st:-?}" "${_rs:-?}"
169
213
  done
170
214
  else
171
215
  printf ' (all board units complete — nothing in flight)\n'