forge-orkes 0.14.0 → 0.17.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/package.json +1 -1
- package/template/.claude/agents/planner.md +4 -0
- package/template/.claude/skills/architecting/SKILL.md +11 -0
- package/template/.claude/skills/debugging/SKILL.md +14 -0
- package/template/.claude/skills/executing/SKILL.md +50 -2
- package/template/.claude/skills/forge/SKILL.md +2 -0
- package/template/.claude/skills/initializing/SKILL.md +23 -3
- package/template/.claude/skills/planning/SKILL.md +127 -19
- package/template/.claude/skills/reviewing/SKILL.md +14 -2
- package/template/.forge/templates/contract.md +27 -0
- package/template/.forge/templates/contracts-index.yml +35 -0
- package/template/.forge/templates/plan.md +23 -11
- package/template/.forge/templates/project.yml +6 -0
- package/template/.forge/templates/roadmap.yml +19 -7
- package/template/CLAUDE.md +3 -0
- package/template/.claude/hooks/forge-claim-check-doctor.sh +0 -37
- package/template/.claude/hooks/forge-claim-check.sh +0 -96
- package/template/.claude/skills/orchestrating/SKILL.md +0 -135
- package/template/.claude/skills/orchestrating/bootstrap-checks.md +0 -48
package/package.json
CHANGED
|
@@ -51,6 +51,9 @@ requirements:
|
|
|
51
51
|
Mark unknowns `[NEEDS CLARIFICATION]` — never guess.
|
|
52
52
|
|
|
53
53
|
### 5. Decompose Tasks
|
|
54
|
+
|
|
55
|
+
**Cross-layer first:** if this phase introduces/changes an interface one layer produces and another consumes (litmus: would an isolated agent have to guess the other's shape?), pin the delta in `contract.md` and either tag tasks `layer:` (Tier 1, one plan) or split into producer plan-NNa (pins contract) + consumer plan-NNb (`depends_on` the *frozen contract*, builds in parallel) -- Tier 2. See planning skill Step 6.1.
|
|
56
|
+
|
|
54
57
|
```xml
|
|
55
58
|
<task type="auto|manual">
|
|
56
59
|
<name>{Verb} {thing} {detail}</name>
|
|
@@ -109,3 +112,4 @@ must_haves:
|
|
|
109
112
|
- **Horizontal slicing**: models->routes->UI (prefer vertical)
|
|
110
113
|
- **Gold-plating**: Beyond requirements
|
|
111
114
|
- **Guessing**: Fill unknowns instead of `[NEEDS CLARIFICATION]`
|
|
115
|
+
- **Guessing a cross-layer shape**: splitting layers without pinning `contract.md` first -- the consumer ends up guessing the producer's shape. Pin the contract, then split.
|
|
@@ -7,6 +7,17 @@ description: "Make architectural decisions: choose frameworks, design data model
|
|
|
7
7
|
|
|
8
8
|
Make architectural decisions. Document rationale. Consider alternatives.
|
|
9
9
|
|
|
10
|
+
## Vertical-Slice Bias
|
|
11
|
+
|
|
12
|
+
Architectural decisions should preserve the planning skill's slice-first decomposition. Favor designs that let the team ship thin end-to-end user journeys early:
|
|
13
|
+
|
|
14
|
+
- **Prefer feature-folder layouts** (`src/features/signup/{ui,api,data}.ts`) over strict layer-folder layouts (`src/models/`, `src/api/`, `src/components/`) when the project allows. Layer-folder layouts are not banned -- but they invite horizontal decomposition.
|
|
15
|
+
- **Avoid framework choices that force big-bang integration.** If picking framework A means UI cannot be wired until the whole data layer ships, that's a red flag -- document the trade-off in the ADR's Consequences section.
|
|
16
|
+
- **Contracts before completeness.** Define the minimal API contract a single slice needs. Resist designing the full API surface upfront -- successive slices extend it.
|
|
17
|
+
- **Data models grow per slice.** Start with the fields slice 1 needs. Add columns/entities as later slices require. Reject "design the whole schema first" unless a `slice_exception: data_migration` phase is planned.
|
|
18
|
+
|
|
19
|
+
When an architectural decision conflicts with vertical slicing (e.g., a framework that requires full backend before any UI is testable), surface the conflict explicitly in the ADR's **Trade-Offs** section.
|
|
20
|
+
|
|
10
21
|
## When to Use
|
|
11
22
|
|
|
12
23
|
- Choosing a framework, library, or major dependency
|
|
@@ -7,6 +7,20 @@ description: "Systematic debugging when tests fail, features break, errors are c
|
|
|
7
7
|
|
|
8
8
|
Every hypothesis tested, every dead end recorded.
|
|
9
9
|
|
|
10
|
+
## Entry Path: Merge Conflict [Experimental — M10]
|
|
11
|
+
|
|
12
|
+
Invoked by `orchestrating` skill when `forge_queue_commit` returns `status: conflict`.
|
|
13
|
+
|
|
14
|
+
**Payload:** `{ conflicted_files: [...], base_sha, messages: [...], branch }`
|
|
15
|
+
|
|
16
|
+
**Workflow:**
|
|
17
|
+
1. Inside agent's worktree: `git fetch origin main && git rebase ${base_sha}` (use payload ref).
|
|
18
|
+
2. For each `conflicted_files[]` entry: inspect both sides via `git status` + `git diff`. Resolve per task context (or prompt user when intent unclear).
|
|
19
|
+
3. After all resolved: `git add <files>` then `git rebase --continue`.
|
|
20
|
+
4. Re-invoke `Skill(orchestrating)` with `action: retry-teardown` → orchestrating re-calls `forge_queue_commit`.
|
|
21
|
+
|
|
22
|
+
**Abort path:** if user aborts or resolution stalls, leave worktree in conflicted state and append `{ kind: "merge_conflict", branch, files }` to `lifecycle.blockers[]` in `.forge/state/milestone-{id}.yml`. Single-agent debugging entry paths below are unaffected.
|
|
23
|
+
|
|
10
24
|
## Scientific Method
|
|
11
25
|
|
|
12
26
|
1. **Observe**: Exact behavior — error, repro steps, when it started
|
|
@@ -35,6 +35,16 @@ Execution-phase operational guidance below supplements the rules — it does not
|
|
|
35
35
|
### Scope Boundary
|
|
36
36
|
Only fix issues DIRECTLY caused by the current task. Pre-existing warnings, tech debt, unrelated bugs → log to `.forge/deferred-issues.md`.
|
|
37
37
|
|
|
38
|
+
### Slice Integrity (Execution-Side)
|
|
39
|
+
|
|
40
|
+
The planning skill enforces vertical slicing at plan-creation time. Executor responsibility: do not silently re-introduce horizontal decomposition.
|
|
41
|
+
|
|
42
|
+
- **Do not split a slice plan into "backend now, UI later"** under Rule 1/2/3. If the UI half of a slice is broken, fix it -- do not defer it. Deferring the UI breaks the slice's user-observable truth.
|
|
43
|
+
- **Do not collapse the slice into a stub** to pass verification. `key_links` must be real (component actually hits handler; handler actually persists). Stubbed links fail the verifying gate.
|
|
44
|
+
- If a slice genuinely cannot ship end-to-end (e.g., external API blocker), invoke **Rule 4 -- STOP, ask user.** Options: redefine the slice, declare a `slice_exception:` and continue, or defer the phase. Do not autonomously ship half a slice.
|
|
45
|
+
|
|
46
|
+
This rule does NOT override the 3-strike limit or scope boundary -- it sits alongside them.
|
|
47
|
+
|
|
38
48
|
## Native Task Tracking
|
|
39
49
|
|
|
40
50
|
Use `TaskCreate`/`TaskUpdate`/`TaskList` for in-session visibility. `.forge/state/milestone-{id}.yml` remains the cross-session source of truth.
|
|
@@ -95,6 +105,30 @@ feat(auth-01): implement JWT-based login
|
|
|
95
105
|
- Include integration test for login flow
|
|
96
106
|
```
|
|
97
107
|
|
|
108
|
+
## Multi-Agent Claim Convention [Experimental — M10]
|
|
109
|
+
|
|
110
|
+
**Trigger:** active milestone state has `lifecycle.worktree_mode: active` (set by `orchestrating` skill). If absent or any other value → skip this section entirely; single-agent behavior unchanged.
|
|
111
|
+
|
|
112
|
+
**Pre-edit:** before the first `Edit` / `Write` / `MultiEdit` / `NotebookEdit` in a task, call MCP tool:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
forge_claim_files {
|
|
116
|
+
session_id: lifecycle.session_id,
|
|
117
|
+
files: [<absolute paths from task <files> manifest>],
|
|
118
|
+
ttl_seconds: 1800,
|
|
119
|
+
reason: "executing m{M}-{N} task <name>"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Branches:**
|
|
124
|
+
- **Full claim granted** → proceed with edits.
|
|
125
|
+
- **Partial rejection** → surface `rejected[].file`, `rejected[].owner_session`, `rejected[].expires_at` to user. Three options: **abort** task, **skip** rejected files (continue with granted subset), **wait** then retry after `expires_at`.
|
|
126
|
+
- **`DB_UNAVAILABLE`** → log warning, proceed. PreToolUse claim-check hook is defense-in-depth — coordination degrades to best-effort, isolation (worktree) still holds.
|
|
127
|
+
|
|
128
|
+
**End of task:** call `forge_release_claims { session_id, files: [...] }` after final commit. Plan-complete bulk release handled by `orchestrating` teardown.
|
|
129
|
+
|
|
130
|
+
See ADR-003 and `.claude/skills/orchestrating/SKILL.md`.
|
|
131
|
+
|
|
98
132
|
## Verification Gate
|
|
99
133
|
|
|
100
134
|
After each task commit, run configured verification commands. Mechanical — not optional.
|
|
@@ -221,7 +255,21 @@ Log to `.forge/state/index.yml → desire_paths` (global, not per-milestone):
|
|
|
221
255
|
- **User corrections**: Repeated correction matching a prior one → `user_correction`, increment count
|
|
222
256
|
- **Agent struggles**: Multiple attempts or user guidance needed → `agent_struggle`
|
|
223
257
|
|
|
258
|
+
## Cross-Layer Seam Check
|
|
259
|
+
|
|
260
|
+
**Trigger:** the phase was split by planning Step 6.1 into a **Tier-2** producer plan-NNa + consumer plan-NNb (both carry a `contract:` frontmatter path pointing at the same `contract.md`). Single-plan / Tier-1 (`layer:` tag, no split, contract honored inline) → skip; no seam check needed.
|
|
261
|
+
|
|
262
|
+
After **both** layer plans are committed, the executing flow owns one final **seam-check task** — there is no standing agent for this:
|
|
263
|
+
|
|
264
|
+
1. **Read** the phase `contract.md` — `delta`, `producer_layer`, `consumer_layer`, `seam_check`, `status` (should already be `ratified` from the planning Tier-2 gate).
|
|
265
|
+
2. **Merge** the layer branches/worktrees. If `lifecycle.worktree_mode: active`, the `orchestrating` teardown merges them; otherwise merge the sibling plan branches into the phase working tree.
|
|
266
|
+
3. **Verify the seam** — run the assertion named in `contract.md` `seam_check` (a test, a type-check, or a structural grep) to prove the shape the producer emits matches what the consumer built against per `delta`.
|
|
267
|
+
4. **Match** → commit the merge: `feat({phase}): seam check {integration_point}`. Contract stays `status: ratified`.
|
|
268
|
+
**Mismatch** → the consumer guessed wrong against a frozen contract → **Rule 1** fix on the consumer side. If the *contract itself* is wrong (producer can't emit the agreed shape) → **Rule 4** STOP, re-ratify with the user before proceeding.
|
|
269
|
+
5. Leave the contract at `status: ratified` — the `reviewing` skill folds `delta` into the governing ADR (`status: absorbed`) at milestone landing. Do **not** absorb here.
|
|
270
|
+
|
|
224
271
|
## Phase Handoff
|
|
225
272
|
1. Confirm persistence — summary documented, commits made, state updated, desire paths logged
|
|
226
|
-
2.
|
|
227
|
-
3.
|
|
273
|
+
2. **Run the Cross-Layer Seam Check** (above) if this phase was a Tier-2 contract split
|
|
274
|
+
3. Set `current.status` to `verifying`
|
|
275
|
+
4. Recommend: *"Tasks committed, state updated. `/clear` then `/forge` to continue with verifying."*
|
|
@@ -188,6 +188,8 @@ Tier + state → invoke via `Skill` tool. All phases use `Skill()`.
|
|
|
188
188
|
|
|
189
189
|
**CRITICAL: NEVER `EnterPlanMode`.** "Planning" = `Skill(planning)`. Native plan mode writes wrong format, bypasses gates + state.
|
|
190
190
|
|
|
191
|
+
**Experimental:** if user invokes `orchestrating` skill (M10) and repo has it installed (`.claude/skills/orchestrating/` present + MCP server + claim-check hook), route through it **before** `executing` to bootstrap multi-agent worktree. Skill is opt-in per ADR-001; absent install → fall through to standard routing.
|
|
192
|
+
|
|
191
193
|
### Auto-Routing (Always Deterministic)
|
|
192
194
|
|
|
193
195
|
**No menus.** Applies on first run and resume. Deterministic. Brief → route. Choices only at `complete` or corrupted.
|
|
@@ -225,6 +225,19 @@ Glob: src/**/index.{ts,tsx,js} # barrel exports
|
|
|
225
225
|
Grep: src/ for "import.*from.*@/" # path aliases
|
|
226
226
|
```
|
|
227
227
|
|
|
228
|
+
### Step 3.5: Architectural Layers
|
|
229
|
+
|
|
230
|
+
Detect distinct layers that hand a typed interface across a boundary — feeds the cross-layer contract detection in planning Step 6.1. A layer = a directory whose code is *produced for* or *consumed by* another (engine↔ui, core↔plugins, api↔web, native↔bindings).
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
Bash: ls -d */ src/*/ 2>/dev/null # top-level + src subdirs as layer candidates
|
|
234
|
+
Grep: cross-boundary imports (e.g. ui importing engine types, generated bindings, ABI/descriptor/schema files)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
A single cohesive codebase with no internal producer→consumer boundary → **not** layered; leave `layers: []` (Step 6.1 no-ops). Only flag layers when one directory's output is another's typed input.
|
|
238
|
+
|
|
239
|
+
Present detected layers for confirmation: *"Detected layers: [{name → path}]. These hand interfaces across a boundary — confirm or correct."* Confirmed 2+ → written to `project.yml` `layers:` and seeded into `.forge/contracts/index.yml` at Finalize.
|
|
240
|
+
|
|
228
241
|
### Step 4: Present
|
|
229
242
|
|
|
230
243
|
*"Project: {name} — {description}
|
|
@@ -269,6 +282,12 @@ User describes project → `.forge/project.yml`: name, goal, stack, constraints,
|
|
|
269
282
|
|
|
270
283
|
Validate each term against `.forge/templates/interface-detection.md` type vocabulary. On unrecognized term, prompt: *"Did you mean [closest match]? Valid: browser | cli | api | desktop | native-apple | none."* Write validated answer as `interface: [...]` in project.yml.
|
|
271
284
|
|
|
285
|
+
### Step 1.6: Architectural Layers
|
|
286
|
+
|
|
287
|
+
*"Will this project have distinct layers that hand a typed interface across a boundary (e.g. engine ↔ ui, core ↔ plugins, api ↔ web)? List them as name → path, or 'no' for a single-layer project."*
|
|
288
|
+
|
|
289
|
+
2+ layers → write `layers:` to project.yml + seed `.forge/contracts/index.yml` at Finalize. Otherwise `layers: []` (planning Step 6.1 no-ops).
|
|
290
|
+
|
|
272
291
|
### Step 2: Design System
|
|
273
292
|
|
|
274
293
|
*"UI library?"*
|
|
@@ -314,10 +333,11 @@ User selects per stack.
|
|
|
314
333
|
|
|
315
334
|
## Finalize
|
|
316
335
|
|
|
317
|
-
1. Write `.forge/project.yml` (all info + `verification`)
|
|
336
|
+
1. Write `.forge/project.yml` (all info + `verification` + `layers`)
|
|
318
337
|
2. Write `.forge/constitution.md`
|
|
319
338
|
3. Write `.forge/design-system.md` (if configured)
|
|
320
|
-
4.
|
|
339
|
+
4. Write `.forge/contracts/index.yml` (only if `layers:` has 2+ entries) — copy `.forge/templates/contracts-index.yml`, fill `layers:` from the confirmed list, leave `integration_points:` empty (first cross-layer phase populates them via planning Step 6.1)
|
|
340
|
+
5. Init state:
|
|
321
341
|
- `.forge/state/index.yml`:
|
|
322
342
|
```yaml
|
|
323
343
|
milestones:
|
|
@@ -339,7 +359,7 @@ User selects per stack.
|
|
|
339
359
|
task: null
|
|
340
360
|
status: not_started
|
|
341
361
|
```
|
|
342
|
-
|
|
362
|
+
6. Templates as needed
|
|
343
363
|
|
|
344
364
|
*"Initialized. Ready?"*
|
|
345
365
|
|
|
@@ -7,6 +7,26 @@ description: "Break work into executable tasks with verification gates. Enforces
|
|
|
7
7
|
|
|
8
8
|
> **Do NOT use `EnterPlanMode`.** Output -> `.forge/phases/`.
|
|
9
9
|
|
|
10
|
+
## Core Principle: Vertical Slicing
|
|
11
|
+
|
|
12
|
+
**Every phase and every plan MUST deliver a thin vertical slice -- a user-observable behavior reachable end-to-end (UI -> API -> data, or CLI -> core -> output).** Never decompose by horizontal layer (all models, then all APIs, then all UI). Horizontal slicing defers user-testable behavior until the last phase and amplifies integration risk.
|
|
13
|
+
|
|
14
|
+
Why:
|
|
15
|
+
- Each slice is testable, demoable, shippable on its own
|
|
16
|
+
- Bugs surface at the seam (where layers meet) on day one, not week three
|
|
17
|
+
- User can redirect direction after slice 1 instead of after the whole stack lands
|
|
18
|
+
|
|
19
|
+
Apply at three levels:
|
|
20
|
+
- **Roadmap (Step 5)**: phases are slices, not layers
|
|
21
|
+
- **Decompose (Step 6)**: plans are slices, not layers
|
|
22
|
+
- **Verify (Step 8)**: Slice Integrity gate -- hard fail on layer-only plans
|
|
23
|
+
|
|
24
|
+
Exceptions (must be explicitly justified in plan frontmatter `slice_exception:`):
|
|
25
|
+
- Foundational infra phase that no slice can reach yet (build setup, framework bootstrap)
|
|
26
|
+
- Shared library / cross-cutting refactor with no user-facing surface
|
|
27
|
+
|
|
28
|
+
If you find yourself writing a plan that only touches `src/models/`, `src/db/`, `src/schemas/`, `src/api/` (without UI/CLI counterpart), or `src/components/` (without data path) -- STOP. Merge with the slice that reaches the user, or claim an exception.
|
|
29
|
+
|
|
10
30
|
## Step 1: Resolution Gate
|
|
11
31
|
|
|
12
32
|
Read `.forge/context.md` **Needs Resolution**. If unchecked `- [ ]` items:
|
|
@@ -77,11 +97,14 @@ Never write to top-level `.forge/requirements.yml` -- that path is deprecated.
|
|
|
77
97
|
### Case A: `roadmap.yml` missing (Full only)
|
|
78
98
|
|
|
79
99
|
Create from `.forge/templates/roadmap.yml`:
|
|
80
|
-
1. Group by
|
|
81
|
-
2. Inter-
|
|
82
|
-
3.
|
|
100
|
+
1. **Group by vertical slice, NOT by layer.** Each phase = a thin end-to-end user journey. Wrong: `m1-models`, `m2-apis`, `m3-ui`. Right: `m1-user-can-sign-up`, `m2-user-can-post`, `m3-user-can-comment`.
|
|
101
|
+
2. Inter-slice dependencies (slice B builds on artifact from slice A)
|
|
102
|
+
3. Each phase has a one-sentence `goal:` written as user-observable outcome ("User can X"), never as "Build Y"
|
|
83
103
|
4. Every FR -> one phase, no orphans
|
|
84
|
-
5. Waves: independent=1, dependent=2+
|
|
104
|
+
5. Waves: independent slices=1, dependent slices=2+
|
|
105
|
+
6. **Phase 1 must be demoable.** If phase 1 has no user-observable output, the roadmap is layered -- redesign.
|
|
106
|
+
|
|
107
|
+
Anti-pattern detection: scan proposed phase names. Reject if any phase name contains layer-only terms without a user verb: `models`, `schema`, `database`, `api-only`, `backend-only`, `ui-only`, `frontend-only`, `infrastructure` (unless tagged as exception phase).
|
|
85
108
|
|
|
86
109
|
### Case B: `roadmap.yml` exists, current milestone already in it
|
|
87
110
|
|
|
@@ -105,16 +128,62 @@ If a sibling milestone (e.g. m50) has state + requirements but is missing from `
|
|
|
105
128
|
|
|
106
129
|
## Step 6: Decompose Tasks
|
|
107
130
|
|
|
108
|
-
|
|
131
|
+
### Step 6.1: Cross-Layer Contract Detection
|
|
132
|
+
|
|
133
|
+
Before decomposing, classify whether this phase crosses a layer boundary with a *new or changing* contract. This decides single-plan vs a contract-pinned layer split. Read the project's layers from `project.yml` `layers:` (or `.forge/contracts/index.yml`; fallback: top-level source dirs).
|
|
134
|
+
|
|
135
|
+
**Trigger -- all three hold:**
|
|
136
|
+
1. Work touches >= 2 declared layers (e.g. engine / blocks / ui).
|
|
137
|
+
2. A struct / signature / ABI field / descriptor is *produced* by one layer and *consumed* by another.
|
|
138
|
+
3. That interface is *new or changing* in this phase.
|
|
139
|
+
|
|
140
|
+
**Litmus (decisive):** "Would an agent building one layer in isolation have to GUESS the shape the other layer owns?" No (already specified in a durable contract) -> not cross-layer here.
|
|
141
|
+
|
|
142
|
+
**Two contract tiers:**
|
|
143
|
+
- **Durable** = the standing layer API. Lives in ADRs (`.forge/decisions/`) + constitution, indexed in `.forge/contracts/index.yml` (integration-point -> governing ADR). Stable + unchanged -> agents read the ADR; no per-phase artifact.
|
|
144
|
+
- **Per-phase delta** = the specific new/changed shape THIS phase introduces. Pinned in `.forge/phases/m{M}-{N}-{name}/contract.md` (from `.forge/templates/contract.md`); references its governing ADR; folded back into that ADR on landing.
|
|
145
|
+
|
|
146
|
+
**Classify:**
|
|
147
|
+
|
|
148
|
+
| Tier | Condition | Response |
|
|
149
|
+
|------|-----------|----------|
|
|
150
|
+
| 0 | Trigger fails | Normal decomposition (6.2). Nothing added. |
|
|
151
|
+
| 1 | Cross-layer delta, small / tightly sequential | Write `contract.md`; tag tasks `layer:`; ONE plan. No interruption. |
|
|
152
|
+
| 2 | Cross-layer delta, cleanly separable, worth parallel sessions | Pin `contract.md`; split into plan-NNa (producer layer, pins contract) + plan-NNb (consumer layer, `depends_on` the contract). Ratify gate. |
|
|
153
|
+
|
|
154
|
+
**Liberal detect, conservative interrupt:** classify every phase. Unsure between 1 and 2 -> default **Tier 1** (write the doc, no interruption). Escalate to 2 only when confident the parallel split pays off.
|
|
155
|
+
|
|
156
|
+
**Tier-2 ratify gate** (the ONLY interruption; frame as contract-correctness, not "parallelize y/n"):
|
|
157
|
+
> *"This phase changes the {integration point} contract ({governing ADR}). Delta: [summary]. plan-NNa ({producer}) pins it; plan-NNb ({consumer}) builds against it in parallel. Is this contract shape correct?"*
|
|
158
|
+
|
|
159
|
+
Block the split until confirmed. Override ("keep it one plan") -> log to `state/index.yml` `desire_paths` (recurring overrides tune the threshold), fall back to Tier 1.
|
|
160
|
+
|
|
161
|
+
**Integration (Tier 2):** layer plans build isolated (per-layer worktrees). The phase's final task is a **seam check** owned by the executing flow (NOT a standing agent): merge the layer branches, verify the shape the producer emits matches what the consumer built against, per `contract.md`.
|
|
162
|
+
|
|
163
|
+
### Step 6.2: Task Decomposition
|
|
164
|
+
|
|
165
|
+
Per phase (or feature, Standard tier). **Each plan = one vertical slice** -- except a sanctioned Tier-2 contract split (6.1), which divides one slice across producer/consumer layer plans reconciled at the seam check.
|
|
166
|
+
|
|
167
|
+
#### Slice-First Decomposition
|
|
168
|
+
|
|
169
|
+
Before writing any plan:
|
|
170
|
+
1. List the user-observable behaviors this phase must deliver (from `requirements/m{N}.yml`)
|
|
171
|
+
2. For each behavior, identify the full path: UI/CLI surface -> handler -> business logic -> persistence (only the parts that behavior needs)
|
|
172
|
+
3. **One plan = one behavior end-to-end.** Plan touches every layer that behavior needs, not all of one layer.
|
|
173
|
+
4. If a plan can only ship part of the path (e.g., UI without backend wired), it is NOT a slice -- restructure.
|
|
174
|
+
|
|
175
|
+
Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.md`.
|
|
176
|
+
|
|
177
|
+
#### File Layout
|
|
109
178
|
|
|
110
179
|
1. `.forge/templates/plan.md` -> `.forge/phases/m{M}-{N}-{name}/plan-{NN}.md`
|
|
111
180
|
- `{M}`=milestone, `{N}`=phase#, `{name}`=kebab, `{NN}`=seq
|
|
112
181
|
- Ex: `.forge/phases/m3-2-providers/plan-01.md`
|
|
113
|
-
2. Frontmatter: phase, plan#, wave, deps
|
|
182
|
+
2. Frontmatter: phase, plan#, wave, deps, `slice_exception:` (optional, see Core Principle)
|
|
114
183
|
3. must_haves:
|
|
115
|
-
- **Truths:** User-observable outcomes (3-7)
|
|
116
|
-
- **Artifacts:** Must exist, substantive not stubs
|
|
117
|
-
- **Key Links:** Connections between artifacts
|
|
184
|
+
- **Truths:** User-observable outcomes (3-7). MUST be phrased as something the user can see, click, or receive -- not "model X exists" or "table Y created".
|
|
185
|
+
- **Artifacts:** Must exist, substantive not stubs. Slice plans typically span 2-4 layers (e.g., component + handler + repo).
|
|
186
|
+
- **Key Links:** Connections between artifacts -- these prove the slice is wired, not stubbed.
|
|
118
187
|
4. XML tasks (2-3/plan, 15-60 min):
|
|
119
188
|
|
|
120
189
|
```xml
|
|
@@ -144,20 +213,45 @@ Per phase (or feature, Standard tier):
|
|
|
144
213
|
| `checkpoint:decision` | Pause for user choice between options |
|
|
145
214
|
| `checkpoint:human-action` | Pause for manual action (email verification, 2FA) |
|
|
146
215
|
|
|
147
|
-
### Vertical Slices (
|
|
216
|
+
### Vertical Slices (Required)
|
|
217
|
+
|
|
148
218
|
```
|
|
149
|
-
Plan 01: User
|
|
150
|
-
Plan 02:
|
|
219
|
+
Plan 01: User can sign up (UI form + /api/signup + users table write) → Wave 1
|
|
220
|
+
Plan 02: User can log in (UI form + /api/login + session issue) → Wave 2 (uses table from 01)
|
|
221
|
+
Plan 03: User can post note (UI editor + /api/notes + notes table) → Wave 2 (uses auth from 02)
|
|
151
222
|
```
|
|
152
|
-
Independent plans run parallel.
|
|
153
223
|
|
|
154
|
-
|
|
224
|
+
Each plan is independently demoable. Bugs at layer seams surface in plan 01.
|
|
225
|
+
|
|
226
|
+
### Horizontal Layers (Anti-Pattern -- BLOCKED)
|
|
227
|
+
|
|
155
228
|
```
|
|
156
|
-
Plan 01: All models
|
|
157
|
-
Plan 02: All APIs
|
|
158
|
-
Plan 03: All UI
|
|
229
|
+
Plan 01: All models → Wave 1
|
|
230
|
+
Plan 02: All APIs → Wave 2 (depends on 01)
|
|
231
|
+
Plan 03: All UI → Wave 3 (depends on 02)
|
|
159
232
|
```
|
|
160
|
-
|
|
233
|
+
|
|
234
|
+
This decomposition is **rejected by default** at Step 8 (Slice Integrity gate). To proceed, declare `slice_exception:` in plan frontmatter with one of:
|
|
235
|
+
- `infra_bootstrap` -- foundational setup with no user-reachable surface
|
|
236
|
+
- `shared_library` -- cross-cutting utility used by future slices
|
|
237
|
+
- `data_migration` -- one-shot schema/data change with no behavior added
|
|
238
|
+
|
|
239
|
+
Anything else: restructure into slices.
|
|
240
|
+
|
|
241
|
+
### Anti-Pattern Auto-Detector
|
|
242
|
+
|
|
243
|
+
A plan fails Slice Integrity if ALL of:
|
|
244
|
+
- `must_haves.truths` contain only artifacts/internals (e.g., "Schema migration applied", "Model class created") with no user-visible verb (see, click, receive, fail-with-error)
|
|
245
|
+
- `must_haves.artifacts` paths all live under a single layer prefix (only `src/models/`, only `src/api/`, only `src/components/`)
|
|
246
|
+
- No `slice_exception:` declared
|
|
247
|
+
|
|
248
|
+
Detection runs in Step 8.
|
|
249
|
+
|
|
250
|
+
### Contract-Driven Layer Split (Tier 2 exception)
|
|
251
|
+
|
|
252
|
+
When Step 6.1 flags a **Tier-2** cross-layer contract, splitting by layer is correct -- NOT the horizontal anti-pattern above. The difference:
|
|
253
|
+
- *Horizontal anti-pattern:* split by layer with no contract; each layer waits on the previous. Serializes.
|
|
254
|
+
- *Contract-driven split:* plan-NNb `depends_on` the **frozen contract** (pinned by NNa up front), not NNa's implementation -> both layers build in parallel (separate sessions/worktrees), reconciled at the seam check.
|
|
161
255
|
|
|
162
256
|
## Step 7: Test Specs (Optional)
|
|
163
257
|
|
|
@@ -249,7 +343,7 @@ Decision captured once, pre-code. Does not block planning.
|
|
|
249
343
|
|
|
250
344
|
## Step 8: Verify Plans
|
|
251
345
|
|
|
252
|
-
|
|
346
|
+
9 dimensions:
|
|
253
347
|
1. **Requirement Coverage** -- every req has task(s)
|
|
254
348
|
2. **Task Completeness** -- files + action + verify + done
|
|
255
349
|
3. **Deps** -- valid DAG, no cycles
|
|
@@ -258,6 +352,20 @@ Decision captured once, pre-code. Does not block planning.
|
|
|
258
352
|
6. **Verification** -- must_haves trace to goal
|
|
259
353
|
7. **Context** -- honors locked, excludes deferred
|
|
260
354
|
8. **Spec Validity** -- valid syntax, correct paths
|
|
355
|
+
9. **Slice Integrity (HARD GATE)** -- every plan delivers a vertical slice OR declares `slice_exception:`
|
|
356
|
+
|
|
357
|
+
### Slice Integrity Check
|
|
358
|
+
|
|
359
|
+
For each plan, FAIL if all of these hold and no `slice_exception:` is declared:
|
|
360
|
+
- `must_haves.truths` lack a user-observable verb (`see`, `click`, `submit`, `receive`, `view`, `download`, `error`, `redirected`, `login`, `signup`, etc.) -- internal-only truths like "Schema applied", "Model registered", "Index built" do not satisfy
|
|
361
|
+
- `must_haves.artifacts` paths cluster in a single layer (all under `models/`, all under `api/`, all under `components/`, etc.)
|
|
362
|
+
- No file path crosses a layer boundary (e.g., a component plus its handler, a CLI plus its core)
|
|
363
|
+
|
|
364
|
+
**Exempt:** a Tier-2 contract-split plan (Step 6.1) carries both `layer:` and `contract:` frontmatter. It is a sanctioned single-layer plan reconciled at the seam check -- treat as auto-`slice_exception` (the *phase*, not the plan, owns the vertical slice). It passes without declaring `slice_exception:`.
|
|
365
|
+
|
|
366
|
+
Roadmap-level check: FAIL if phase 1 has no user-observable goal.
|
|
367
|
+
|
|
368
|
+
On fail: restructure plan(s) into slices, or declare `slice_exception:` with one of `infra_bootstrap | shared_library | data_migration` and a one-line rationale. Re-verify.
|
|
261
369
|
|
|
262
370
|
Issues -> fix, re-verify. Max 3 cycles.
|
|
263
371
|
|
|
@@ -371,9 +371,21 @@ If the milestone being completed has `milestone.origin: {R-id}` set (promoted fr
|
|
|
371
371
|
3. Update item: `status: resolved`, set `completed: "<ISO 8601 date>"`. Keep `promoted_to: {milestone-id}` intact for audit trail.
|
|
372
372
|
4. Log in summary: *"Backlog item {R-id} → resolved (promoted milestone {id} complete)."*
|
|
373
373
|
|
|
374
|
+
## Contract Landing (cross-layer phases)
|
|
375
|
+
|
|
376
|
+
If the milestone's phases produced `contract.md` files (planning Step 6.1 Tier 1/2), close their lifecycle before completing the milestone. The durable contract is the ADR; the per-phase `contract.md` is a working delta that must be folded back in.
|
|
377
|
+
|
|
378
|
+
1. Glob `.forge/phases/m{id}-*/contract.md`.
|
|
379
|
+
2. For each contract not yet `absorbed` (Tier-2 lands at `ratified` after the executing seam check; Tier-1 lands at `proposed` — both fold the same way now that the phase is verified):
|
|
380
|
+
- Fold `delta` into its `governing_adr` — amend the ADR in `.forge/decisions/`, or supersede it (`Status: Superseded by ADR-{NNN}`) if the shape changed materially.
|
|
381
|
+
- If a **new** integration point was introduced, add it to `.forge/contracts/index.yml` `integration_points:` (id, produces, consumes, governing_adr, summary).
|
|
382
|
+
- Set the contract's `status: absorbed`. The ADR is now authoritative; `contract.md` becomes history.
|
|
383
|
+
3. Any contract with no `governing_adr` set (nothing to fold into) → warn: *"Contract {integration_point} has no governing ADR — file one in `.forge/decisions/` before close, or the durable contract drifts from code."* Advisory — does not block completion.
|
|
384
|
+
|
|
374
385
|
## Phase Handoff
|
|
375
386
|
|
|
376
387
|
1. Confirm report + backlog
|
|
377
388
|
2. **Run promoted-milestone completion hook** (above) if `milestone.origin` set
|
|
378
|
-
3.
|
|
379
|
-
4.
|
|
389
|
+
3. **Run Contract Landing** (above) for any cross-layer phases — fold ratified contracts into their ADRs
|
|
390
|
+
4. Set `current.status: complete` and `current.completed_at: "<ISO 8601 timestamp>"`
|
|
391
|
+
5. *"Milestone [{name}] complete. Report: `.forge/audits/milestone-{id}-health-report.md`. {N} backlog items. `/forge` or backlog."*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Phase Contract: {integration point}
|
|
2
|
+
|
|
3
|
+
Copy to `.forge/phases/m{M}-{N}-{name}/contract.md` when planning **Step 6.1** detects a cross-layer delta (Tier 1 or 2). Pins the NEW or CHANGED interface shape the producing and consuming layers must agree on for this phase, so an agent building one layer in isolation does not have to guess the other's shape.
|
|
4
|
+
|
|
5
|
+
Lifecycle: pinned by the producer plan (NNa) BEFORE the consumer plan (NNb) builds against it -> ratified at the Tier-2 gate -> folded into the governing ADR when the phase lands (`status: absorbed`). The durable contract is the ADR; this file is the working delta on top of it.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
contract:
|
|
11
|
+
integration_point: "" # e.g. "engine -> ui (block descriptor)"
|
|
12
|
+
governing_adr: "" # durable contract this delta extends, e.g. "ADR-026" (see .forge/contracts/index.yml)
|
|
13
|
+
producer_layer: "" # owns + pins the shape (plan-NNa), e.g. "engine"
|
|
14
|
+
consumer_layer: "" # builds against it (plan-NNb), e.g. "ui"
|
|
15
|
+
status: proposed # proposed | ratified | absorbed
|
|
16
|
+
delta: |
|
|
17
|
+
# The exact new/changed shape the consumer must NOT have to guess:
|
|
18
|
+
# struct fields, ABI fields + sizeof/layout, function signatures,
|
|
19
|
+
# enum values, units, ownership. ASCII only.
|
|
20
|
+
seam_check: "" # how integration verifies producer output == consumer expectation,
|
|
21
|
+
# e.g. "ui block_shape_projection_test asserts port count from descriptor"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Notes
|
|
25
|
+
- One contract block per cross-layer delta. A phase changing two integration points pins two blocks (or two files).
|
|
26
|
+
- The consumer plan's `depends_on` points at THIS contract reaching `status: ratified`, NOT the producer plan's completion -- that is what lets the layers build in parallel.
|
|
27
|
+
- On landing: amend or supersede `governing_adr` to absorb `delta`, set `status: absorbed`. The ADR is then authoritative; this file becomes history.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Durable cross-layer contract index
|
|
2
|
+
#
|
|
3
|
+
# Maps each standing integration point between layers to the ADR(s) that
|
|
4
|
+
# govern it. Planning Step 6.1 reads this to (a) know an integration point
|
|
5
|
+
# already has a durable contract, (b) decide whether the current phase
|
|
6
|
+
# CHANGES it, (c) point producer/consumer agents at the authoritative shape.
|
|
7
|
+
#
|
|
8
|
+
# Durable contracts live in the ADRs themselves (.forge/decisions/). This
|
|
9
|
+
# index is only the lookup. A phase that changes a contract pins a per-phase
|
|
10
|
+
# delta in its contract.md, then folds it back into the governing ADR.
|
|
11
|
+
#
|
|
12
|
+
# Copy to .forge/contracts/index.yml and fill in for your project.
|
|
13
|
+
|
|
14
|
+
# The project's architectural layers -- used by the cross-layer trigger
|
|
15
|
+
# (Step 6.1 condition 1: "work touches >= 2 declared layers").
|
|
16
|
+
layers:
|
|
17
|
+
- name: "" # e.g. "engine"
|
|
18
|
+
path: "" # e.g. "engine/"
|
|
19
|
+
# - name: "blocks"
|
|
20
|
+
# path: "blocks/"
|
|
21
|
+
# - name: "ui"
|
|
22
|
+
# path: "ui/"
|
|
23
|
+
|
|
24
|
+
# Standing integration points and their governing ADR(s).
|
|
25
|
+
integration_points:
|
|
26
|
+
- id: "" # e.g. "engine<->block"
|
|
27
|
+
produces: "" # producer layer, e.g. "engine"
|
|
28
|
+
consumes: "" # consumer layer, e.g. "blocks"
|
|
29
|
+
governing_adr: [] # e.g. ["ADR-001"]
|
|
30
|
+
summary: "" # one line: what the contract covers
|
|
31
|
+
# - id: "engine->ui"
|
|
32
|
+
# produces: "engine"
|
|
33
|
+
# consumes: "ui"
|
|
34
|
+
# governing_adr: ["ADR-026"]
|
|
35
|
+
# summary: "Block descriptor: UI projects ports/params/layout from the engine descriptor"
|
|
@@ -16,20 +16,32 @@ type: execute # execute | tdd
|
|
|
16
16
|
wave: 1 # Execution wave (1 = no dependencies)
|
|
17
17
|
depends_on: [] # Plan IDs that must complete first
|
|
18
18
|
autonomous: true # false if contains checkpoints
|
|
19
|
+
layer: "" # name from project.yml layers[], or "" -- set by planning Step 6.1 cross-layer split (Tier 1/2)
|
|
20
|
+
contract: "" # path to this phase's contract.md (cross-layer delta this plan pins/consumes), if any
|
|
21
|
+
|
|
22
|
+
# Vertical slice declaration. A plan delivers a thin end-to-end user behavior
|
|
23
|
+
# (UI -> API -> data, or CLI -> core -> output). Plans that touch only ONE layer
|
|
24
|
+
# are rejected by the planning Slice Integrity gate unless slice_exception is set.
|
|
25
|
+
slice_exception: null # null | infra_bootstrap | shared_library | data_migration
|
|
26
|
+
slice_exception_rationale: "" # Required if slice_exception != null. One line.
|
|
19
27
|
|
|
20
28
|
must_haves:
|
|
21
|
-
truths: #
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
29
|
+
truths: # USER-observable when plan is done. Must contain a user verb
|
|
30
|
+
# (see, click, submit, receive, view, download, error, redirect).
|
|
31
|
+
# Internal-only truths like "Schema applied" do NOT satisfy.
|
|
32
|
+
- "" # e.g., "User submits signup form and lands on dashboard"
|
|
33
|
+
- "" # e.g., "Invalid email shows inline 'must be a valid email' error"
|
|
34
|
+
artifacts: # Files that must exist and be substantive (not stubs).
|
|
35
|
+
# Slice plans typically span 2-4 layers — paths should cross
|
|
36
|
+
# boundaries (component + handler + repo), not cluster in one dir.
|
|
37
|
+
- path: "" # e.g., "src/components/SignupForm.tsx"
|
|
38
|
+
provides: "" # e.g., "Signup form with email + password fields"
|
|
27
39
|
min_lines: 30 # Stub detection threshold
|
|
28
|
-
key_links: #
|
|
29
|
-
- from: "" # e.g., "src/components/
|
|
30
|
-
to: "" # e.g., "/api/
|
|
31
|
-
via: "" # e.g., "fetch
|
|
32
|
-
pattern: "" # e.g., "fetch.*api/
|
|
40
|
+
key_links: # Connections between layers — these prove the slice is wired
|
|
41
|
+
- from: "" # e.g., "src/components/SignupForm.tsx"
|
|
42
|
+
to: "" # e.g., "/api/signup"
|
|
43
|
+
via: "" # e.g., "fetch on submit"
|
|
44
|
+
pattern: "" # e.g., "fetch.*api/signup"
|
|
33
45
|
```
|
|
34
46
|
|
|
35
47
|
## Tasks
|
|
@@ -14,6 +14,12 @@ tech_stack:
|
|
|
14
14
|
testing: "" # e.g., Vitest, Jest, Pytest
|
|
15
15
|
other: [] # Additional key dependencies
|
|
16
16
|
|
|
17
|
+
layers: [] # Architectural layers — enables cross-layer contract detection (planning Step 6.1).
|
|
18
|
+
# Populated during init (brownfield: producer/consumer source dirs; greenfield: asked).
|
|
19
|
+
# Each entry: {name, path}. Empty/absent = single-layer project, detection no-ops.
|
|
20
|
+
# e.g. [{name: engine, path: src/engine/}, {name: ui, path: src/ui/}]
|
|
21
|
+
# Durable integration points + governing ADRs live in .forge/contracts/index.yml.
|
|
22
|
+
|
|
17
23
|
interface: [none] # Surfaces this project exposes: browser | cli | api | desktop | native-apple | none
|
|
18
24
|
# Array — e.g. [browser, api] for full-stack projects
|
|
19
25
|
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# Forge Roadmap Template
|
|
2
2
|
# Copy to .forge/roadmap.yml and customize.
|
|
3
|
-
#
|
|
3
|
+
#
|
|
4
|
+
# Phases are VERTICAL SLICES — thin end-to-end user journeys shippable on their own.
|
|
5
|
+
# NOT horizontal layers (do NOT carve into "all models" / "all APIs" / "all UI" phases).
|
|
6
|
+
#
|
|
7
|
+
# Right: m1-user-can-sign-up, m2-user-can-post, m3-user-can-comment
|
|
8
|
+
# Wrong: m1-models, m2-apis, m3-ui
|
|
9
|
+
#
|
|
10
|
+
# Each phase's `goal:` must read as a user-observable outcome ("User can X"),
|
|
11
|
+
# never as "Build Y". Phase 1 must be demoable. The planning skill's
|
|
12
|
+
# Slice Integrity gate will reject layered roadmaps unless a `slice_exception:`
|
|
13
|
+
# is declared on the plan (infra_bootstrap | shared_library | data_migration).
|
|
4
14
|
|
|
5
15
|
roadmap:
|
|
6
16
|
# Milestones group phases into concurrent work streams.
|
|
@@ -16,13 +26,15 @@ roadmap:
|
|
|
16
26
|
# Example: m1-1-foundation/, m1-2-auth/, m2-1-dashboard/
|
|
17
27
|
phases:
|
|
18
28
|
- id: 1
|
|
19
|
-
name: "" # e.g., "
|
|
20
|
-
goal: "" #
|
|
21
|
-
|
|
29
|
+
name: "" # Vertical slice name, e.g., "User can sign up"
|
|
30
|
+
goal: "" # User-observable outcome. "User can X". Never "Build Y".
|
|
31
|
+
slice_exception: null # null | infra_bootstrap | shared_library | data_migration
|
|
32
|
+
# Only set if this phase legitimately has no user-facing surface.
|
|
33
|
+
requirements: [] # List of FR-IDs this phase delivers (end-to-end)
|
|
22
34
|
dependencies: [] # Phase IDs that must complete first
|
|
23
|
-
success_criteria: #
|
|
24
|
-
- "" # e.g., "User
|
|
25
|
-
- "" # e.g., "
|
|
35
|
+
success_criteria: # User-observable truths when phase is done
|
|
36
|
+
- "" # e.g., "User submits signup form and lands on dashboard"
|
|
37
|
+
- "" # e.g., "Invalid email shows inline error"
|
|
26
38
|
estimated_hours: null
|
|
27
39
|
status: pending # pending | researching | planning | executing | verifying | deferred | complete
|
|
28
40
|
|
package/template/CLAUDE.md
CHANGED
|
@@ -60,6 +60,9 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
|
60
60
|
| Systematic debugging | `debugging` | When stuck |
|
|
61
61
|
| Upgrade Forge files | `upgrading` | On-demand |
|
|
62
62
|
| Cross-session memory | `beads-integration` | When Beads installed |
|
|
63
|
+
| Multi-agent orchestration (experimental) | `orchestrating` | Full (opt-in) |
|
|
64
|
+
|
|
65
|
+
> Experimental skills require opt-in install — see `packages/create-forge/experimental/m10/README.md`.
|
|
63
66
|
|
|
64
67
|
## Context Engineering
|
|
65
68
|
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Forge claim-check hook prerequisites probe. Informational only — always exit 0.
|
|
3
|
-
# Run manually or via install procedure to confirm bash/jq/sqlite3/timeout availability.
|
|
4
|
-
|
|
5
|
-
set -uo pipefail
|
|
6
|
-
|
|
7
|
-
check() {
|
|
8
|
-
local name=$1 cmd=$2 version_flag=${3:---version}
|
|
9
|
-
if command -v "$cmd" >/dev/null 2>&1; then
|
|
10
|
-
local ver
|
|
11
|
-
ver=$("$cmd" "$version_flag" 2>&1 | head -1)
|
|
12
|
-
printf ' ✓ %-10s %s\n' "$name" "$ver"
|
|
13
|
-
else
|
|
14
|
-
printf ' ✗ %-10s MISSING\n' "$name"
|
|
15
|
-
fi
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
echo "Forge claim-check hook — prerequisites"
|
|
19
|
-
echo
|
|
20
|
-
|
|
21
|
-
check "bash" "bash" "--version"
|
|
22
|
-
check "jq" "jq" "--version"
|
|
23
|
-
check "sqlite3" "sqlite3" "--version"
|
|
24
|
-
|
|
25
|
-
if command -v timeout >/dev/null 2>&1; then
|
|
26
|
-
printf ' ✓ %-10s %s\n' "timeout" "$(timeout --version 2>&1 | head -1)"
|
|
27
|
-
elif command -v gtimeout >/dev/null 2>&1; then
|
|
28
|
-
printf ' ✓ %-10s (gtimeout) %s\n' "timeout" "$(gtimeout --version 2>&1 | head -1)"
|
|
29
|
-
else
|
|
30
|
-
printf ' ✗ %-10s MISSING (optional — install coreutils for bounded queries)\n' "timeout"
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
echo
|
|
34
|
-
echo "DB lookup path: ${FORGE_CLAIMS_DB:-${CLAUDE_PROJECT_DIR:-$PWD}/.forge/.mcp-server/claims.db}"
|
|
35
|
-
echo "Session id: ${CLAUDE_SESSION_ID:-<unset — hook will fail-open>}"
|
|
36
|
-
|
|
37
|
-
exit 0
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Forge PreToolUse hook — cross-session file-claim collision detector.
|
|
3
|
-
#
|
|
4
|
-
# Contract:
|
|
5
|
-
# stdin: Claude Code PreToolUse JSON payload
|
|
6
|
-
# exit 0 = allow (no claim, own claim, no DB, no session context, unknown schema)
|
|
7
|
-
# exit 2 = deny (cross-session claim active, or any internal error — fail-closed)
|
|
8
|
-
#
|
|
9
|
-
# Defense-in-depth: ERR trap converts ANY unexpected failure into an exit-2 deny.
|
|
10
|
-
# Never exit 1 — Claude Code treats exit 1 as soft warning; we want hard block on error.
|
|
11
|
-
|
|
12
|
-
set -euo pipefail
|
|
13
|
-
|
|
14
|
-
deny() {
|
|
15
|
-
echo "[forge-hook] $*" >&2
|
|
16
|
-
exit 2
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
trap 'deny "internal error at line $LINENO — denying for safety (fail-closed)"' ERR
|
|
20
|
-
|
|
21
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
22
|
-
DB="${FORGE_CLAIMS_DB:-$PROJECT_DIR/.forge/.mcp-server/claims.db}"
|
|
23
|
-
SESSION_ID="${CLAUDE_SESSION_ID:-}"
|
|
24
|
-
|
|
25
|
-
# Detect timeout wrapper (macOS lacks GNU `timeout` unless coreutils installed → `gtimeout`).
|
|
26
|
-
if command -v timeout >/dev/null 2>&1; then
|
|
27
|
-
TIMEOUT_CMD=(timeout 4)
|
|
28
|
-
elif command -v gtimeout >/dev/null 2>&1; then
|
|
29
|
-
TIMEOUT_CMD=(gtimeout 4)
|
|
30
|
-
else
|
|
31
|
-
TIMEOUT_CMD=() # no wrapper — sqlite call runs unbounded; busy_timeout in DB still applies
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
PAYLOAD=$(cat)
|
|
35
|
-
|
|
36
|
-
# Normalize across known path fields:
|
|
37
|
-
# Edit / Write → tool_input.file_path
|
|
38
|
-
# NotebookEdit → tool_input.notebook_path (validated in spike — see milestone-10-validation.md §2)
|
|
39
|
-
# MultiEdit → tool_input.file_path (single file, multiple edits; per docs)
|
|
40
|
-
# Future / unknown tools → tool_input.path (defensive)
|
|
41
|
-
# jq emits each matching path on its own line. Empty output = no path field present.
|
|
42
|
-
FILES=$(printf '%s' "$PAYLOAD" | jq -r '
|
|
43
|
-
[ .tool_input.file_path?
|
|
44
|
-
, .tool_input.notebook_path?
|
|
45
|
-
, .tool_input.path?
|
|
46
|
-
, ( .tool_input.edits? // [] | .[]?.file_path? )
|
|
47
|
-
] | map(select(. != null and . != "")) | unique | .[]
|
|
48
|
-
')
|
|
49
|
-
|
|
50
|
-
if [ -z "$FILES" ]; then
|
|
51
|
-
# No recognized path field — unknown schema. Allow (do not deny on schema drift).
|
|
52
|
-
exit 0
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# No DB = MCP server has never run in this repo (fresh-repo case). Fail-open only here.
|
|
56
|
-
if [ ! -f "$DB" ]; then
|
|
57
|
-
exit 0
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
if [ -z "$SESSION_ID" ]; then
|
|
61
|
-
# No session context — single-agent or hook invoked outside Claude Code. Allow.
|
|
62
|
-
echo "[forge-hook] CLAUDE_SESSION_ID unset — allowing (single-agent mode)" >&2
|
|
63
|
-
exit 0
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
# Resolve each path to absolute (matches what MCP server stores via path.resolve).
|
|
67
|
-
abspath() {
|
|
68
|
-
case "$1" in
|
|
69
|
-
/*) printf '%s' "$1" ;;
|
|
70
|
-
*) printf '%s/%s' "$PROJECT_DIR" "$1" ;;
|
|
71
|
-
esac
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
while IFS= read -r raw; do
|
|
75
|
-
[ -z "$raw" ] && continue
|
|
76
|
-
file=$(abspath "$raw")
|
|
77
|
-
|
|
78
|
-
# Parameterized query via .param — avoids quoting injection on path strings.
|
|
79
|
-
result=$("${TIMEOUT_CMD[@]+"${TIMEOUT_CMD[@]}"}" sqlite3 -batch "$DB" \
|
|
80
|
-
".param set :fp '$file'" \
|
|
81
|
-
"SELECT session_id || '|' || expires_at FROM claims WHERE file_path = :fp AND expires_at > strftime('%s','now') LIMIT 1;")
|
|
82
|
-
|
|
83
|
-
[ -z "$result" ] && continue
|
|
84
|
-
|
|
85
|
-
owner="${result%%|*}"
|
|
86
|
-
expires_at="${result##*|}"
|
|
87
|
-
|
|
88
|
-
if [ "$owner" = "$SESSION_ID" ]; then
|
|
89
|
-
continue # own claim
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
expires_human=$(date -r "$expires_at" "+%Y-%m-%d %H:%M:%S %Z" 2>/dev/null || echo "epoch:$expires_at")
|
|
93
|
-
deny "Edit denied: $file claimed by session $owner until $expires_human. Call forge_release_claims in that session, or wait."
|
|
94
|
-
done <<< "$FILES"
|
|
95
|
-
|
|
96
|
-
exit 0
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: orchestrating
|
|
3
|
-
description: "[Experimental — M10] Owns multi-agent session lifecycle. Bootstrap, worktree create, claim+merge coordination, teardown. Refuses worktree mode on incompatible repos and falls back to single-agent."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Orchestrating
|
|
7
|
-
|
|
8
|
-
Multi-agent session lifecycle. Worktree isolation + MCP-coordinated claims + merge queue. Experimental — opt-in per ADR-001. Refuses on incompatible repos, falls back to single-agent.
|
|
9
|
-
|
|
10
|
-
## When to use
|
|
11
|
-
|
|
12
|
-
- User explicitly invokes multi-agent mode (`/forge` argument selects multi-agent, or direct skill invocation).
|
|
13
|
-
- `executing` skill at Full tier with ≥2 concurrent-eligible phases routes through this skill.
|
|
14
|
-
|
|
15
|
-
Skip on Quick tier, single-phase work, or when bootstrap checks fail.
|
|
16
|
-
|
|
17
|
-
## Step 1: Bootstrap
|
|
18
|
-
|
|
19
|
-
Run all checks in `bootstrap-checks.md`:
|
|
20
|
-
|
|
21
|
-
1. Git version ≥ 2.48
|
|
22
|
-
2. LFS version ≥ 3.6 (skip if not installed)
|
|
23
|
-
3. No submodules
|
|
24
|
-
4. `core.hooksPath` empty or resolves inside worktree
|
|
25
|
-
5. `git hook run pre-commit` smoke test in fresh worktree
|
|
26
|
-
|
|
27
|
-
**Any check fails** → log reason, write `lifecycle.worktree_mode: refused` + `lifecycle.refused_reason` into active milestone state, emit fallback message (see `bootstrap-checks.md`), return to caller. Caller continues single-agent.
|
|
28
|
-
|
|
29
|
-
## Step 2: Session ID + worktree
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
session_id=$(uuidgen | cut -c1-8)
|
|
33
|
-
git worktree prune
|
|
34
|
-
git worktree add -b forge/${session_id} --lock --reason "forge session" ../forge-worktrees/${session_id} main
|
|
35
|
-
( cd ../forge-worktrees/${session_id} && git hook run pre-commit || true )
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Verify worktree dir exists, branch locked. On failure → cleanup partial state, refuse mode.
|
|
39
|
-
|
|
40
|
-
## Step 3: State update
|
|
41
|
-
|
|
42
|
-
Write into `.forge/state/milestone-{id}.yml`:
|
|
43
|
-
|
|
44
|
-
```yaml
|
|
45
|
-
lifecycle:
|
|
46
|
-
session_id: "{session_id}"
|
|
47
|
-
worktree_path: "../forge-worktrees/{session_id}"
|
|
48
|
-
worktree_branch: "forge/{session_id}"
|
|
49
|
-
worktree_mode: "active"
|
|
50
|
-
started_at: "{ISO8601}"
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Update `state/index.yml` milestone `last_updated`.
|
|
54
|
-
|
|
55
|
-
## Step 4: Hand back
|
|
56
|
-
|
|
57
|
-
Return control to caller (typically `executing`). All subsequent work runs inside `../forge-worktrees/{session_id}`. Caller honors claim convention.
|
|
58
|
-
|
|
59
|
-
### Claim convention (executing-skill contract)
|
|
60
|
-
|
|
61
|
-
Before any `Edit`, `Write`, `MultiEdit`, or `NotebookEdit` on a file outside `.forge/state/milestone-{own_id}.yml`:
|
|
62
|
-
|
|
63
|
-
1. Call `forge_claim_files` with `{ session_id, files: [...], ttl_seconds: 900 }`.
|
|
64
|
-
2. On `granted` → proceed with edit.
|
|
65
|
-
3. On `conflict: { holder_session, files: [...] }` → surface holder + files to user. Options:
|
|
66
|
-
- **wait** → poll `forge_claim_status` until released or TTL expiry.
|
|
67
|
-
- **skip** → drop the conflicted file from the task scope, continue with rest.
|
|
68
|
-
- **steal** → only if holder session is provably dead (PreToolUse hook validates). Otherwise refused.
|
|
69
|
-
4. After edit batch → claim auto-extends on continued use; explicit `forge_release_claims` on plan-complete.
|
|
70
|
-
|
|
71
|
-
PreToolUse hook (installed by plan-03) enforces this — uncaught violations block at hook level, not skill level.
|
|
72
|
-
|
|
73
|
-
## Step 5: Teardown
|
|
74
|
-
|
|
75
|
-
Triggered when caller signals work complete OR user requests teardown.
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
forge_queue_commit(branch=forge/{session_id}, base_sha={merge_base})
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Branch on response status:
|
|
82
|
-
|
|
83
|
-
- **`merged`** → `forge_release_claims(session_id)` → `git worktree remove --force ../forge-worktrees/{session_id}` → `git branch -d forge/{session_id}` → clear `lifecycle.*` (set `worktree_mode: complete`, retain `session_id` for audit).
|
|
84
|
-
- **`conflict`** → invoke `Skill(debugging)` with payload `{ conflicted_files, base_sha, messages, branch }`. Teardown blocks until debugging signals resolution (re-invoke teardown after fix).
|
|
85
|
-
- **`stale_base`** → caller rebases worktree branch onto `current_main_sha` from response, retries `forge_queue_commit`. Max 3 retries → escalate to conflict path.
|
|
86
|
-
|
|
87
|
-
## Step 6: Crash recovery (next session start)
|
|
88
|
-
|
|
89
|
-
If no clean teardown happened previously:
|
|
90
|
-
|
|
91
|
-
1. `git worktree prune` — drops stale admin dirs.
|
|
92
|
-
2. `git branch --list 'forge/*'` — for each branch with no live worktree, prompt user:
|
|
93
|
-
- **resume** → re-attach: `git worktree add ../forge-worktrees/{id} forge/{id}` and restore lifecycle state.
|
|
94
|
-
- **delete** → `git branch -D forge/{id}`.
|
|
95
|
-
3. MCP server startup handles pidfile takeover + claim TTL expiry independently (see ADR-003).
|
|
96
|
-
|
|
97
|
-
## Failure modes & operator notes
|
|
98
|
-
|
|
99
|
-
- **MCP server absent** — bootstrap check 5 (hook smoke) will not detect this. Skill detects on first `forge_claim_files` call: error `MCP_SERVER_UNAVAILABLE` → write `lifecycle.worktree_mode: degraded`, warn user, continue without claim coordination. Hard isolation (worktree) still active; coordination is downgraded to best-effort.
|
|
100
|
-
- **Disk full during worktree add** — `git worktree add` will error. Cleanup any partial `../forge-worktrees/{session_id}/` dir, refuse mode, fall back.
|
|
101
|
-
- **Concurrent orchestrating invocations** — second invocation reads first's `lifecycle.session_id` in state. If present and `worktree_mode: active` → refuse (one orchestration per milestone). Use a separate milestone for parallel orchestrated work.
|
|
102
|
-
- **Worktree path collision** — UUIDv4 short (8 chars) collision negligible at <100 concurrent sessions. If `../forge-worktrees/{id}/` already exists → regenerate session_id, retry up to 3 times.
|
|
103
|
-
|
|
104
|
-
## Example: clean session
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
user: /forge multi-agent
|
|
108
|
-
forge → orchestrating
|
|
109
|
-
orchestrating: bootstrap OK → session_id=a1b2c3d4 → worktree created → state written
|
|
110
|
-
orchestrating → executing (working dir: ../forge-worktrees/a1b2c3d4)
|
|
111
|
-
executing: claim files → edit → commit (× N tasks)
|
|
112
|
-
executing → verifying → reviewing (all inside worktree)
|
|
113
|
-
reviewing → orchestrating (teardown)
|
|
114
|
-
orchestrating: forge_queue_commit → merged → release claims → remove worktree → delete branch
|
|
115
|
-
done.
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Example: conflict path
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
orchestrating: forge_queue_commit → conflict { files: [src/auth.ts] }
|
|
122
|
-
orchestrating → debugging { conflicted_files, base_sha, branch }
|
|
123
|
-
debugging: user resolves → signal resolved
|
|
124
|
-
orchestrating: retry forge_queue_commit → merged → cleanup
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## References
|
|
128
|
-
|
|
129
|
-
- ADR-001 — experimental track / opt-in carve-out
|
|
130
|
-
- ADR-002 — worktrees as isolation substrate
|
|
131
|
-
- ADR-003 — MCP server + per-repo SQLite
|
|
132
|
-
- ADR-004 — merge queue (forge_queue_commit status semantics)
|
|
133
|
-
- ADR-005 — session lifecycle (this skill realizes it)
|
|
134
|
-
- `.forge/research/milestone-10.md` — spike findings
|
|
135
|
-
- `bootstrap-checks.md` — bootstrap check matrix + fallback
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Bootstrap Checks
|
|
2
|
-
|
|
3
|
-
Run before worktree creation. Any failure → refuse worktree mode, fall back to single-agent.
|
|
4
|
-
|
|
5
|
-
| Check | Command | Pass criterion | Fail action | Reason |
|
|
6
|
-
|-------|---------|----------------|-------------|--------|
|
|
7
|
-
| Git version | `git --version` | major.minor ≥ 2.48 | refuse worktree mode | Known worktree bugs < 2.48 (admin-dir leaks, prune races) |
|
|
8
|
-
| LFS version | `git lfs version` (skip if not installed) | ≥ 3.6 OR not installed | refuse worktree mode | Known worktree-locking bugs < 3.6 |
|
|
9
|
-
| Submodule scan | `git submodule status` | empty output | refuse worktree mode | Submodules officially "not recommended" in worktrees (git docs) |
|
|
10
|
-
| `core.hooksPath` | `git config core.hooksPath` | empty OR path resolves inside worktree | refuse worktree mode | Husky/lefthook silent-skip risk when path points outside worktree |
|
|
11
|
-
| Hook smoke test | `git hook run pre-commit` in fresh worktree | exit 0 OR matches main-repo exit code | refuse worktree mode | Confirms hook plumbing actually fires inside worktree |
|
|
12
|
-
|
|
13
|
-
## Fallback message
|
|
14
|
-
|
|
15
|
-
On any check failure, emit verbatim to user:
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
M10 worktree mode unavailable: <reason>. Continuing in single-agent mode. See ADR-005 for compatibility matrix.
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Replace `<reason>` with the failing check name + observed value (e.g., "Git version 2.45.1 < required 2.48").
|
|
22
|
-
|
|
23
|
-
## State write on refusal
|
|
24
|
-
|
|
25
|
-
```yaml
|
|
26
|
-
lifecycle:
|
|
27
|
-
worktree_mode: refused
|
|
28
|
-
refused_reason: "<check name>: <observed>"
|
|
29
|
-
refused_at: "{ISO8601}"
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Check execution order
|
|
33
|
-
|
|
34
|
-
Run checks 1→5 in order. First failure halts the sequence — no point running hook smoke if Git itself is too old. Record the failing check name + observed value as `refused_reason` so the user knows exactly what to fix.
|
|
35
|
-
|
|
36
|
-
## Remediation hints
|
|
37
|
-
|
|
38
|
-
| Failing check | User remediation |
|
|
39
|
-
|---------------|------------------|
|
|
40
|
-
| Git version | Upgrade Git to ≥ 2.48 (Homebrew: `brew upgrade git`; apt: backports or PPA) |
|
|
41
|
-
| LFS version | Upgrade Git LFS to ≥ 3.6 OR uninstall if not actually used |
|
|
42
|
-
| Submodule scan | Convert submodules to subtrees or vendored copies; M10 cannot coexist with submodules per upstream git guidance |
|
|
43
|
-
| `core.hooksPath` | Either unset (`git config --unset core.hooksPath`) or move hook dir inside repo tree so it resolves under each worktree |
|
|
44
|
-
| Hook smoke test | Inspect failing hook output; common cause is hook script assuming `$PWD` is main repo root — fix to use `git rev-parse --show-toplevel` |
|
|
45
|
-
|
|
46
|
-
## Re-running checks
|
|
47
|
-
|
|
48
|
-
Bootstrap is idempotent and cheap (~200ms total). Skill re-runs on every session start; users do not invoke directly. If a check transiently fails (e.g., LFS not yet installed during initial setup), simply restart the orchestration entry.
|