forge-orkes 0.41.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-forge.js +245 -43
- package/package.json +5 -1
- package/template/.claude/agents/doc-reviewer.md +115 -0
- package/template/.claude/agents/performance-reviewer.md +138 -0
- package/template/.claude/agents/security-reviewer.md +163 -0
- package/template/.claude/agents/tester.md +3 -5
- package/template/.claude/hooks/README.md +39 -0
- package/template/.claude/hooks/block-dangerous-commands.sh +158 -0
- package/template/.claude/hooks/forge-active-skill-guard.sh +44 -0
- package/template/.claude/hooks/format-on-save.sh +95 -0
- package/template/.claude/hooks/protect-files.sh +101 -0
- package/template/.claude/hooks/scan-secrets.sh +87 -0
- package/template/.claude/hooks/tests/README.md +76 -0
- package/template/.claude/hooks/tests/cases/block-dangerous-commands.cases.json +376 -0
- package/template/.claude/hooks/tests/cases/protect-files.cases.json +222 -0
- package/template/.claude/hooks/tests/cases/scan-secrets.cases.json +218 -0
- package/template/.claude/hooks/tests/cases/warn-large-files.cases.json +146 -0
- package/template/.claude/hooks/tests/run.sh +118 -0
- package/template/.claude/hooks/warn-large-files.sh +71 -0
- package/template/.claude/rules/README.md +63 -0
- package/template/.claude/rules/agent-discipline.md +14 -0
- package/template/.claude/settings.json +69 -1
- package/template/.claude/skills/architecting/SKILL.md +2 -0
- package/template/.claude/skills/chief-of-staff/SKILL.md +37 -26
- package/template/.claude/skills/executing/SKILL.md +16 -0
- package/template/.claude/skills/forge/SKILL.md +15 -27
- package/template/.claude/skills/forge/desire-paths-review.md +37 -0
- package/template/.claude/skills/initializing/SKILL.md +4 -0
- package/template/.claude/skills/planning/SKILL.md +17 -3
- package/template/.claude/skills/reviewing/SKILL.md +2 -0
- package/template/.claude/skills/testing/SKILL.md +3 -3
- package/template/.claude/skills/verifying/SKILL.md +20 -9
- package/template/.forge/FORGE.md +36 -8
- package/template/.forge/migrations/0.20.0-nested-phase-layout.md +10 -2
- package/template/.forge/migrations/0.42.0-id-reservation.md +48 -0
- package/template/.forge/migrations/0.43.0-safety-policy.md +60 -0
- package/template/.forge/migrations/0.44.0-desire-paths.md +57 -0
- package/template/.forge/reservations.yml +31 -0
|
@@ -23,6 +23,39 @@
|
|
|
23
23
|
"max_retries": 2
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
+
"permissions": {
|
|
27
|
+
"deny": [
|
|
28
|
+
"Read(./.env)",
|
|
29
|
+
"Read(./.env.*)",
|
|
30
|
+
"Read(**/.env)",
|
|
31
|
+
"Read(**/.env.*)",
|
|
32
|
+
"Read(**/secrets/**)",
|
|
33
|
+
"Read(**/.secrets/**)",
|
|
34
|
+
"Read(**/*.pem)",
|
|
35
|
+
"Read(**/*.key)",
|
|
36
|
+
"Read(**/*.p12)",
|
|
37
|
+
"Read(**/*.pfx)",
|
|
38
|
+
"Read(**/id_rsa)",
|
|
39
|
+
"Read(**/id_ed25519)",
|
|
40
|
+
"Read(**/credentials.json)",
|
|
41
|
+
"Read(**/auth.json)",
|
|
42
|
+
"Read(**/.composer-auth.json)",
|
|
43
|
+
"Read(**/.npmrc)",
|
|
44
|
+
"Read(**/.pypirc)",
|
|
45
|
+
"Edit(**/.env)",
|
|
46
|
+
"Edit(**/.env.*)",
|
|
47
|
+
"Edit(**/secrets/**)",
|
|
48
|
+
"Edit(**/.secrets/**)",
|
|
49
|
+
"Edit(**/*.pem)",
|
|
50
|
+
"Edit(**/*.key)",
|
|
51
|
+
"Write(**/.env)",
|
|
52
|
+
"Write(**/.env.*)",
|
|
53
|
+
"Write(**/secrets/**)",
|
|
54
|
+
"Write(**/.secrets/**)",
|
|
55
|
+
"Write(**/*.pem)",
|
|
56
|
+
"Write(**/*.key)"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
26
59
|
"hooks": {
|
|
27
60
|
"PostToolUse": [
|
|
28
61
|
{
|
|
@@ -43,6 +76,15 @@
|
|
|
43
76
|
"command": "mkdir -p .forge && echo \"$TOOL_INPUT\" > .forge/.active-skill"
|
|
44
77
|
}
|
|
45
78
|
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
82
|
+
"hooks": [
|
|
83
|
+
{
|
|
84
|
+
"type": "command",
|
|
85
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-save.sh\""
|
|
86
|
+
}
|
|
87
|
+
]
|
|
46
88
|
}
|
|
47
89
|
],
|
|
48
90
|
"PreToolUse": [
|
|
@@ -51,7 +93,24 @@
|
|
|
51
93
|
"hooks": [
|
|
52
94
|
{
|
|
53
95
|
"type": "command",
|
|
54
|
-
"command": "
|
|
96
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/forge-active-skill-guard.sh\""
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
102
|
+
"hooks": [
|
|
103
|
+
{
|
|
104
|
+
"type": "command",
|
|
105
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.sh\""
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"type": "command",
|
|
109
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/warn-large-files.sh\""
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "command",
|
|
113
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scan-secrets.sh\""
|
|
55
114
|
}
|
|
56
115
|
]
|
|
57
116
|
},
|
|
@@ -63,6 +122,15 @@
|
|
|
63
122
|
"prompt": "Verify the git commit message follows Forge format: {type}({scope}): {description}. Types allowed: feat, fix, test, refactor, chore, docs. If the message doesn't match, suggest the correct format."
|
|
64
123
|
}
|
|
65
124
|
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"matcher": "Bash",
|
|
128
|
+
"hooks": [
|
|
129
|
+
{
|
|
130
|
+
"type": "command",
|
|
131
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous-commands.sh\""
|
|
132
|
+
}
|
|
133
|
+
]
|
|
66
134
|
}
|
|
67
135
|
]
|
|
68
136
|
}
|
|
@@ -28,6 +28,8 @@ When an architectural decision conflicts with vertical slicing (e.g., a framewor
|
|
|
28
28
|
|
|
29
29
|
## Architecture Decision Record (ADR)
|
|
30
30
|
|
|
31
|
+
**Reserve the ADR number first (cross-worktree safety — blocking gate).** Before authoring a new ADR, allocate its number via the **ID Reservation Protocol** (FORGE.md). **Do not create the ADR file until its reservation is appended and committed** — the reserve step is mandatory, not advisory. The next number is `max(highest `kind: adr` in `.forge/reservations.yml`, highest `ADR-NNN` in `.forge/decisions/`) + 1; append one `{kind: adr, id, milestone, reserved_at, summary}` entry to `.forge/reservations.yml`, **commit + push it**, *then* write the ADR file. Bare "scan `decisions/` for the highest + increment" only sees committed state, so two worktrees architecting in parallel both pick the same `ADR-NNN` and collide silently until merge (a multi-file renumber). See FORGE.md → **ID Reservation Protocol** for the full rule. No `reservations.yml` yet ⇒ scan as before and the first reservation creates it (lazy migration).
|
|
32
|
+
|
|
31
33
|
Record every significant decision in `.forge/decisions/`:
|
|
32
34
|
|
|
33
35
|
```markdown
|
|
@@ -78,29 +78,25 @@ every field has one source and coverage is computed into the artifact. Run the
|
|
|
78
78
|
rollup at the **start of Show and Sync**, and after any Start/Resume/Pause/Close
|
|
79
79
|
edit to a stream file.
|
|
80
80
|
|
|
81
|
-
Procedure
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
auto-create).
|
|
96
|
-
4. Derive `merge_queue:` — one entry per stream with `merge.readiness: ready`.
|
|
97
|
-
5. Write `active.yml` (`version`, `updated_at`, derived `streams:` sorted by id,
|
|
98
|
-
derived `merge_queue:`). No hand-edited keys.
|
|
81
|
+
Procedure: run the **Stream Rollup** exactly as defined in FORGE.md → Stream
|
|
82
|
+
Rollup — the deterministic join + write (glob stream files, derive each row's
|
|
83
|
+
`coordination`/`merge_readiness`/`blocked_by`/`next_action`, read `phase` from the
|
|
84
|
+
linked milestone, emit `stream: implicit` coverage rows, derive `merge_queue` from
|
|
85
|
+
`merge.readiness: ready`, write `active.yml`) is **single-sourced there**. Never
|
|
86
|
+
hand-edit `active.yml`. Beyond that deterministic write, the Chief adds two
|
|
87
|
+
**interactive** steps the boot-time rollup omits:
|
|
88
|
+
|
|
89
|
+
- **Deferred/complete linked milestone** → when a stream's `stream.milestone`
|
|
90
|
+
points at a `deferred`/`complete` milestone, surface it: the stream should
|
|
91
|
+
likely pause — offer it.
|
|
92
|
+
- **Implicit row needs coordination** → when an active milestone has no stream
|
|
93
|
+
file (the rollup emits a `stream: implicit` row for coverage), offer to
|
|
94
|
+
materialize a stream file for it. **Never auto-create.**
|
|
99
95
|
|
|
100
96
|
Note: invoked directly, `/chief-of-staff` does not run `forge`'s milestone Rollup,
|
|
101
|
-
so `index.yml` may be slightly stale; read it as-is
|
|
102
|
-
`/forge` pass if it looks off. The Chief writes `active.yml`
|
|
103
|
-
never writes `index.yml` or any milestone file.
|
|
97
|
+
so `index.yml` may be slightly stale; read it as-is when deriving implicit-row
|
|
98
|
+
coverage and suggest a `/forge` pass if it looks off. The Chief writes `active.yml`
|
|
99
|
+
(derived class); it never writes `index.yml` or any milestone file.
|
|
104
100
|
|
|
105
101
|
## Start Stream Flow
|
|
106
102
|
|
|
@@ -168,11 +164,12 @@ existing M10 state.
|
|
|
168
164
|
pre-convention `git worktree add`; `git worktree move` it and update the
|
|
169
165
|
record."* Adopt the recorded path as-is regardless (advisory, never a block;
|
|
170
166
|
honors `orchestration.worktree_root`).
|
|
167
|
+
- **Submodules:** if the worktree was created manually (`git worktree add`, not via `orchestrating`) and the repo has submodules, they are empty until inited. Don't build from it blind — `executing`'s Workspace Prerequisites step inits them before the first build; surface this when handing the stream off so it isn't discovered as a mid-build failure.
|
|
171
168
|
- `runtime.worker_sessions` with the M10 session / claim ids
|
|
172
169
|
- `ownership.owned_surfaces`, `shared_surfaces`, and `read_only_surfaces`
|
|
173
170
|
as empty lists if unknown
|
|
174
171
|
- `blockers` from milestone blockers or degraded/refused notes
|
|
175
|
-
- `
|
|
172
|
+
- `stream.milestone: "m-{id}"` — the rollup join key (phase derives from the linked milestone's state); this is the same link step 8 confirms
|
|
176
173
|
7. Populate the stream brief with:
|
|
177
174
|
- current milestone/phase/status
|
|
178
175
|
- worktree path and branch
|
|
@@ -242,11 +239,24 @@ Compare all `active`, `delegated`, and `ready` streams:
|
|
|
242
239
|
reservation, contract, or explicit merge order
|
|
243
240
|
- overlapping `shared_surfaces` requires a named contract owner
|
|
244
241
|
- overlapping `read_only_surfaces` is safe
|
|
242
|
+
- two streams holding the same `runtime.lease.resource` is an exclusive-resource conflict — serialize (see **Exclusive Resource Leases**)
|
|
245
243
|
- missing ownership is a conflict until clarified
|
|
246
244
|
|
|
247
245
|
Record conflicts in both the registry and the affected stream files. Do not let
|
|
248
246
|
workers resolve cross-stream conflicts directly.
|
|
249
247
|
|
|
248
|
+
## Exclusive Resource Leases
|
|
249
|
+
|
|
250
|
+
Surface conflict detection covers **files**. It does not cover a **single exclusive non-file resource** that two concurrent streams both need — a physical test device, a hardware port, a singleton bundle ID, a shared local DB/emulator. Two streams each `xcrun devicectl device install` to the same device, or deploy the same bundle ID, and each **clobbers the other's build**: a stream's device-verify gate opens the app and finds a *sibling's* build, the block under test absent (forge#15, canvaz m-AW01). Files don't collide here, so the surface check passes — and the clobber only shows up at device UAT.
|
|
251
|
+
|
|
252
|
+
Two complementary remedies; prefer (1), fall back to (2):
|
|
253
|
+
|
|
254
|
+
1. **Isolate the resource per stream (no lease needed).** When the resource *can* be made per-stream, do that instead of serializing. The canonical case: derive a **per-worktree bundle-ID suffix** from the worktree anchor (`co.example.app` → `co.example.app.m-AW01`) so every stream's build coexists on one device. This is **project-level config** (the iOS/CMake build reads the anchor from `git branch --show-current`), not Forge core — record it as a constitution/`project.yml` convention for the stack. Isolated → the streams never contend; skip the lease.
|
|
255
|
+
|
|
256
|
+
2. **Lease the resource (when it genuinely can't be split).** Declare exclusive resources in `project.yml` (`orchestration.exclusive_resources: [ios_device, …]`). A stream that needs one records a lease in **its own** stream file — `runtime.lease: {resource, acquired_at, operation}` (single-owner-mutable, the stream's driver writes it; no cross-stream contention). The owning skill (e.g. `verifying`'s device-verify gate) **acquires before** the deploy-and-verify, **releases after** (clears the field). The Chief detects double-claims the same way it detects surface overlap: scan stream files for two holding the same `runtime.lease.resource` → **serialize** them (one waits, or skips its device gate until the resource frees). Until a stream releases, siblings needing that resource queue.
|
|
257
|
+
|
|
258
|
+
Add the lease scan to **Detect Conflicts** (above) and the **Merge Cadence Check**: a held lease is a soft block on the holder's siblings' device gates, never on merge.
|
|
259
|
+
|
|
250
260
|
## Merge Safe Flow
|
|
251
261
|
|
|
252
262
|
Use "merge safe" to identify streams that can land:
|
|
@@ -282,10 +292,11 @@ merge/rebase), and **cross-stream merge order**. A checkpoint stream that is
|
|
|
282
292
|
cleanly auto-publishing needs no nudge here.
|
|
283
293
|
|
|
284
294
|
1. **Ready-to-merge surfacing.** From `active.yml`, collect streams with
|
|
285
|
-
`
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
295
|
+
`coordination: ready` (the derived-row coordination field) plus any
|
|
296
|
+
`merge_queue` entries (derived from `merge.readiness: ready`). For each, run the
|
|
297
|
+
Merge Safe Flow conditions. Unblocked + no conflicts → list under **Ready to
|
|
298
|
+
merge** with a one-line *"ready — integrate now"* and, if known, how long it has
|
|
299
|
+
been ready (from the stream's `stream.updated_at` / brief date). Add/refresh its `merge_queue`
|
|
289
300
|
entry (`status: waiting`). Blocked → list the named reason instead.
|
|
290
301
|
2. **Idle-ready nudge.** A stream that has sat `ready` across **multiple Show/Sync
|
|
291
302
|
passes** or whose branch is mergeable but untouched is the cadence smell.
|
|
@@ -10,9 +10,25 @@ description: "Build to plan with atomic commits, deviation rules, and context en
|
|
|
10
10
|
- [ ] Context.md locked decisions noted
|
|
11
11
|
- [ ] Constitution.md gates satisfied
|
|
12
12
|
- [ ] Milestone state updated to `status: executing`
|
|
13
|
+
- [ ] Workspace prerequisites met — submodules populated (see below)
|
|
13
14
|
- [ ] Baseline snapshot captured (see below)
|
|
14
15
|
- [ ] Plan anchors re-derived against HEAD (see below)
|
|
15
16
|
|
|
17
|
+
## Workspace Prerequisites
|
|
18
|
+
|
|
19
|
+
Run **first, before the baseline snapshot** — a missing prerequisite makes the baseline's build/test commands fail for reasons that have nothing to do with the plan.
|
|
20
|
+
|
|
21
|
+
**Submodules.** A freshly-created worktree carries submodule gitlinks but leaves the directories **empty** until `git submodule update --init` runs — the first build then fails partway through with a confusing error (a missing third-party target, an empty `external/` dir), not an obvious "submodules uninitialised". `orchestrating` inits submodules when *it* creates the worktree, but a worktree from any other origin (a manual `git worktree add`, a `chief-of-staff` stream, a fresh clone) does not. So assert it here, regardless of how the workspace was created:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# No-op when the repo has no submodules; a fast checkout when objects are already in the shared common git dir.
|
|
25
|
+
if [ -f .gitmodules ] && git submodule status | grep -q '^-'; then
|
|
26
|
+
git submodule update --init --recursive
|
|
27
|
+
fi
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Surface the init if it ran (*"populated N uninitialised submodule(s) before baseline"*). A `+`/`U` line (divergent/conflicted submodule) is **not** an init case — STOP under Rule 4; it signals real submodule movement. (Desire-path: canvaz m-TIDES01 — empty `external/JUCE` failed the first CMake configure mid-task.)
|
|
31
|
+
|
|
16
32
|
## Baseline Snapshot
|
|
17
33
|
|
|
18
34
|
Run **before the first task begins**. Makes failure causality mechanical — no self-assessment.
|
|
@@ -45,7 +45,14 @@ git worktree list --porcelain 2>/dev/null \
|
|
|
45
45
|
- `behind > 0` and the worktree has **no divergent local commits** (`git rev-list --count main..HEAD` is 0) → **fast-forward automatically**: `git merge --ff-only main`. A fast-forward is conflict-free by definition, so do it without asking; report *"pulled {N} commit(s) from main (checkpoint integration)."* Mark the sha seen.
|
|
46
46
|
- `behind > 0` **and** the worktree has diverged → **surface, do not auto-rebase**: *"main advanced on {surfaces} since this worktree branched — rebase onto main? (`git rebase main`)"* — rebase can conflict; the operator decides.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
**Integration flag check (consumer — main checkout).** When `in_main_checkout: true`, the same flag tells the operator's **canonical** checkout that a checkpoint pushed work to `origin/main` it hasn't pulled — the other half of the producer's strand-safe publish (FORGE.md → Stream Integration Checkpoints; forge#13). Without this, local main sits behind origin after every checkpoint and the next local commit silently diverges it. Same event-driven gate:
|
|
49
|
+
|
|
50
|
+
- Read `.git/forge/integration-pending`. Absent, or its sha already seen by this checkout → **skip** (no fetch). No flag → no work.
|
|
51
|
+
- Flag set with an unseen sha → `git fetch origin main` (if a remote), then `behind=$(git rev-list --count main..origin/main 2>/dev/null)` (local `main` vs the freshly-fetched `origin/main`). `behind` is 0 → mark seen, skip.
|
|
52
|
+
- `behind > 0` and local main has **not** diverged (`git rev-list --count origin/main..main` is 0) → **fast-forward automatically** (the producer's strand guard guarantees this is a true ff): `git merge --ff-only origin/main` on `main`. Report *"fast-forwarded local main +{N} from origin (checkpoint integration)."* Mark seen.
|
|
53
|
+
- `behind > 0` **and** local main has diverged → **surface, never auto-merge**: *"local main and origin/main have diverged ({A} local / {B} remote commits) — this shouldn't happen if checkpoints used the strand guard; reconcile with `git fetch` then `git merge origin/main` (inspect first). Do NOT `git pull --ff-only` (it fails on divergence)."* The operator integrates.
|
|
54
|
+
|
|
55
|
+
Cross-machine note: the flag file is machine-local, so a worktree (or the primary checkout) on another laptop relies on the opt-in `forge.worktree_rebase_check` / a periodic fetch instead — eventual, not instant.
|
|
49
56
|
|
|
50
57
|
**Worktree path convention check (advisory — main or worktree).** A recorded worktree path is trusted *verbatim* everywhere (FORGE.md → "Recorded path is authoritative"), so a path created **outside** the convention — a manual `git worktree add ../forge-worktrees/m115`, or pre-convention state — is honored unchallenged by the 1.1a gate and by `chief-of-staff` adoption. This check surfaces that drift. It never blocks and never moves anything.
|
|
51
58
|
|
|
@@ -54,7 +61,11 @@ Cross-machine note: the flag file is machine-local, so a worktree on another lap
|
|
|
54
61
|
- Mismatch → one advisory line: *"recorded worktree path `{path}` for m{id} is not under the convention root `{root}` — likely a manual `git worktree add` or pre-convention state. Fix: `git worktree move {path} {root}/{anchor}` then update the recorded path. (Advisory; honors `orchestration.worktree_root`.)"*
|
|
55
62
|
- Parent dir under the root, or no recorded path → silent. An explicit `orchestration.worktree_root` is honored by construction (the override **is** the root compared against), so a deliberately relocated root never false-warns.
|
|
56
63
|
|
|
57
|
-
**
|
|
64
|
+
**ID reservation preflight (advisory — main or worktree).** The cross-worktree ID Reservation Protocol (FORGE.md, ADR-016) only protects numbers if `.forge/reservations.yml` actually carries them. Two cheap checks, both advisory — never block:
|
|
65
|
+
- **Absent registry, but IDs already exist.** `.forge/reservations.yml` missing *and* the tree already holds allocated IDs (`requirements/*.yml` has `FR-`/`NFR-`/`DEF-`, or `.forge/decisions/` has `ADR-NNN`) → one-time nudge: *"No `reservations.yml` yet, but this project already allocates IDs. Concurrent worktrees will collide on the next number. Create it by reserving your next ID via the protocol (`planning`/`architecting` do this), or backfill existing IDs now."* (No IDs anywhere → silent; the first reservation creates the file lazily.)
|
|
66
|
+
- **Backfill scan (un-reserved landed IDs).** When `reservations.yml` exists, diff the IDs actually landed in the tree (`FR-`/`NFR-`/`DEF-` across `requirements/*.yml`; `ADR-NNN` across `.forge/decisions/`) against the ids recorded in `reservations.yml`. Any landed ID with **no** reservation entry → list them once: *"{N} ID(s) are in the tree but not in `reservations.yml` ({ids}) — landed before the protocol or via an un-reserved path. They are still respected (next = max(reserved, in-tree)+1), but appending backfill entries keeps the registry the single audit trail."* Advisory: surface, don't auto-write — backfilling is the operator's call.
|
|
67
|
+
|
|
68
|
+
**Stream Rollup (active.yml is derived).** If `.forge/streams/` has any stream files, regenerate `.forge/streams/active.yml` from them + active milestones per FORGE.md → Stream Rollup, the same way step 1.0 regenerates `index.yml`. **Run the 1.0 State Rollup (below) first** so `index.yml` is fresh — the Stream Rollup reads active milestones from it for implicit-row coverage, so on a boot where a just-merged milestone promotion lands, a stale `index.yml` would silently drop that milestone's coverage row. `active.yml` is derived — never read it as authoritative without rolling up first, and never hand-edit it. Runs in the main checkout (worktrees never write `active.yml`). No `streams/` dir → skip.
|
|
58
69
|
|
|
59
70
|
**Ready-to-merge nudge (main checkout / streams without checkpoints).** From the just-regenerated `active.yml`, scan for streams with `coordination: ready` or a non-empty `merge_queue` **that are not auto-publishing via checkpoints**. Any present → one line: *"{N} stream(s) ready to merge — run `/chief-of-staff` (merge safe) to integrate."* Pointer only — the cadence logic lives in the Chief's Merge Cadence Check; `forge` never merges. Nothing pending → silent.
|
|
60
71
|
|
|
@@ -125,7 +136,7 @@ Single-owner-mutable state must be written by exactly one checkout. When this se
|
|
|
125
136
|
Resolve the worktree path from the recorded `lifecycle.worktree_path` / `runtime.worktree`, or — if absent — from the base **Worktree Convention** (FORGE.md).
|
|
126
137
|
|
|
127
138
|
**Completed milestone + live worktree → finalize, not gate (precedence).** The gate assumes the live worktree holds *in-progress, owned* work. A worktree whose milestone file is already `current.status: complete` — it set complete and merged to main, then a fresh main boot lands here — needs the **opposite** of a write-block: it needs wrapping up. So when the selected milestone is `complete` **and** still has a live worktree, do **not** raise the four-option ownership gate. Surface the finalize offer instead: *"m{id} is complete but its worktree `{path}` (`{branch}`) is still live — roll up `index.yml` and tear down the worktree + branch? [yes / keep]"*.
|
|
128
|
-
- **yes** → confirm, then `git worktree remove {path}`, delete the branch, clear `lifecycle.worktree_*` on the milestone, and run **Rollup (1.0)**. (Never auto-remove without the explicit yes.)
|
|
139
|
+
- **yes** → confirm, then `git worktree remove {path}`, delete the branch, clear `lifecycle.worktree_*` on the milestone, and run **Rollup (1.0)**. If a stream file backs this milestone, also close it — clear `runtime.worktree`, set `stream.status: closed` (or prompt) — and run the **Stream Rollup** so `active.yml` drops the row pointing at the torn-down worktree. (Never auto-remove without the explicit yes.)
|
|
129
140
|
- **keep** → leave it; impose no write-block — a `complete` milestone has no live cursor left to fork.
|
|
130
141
|
|
|
131
142
|
The gate (below) and this finalize path are mutually exclusive on `current.status`: `complete` → finalize; any other status with a live worktree → gate.
|
|
@@ -185,30 +196,7 @@ Downstream skills (researching, discussing, planning, executing, verifying, revi
|
|
|
185
196
|
|
|
186
197
|
**Skip declined groups first.** Glob `.forge/state/desire-paths/declined/*.yml`; skip any group whose key (`type` + normalized `note`) already has a decline file — it stays dismissed, no re-nag.
|
|
187
198
|
|
|
188
|
-
**Surface at 3+.** For each remaining group with 3+ files,
|
|
189
|
-
|
|
190
|
-
| Type | Suggested evolution |
|
|
191
|
-
|------|---------------------|
|
|
192
|
-
| `deviation_pattern` | Add a pre-check to planning, or a new constitutional article |
|
|
193
|
-
| `tier_override` | Adjust tier-detection heuristics in the forge skill |
|
|
194
|
-
| `skipped_step` | Make the step optional, or merge it into another |
|
|
195
|
-
| `recurring_friction` | Add guidance to the relevant skill, or create a template |
|
|
196
|
-
| `agent_struggle` | Add examples or anti-patterns to the relevant skill |
|
|
197
|
-
| `user_correction` | Add a rule to constitution / context.md / the relevant skill |
|
|
198
|
-
|
|
199
|
-
Prompt: *"Recurring: [{note}] ({N}x, scope:{scope}). Fix: [concrete suggestion from table]. Apply / decline?"*
|
|
200
|
-
|
|
201
|
-
- **Agree** → apply the fix; archive the group's files to `.forge/state/desire-paths/resolved/`.
|
|
202
|
-
- **Decline** → write ONE file `.forge/state/desire-paths/declined/{date}-{type}-{slug}.yml` with `{group_key, declined_at, reason}`, so it never re-prompts. (Replaces the old "note, don't nag" — which wrote nothing, so the 3+ files re-nagged every boot.)
|
|
203
|
-
- (Occurrence count is derived from file count — there is no counter to reset.)
|
|
204
|
-
|
|
205
|
-
**Upstream transport — for `scope: framework` groups only** (ADR-012). A framework-scope signal's fix targets Forge's own skills/templates, which live in the Forge repo, not this project. So in addition to the Apply/decline prompt above:
|
|
206
|
-
|
|
207
|
-
1. **Always** write a portable, self-contained block to `.forge/state/desire-paths/upstream/{date}-{type}-{slug}.md` — pattern, occurrence count, the sibling observation filenames, the suggested evolution (from the table), and `suggestion_target`. Formatted to paste cleanly into a GH issue. This is the durable floor; it survives even with no network/tooling.
|
|
208
|
-
2. **Resolve `forge.upstream_repo`** from `project.yml`; if absent, default to `https://github.com/zayneupton/forge`. If resolvable **and** `gh` is on PATH → offer (don't auto-run — it publishes externally): `gh issue create --repo {upstream_repo} --title "[desire-path] {note}" --body-file {block}`. **The GH issue is the real close** — tracked, central, survives machine loss.
|
|
209
|
-
3. If `gh`/url unavailable → note the block is on disk and harvestable by `upgrading` (which runs from a local Forge clone) into `{source}/docs/desire-paths-inbox/`.
|
|
210
|
-
|
|
211
|
-
`project`-scope groups keep the in-project flow above — no upstream. Applying a framework fix locally (when this *is* the Forge repo) and routing it upstream are not mutually exclusive: in a user project you route; in the Forge repo you apply.
|
|
199
|
+
**Surface at 3+.** For each remaining group with 3+ files (declined groups already skipped), **load `desire-paths-review.md` in this skill dir and follow it** — it carries the concrete fix-suggestion map (per `type`), the Apply/decline mechanics (archive to `resolved/` on apply; write a `declined/` file on decline — no re-nag), and the upstream transport for `scope: framework` signals (portable block + optional `gh issue` against `forge.upstream_repo`). Groups under 3 (the common boot) → nothing to surface; skip the load. (Occurrence count is derived from file count — no counter to reset.)
|
|
212
200
|
|
|
213
201
|
### 1.3 Interface Check
|
|
214
202
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Desire-Path Review — Surfacing & Fix Map
|
|
2
|
+
|
|
3
|
+
Loaded by `forge` boot (SKILL.md Step 1.2) **only when a desire-path group reaches
|
|
4
|
+
3+ occurrences**. The cheap part of the review — glob, skip declined groups, count
|
|
5
|
+
per group — stays inline in SKILL.md; this file carries the fix-suggestion map,
|
|
6
|
+
the apply/decline mechanics, and the upstream transport for framework-scope
|
|
7
|
+
signals, so the common boot (no group at 3+) never loads it.
|
|
8
|
+
|
|
9
|
+
## Surface at 3+ — concrete fix map
|
|
10
|
+
|
|
11
|
+
For each remaining group with 3+ files, name a CONCRETE fix from this table:
|
|
12
|
+
|
|
13
|
+
| Type | Suggested evolution |
|
|
14
|
+
|------|---------------------|
|
|
15
|
+
| `deviation_pattern` | Add a pre-check to planning, or a new constitutional article |
|
|
16
|
+
| `tier_override` | Adjust tier-detection heuristics in the forge skill |
|
|
17
|
+
| `skipped_step` | Make the step optional, or merge it into another |
|
|
18
|
+
| `recurring_friction` | Add guidance to the relevant skill, or create a template |
|
|
19
|
+
| `agent_struggle` | Add examples or anti-patterns to the relevant skill |
|
|
20
|
+
| `user_correction` | Add a rule to constitution / context.md / the relevant skill |
|
|
21
|
+
|
|
22
|
+
Prompt: *"Recurring: [{note}] ({N}x, scope:{scope}). Fix: [concrete suggestion from table]. Apply / decline?"*
|
|
23
|
+
|
|
24
|
+
- **Agree** → apply the fix; archive the group's files to `.forge/state/desire-paths/resolved/`.
|
|
25
|
+
- **Decline** → write ONE file `.forge/state/desire-paths/declined/{date}-{type}-{slug}.yml` with `{group_key, declined_at, reason}`, so it never re-prompts. (Replaces the old "note, don't nag" — which wrote nothing, so the 3+ files re-nagged every boot.)
|
|
26
|
+
- (Occurrence count is derived from file count — there is no counter to reset.)
|
|
27
|
+
|
|
28
|
+
## Upstream transport — `scope: framework` groups only (ADR-012)
|
|
29
|
+
|
|
30
|
+
A framework-scope signal's fix targets Forge's own skills/templates, which live in
|
|
31
|
+
the Forge repo, not this project. So in addition to the Apply/decline prompt above:
|
|
32
|
+
|
|
33
|
+
1. **Always** write a portable, self-contained block to `.forge/state/desire-paths/upstream/{date}-{type}-{slug}.md` — pattern, occurrence count, the sibling observation filenames, the suggested evolution (from the table), and `suggestion_target`. Formatted to paste cleanly into a GH issue. This is the durable floor; it survives even with no network/tooling.
|
|
34
|
+
2. **Resolve `forge.upstream_repo`** from `project.yml`; if absent, default to `https://github.com/zayneupton/forge`. If resolvable **and** `gh` is on PATH → offer (don't auto-run — it publishes externally): `gh issue create --repo {upstream_repo} --title "[desire-path] {note}" --body-file {block}`. **The GH issue is the real close** — tracked, central, survives machine loss.
|
|
35
|
+
3. If `gh`/url unavailable → note the block is on disk and harvestable by `upgrading` (which runs from a local Forge clone) into `{source}/docs/desire-paths-inbox/`.
|
|
36
|
+
|
|
37
|
+
`project`-scope groups keep the in-project flow above — no upstream. Applying a framework fix locally (when this *is* the Forge repo) and routing it upstream are not mutually exclusive: in a user project you route; in the Forge repo you apply.
|
|
@@ -270,6 +270,10 @@ No library + UI files → *"No component library. Design system or custom?"*
|
|
|
270
270
|
|
|
271
271
|
Present grouped. User confirms/adds/removes.
|
|
272
272
|
|
|
273
|
+
### Step 6.5: Rules Layer
|
|
274
|
+
|
|
275
|
+
The project ships with `.claude/rules/agent-discipline.md` (always-on AI-discipline floor). Point the user at the **rules layer** (`.claude/rules/README.md`) for codifying conventions that should auto-load by file path — cheaper than the constitution, lighter than a skill (ADR-018). If a stack was detected (Laravel/PHP, Python, Vue), offer the **opt-in convention pack**: *"Detected {stack}. Install the convention skills + path-scoped rules? `bash experimental/conventions/install.sh {stack}`"* (skip if the pack isn't present in this install). Stack conventions are opt-in — never auto-installed into the stack-agnostic core.
|
|
276
|
+
|
|
273
277
|
## Greenfield
|
|
274
278
|
|
|
275
279
|
### Step 1: Basics
|
|
@@ -76,11 +76,11 @@ Resolve current milestone ID from `.forge/state/index.yml` (active milestone) or
|
|
|
76
76
|
|
|
77
77
|
If missing, create from `.forge/templates/requirements.yml`:
|
|
78
78
|
1. Extract from user description + research
|
|
79
|
-
2. IDs: FR-001, FR-002... **Globally unique across milestones**
|
|
79
|
+
2. IDs: FR-001, FR-002... **Globally unique across milestones.** Allocate via the **ID Reservation Protocol** (FORGE.md). **This is a blocking gate, not advisory: do not write an `FR-`/`NFR-`/`DEF-` ID into any artifact until its reservation is appended and committed.** Per ID: next = `max(highest `kind: fr` in `.forge/reservations.yml`, highest FR in `.forge/requirements/*.yml`) + 1; append `{kind: fr, id, milestone, reserved_at, summary}` to `.forge/reservations.yml`, **commit + push**, *then* write the FR. Skipping the reserve step is the exact failure ADR-016 exists to prevent — bare scan-and-increment sees only committed state, so two worktrees branched from one baseline claim the same number and collide silently until merge (a multi-file renumber). No `reservations.yml` yet ⇒ scan as before; the first reservation creates it (lazy migration), but once it exists the gate is mandatory.
|
|
80
80
|
3. Acceptance: Given/When/Then
|
|
81
81
|
4. Uncertain: `[NEEDS CLARIFICATION]`
|
|
82
82
|
5. P1 (must) / P2 (should) / P3 (nice)
|
|
83
|
-
6. Deferred: DEF-001...
|
|
83
|
+
6. Deferred: DEF-001..., and NFR-001... — **also globally unique; reserve the same way** (`kind: def` / `kind: nfr`) before writing.
|
|
84
84
|
|
|
85
85
|
**E2E gate (M9):** For each functional requirement being added or refined:
|
|
86
86
|
1. Decide `e2e: true|false` -- does this story need a post-validation e2e test?
|
|
@@ -135,6 +135,8 @@ This is the common case when starting a new milestone alongside an existing one.
|
|
|
135
135
|
|
|
136
136
|
If a sibling milestone (e.g. m50) has state + requirements but is missing from `roadmap.yml`, that's a pre-existing gap -- flag to user, don't silently backfill.
|
|
137
137
|
|
|
138
|
+
**Promoted-from-backlog milestone (`milestone.origin` set).** If `state/milestone-{N}.yml` carries `milestone.origin: {R-id}` (set by `forge` Step 1.2 when promoting a Standard-tier refactor item), no extra bookkeeping is needed here — the originating backlog item is flipped to `done` automatically when the milestone completes (see `reviewing` → **Promoted Milestone Completion Hook**). Just plan it as a normal milestone.
|
|
139
|
+
|
|
138
140
|
## Step 6: Decompose Tasks
|
|
139
141
|
|
|
140
142
|
### Step 6.1: Cross-Layer Contract Detection
|
|
@@ -205,6 +207,18 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
|
|
|
205
207
|
</task>
|
|
206
208
|
```
|
|
207
209
|
|
|
210
|
+
#### Overlay / Popover must_haves (when a plan adds a floating surface)
|
|
211
|
+
|
|
212
|
+
When a plan introduces an **overlay, popover, sheet, anchored panel, or any floating surface** (especially one anchored to a moving element like a canvas block), specifying its *content* (rows, keys, payload) is **not enough** — the *behavior* questions below only surface when a human touches the running UI, each costing a full build → deploy → UAT round. Add a `must_haves.truths` entry for every item that applies, phrased as a user-observable behavior:
|
|
213
|
+
|
|
214
|
+
1. **Placement + edge clamp** — where does it appear relative to its anchor, and does it stay fully on-screen when the anchor is near a viewport/canvas edge?
|
|
215
|
+
2. **Drag-tracking** — if the anchor can move (drag, scroll, reflow), does the overlay follow it?
|
|
216
|
+
3. **Live param re-sync** — while open, does it re-read the anchor's state so it reflects live changes (not a stale snapshot from open time)?
|
|
217
|
+
4. **Z-order + tap-order** — draw order *and* hit-test order versus every other overlay/popover that can be open in the same surface.
|
|
218
|
+
5. **Open/dismiss lifecycle** — what opens it, what dismisses it, and what happens if another overlay opens while it is already open?
|
|
219
|
+
|
|
220
|
+
Treat this as a checklist, not a template: drop the items that don't apply (a static centered modal has no anchor, so 1–3 collapse to "centered + on-screen"). The point is to decide each at plan time rather than discover it at device UAT. (Desire-path: canvaz m-SEQ01 — six overlay-behavior gaps, 5+ deploy-and-retest rounds.)
|
|
221
|
+
|
|
208
222
|
#### Integration Checkpoints (merge often and early)
|
|
209
223
|
|
|
210
224
|
Mark the phases where a verified slice should **land on main**, so a stream integrates continuously instead of batching at milestone end (FORGE.md → Stream Integration Checkpoints). Set `integration_checkpoint: true` in the plan frontmatter for those phases.
|
|
@@ -364,7 +378,7 @@ Prompt pattern:
|
|
|
364
378
|
|
|
365
379
|
- **yes** — defer to `testing` after planning completes; note in plan frontmatter
|
|
366
380
|
- **no** — document rationale in `context.md` Discretion Areas
|
|
367
|
-
- **later** —
|
|
381
|
+
- **later** — rides on the verifying **Interface Testing Gate**: when `interface` is declared in `project.yml`, that gate already BLOCKS at verifying if UI tests are still missing (no separate planning hook needed)
|
|
368
382
|
|
|
369
383
|
Decision captured once, pre-code. Does not block planning.
|
|
370
384
|
|
|
@@ -56,6 +56,8 @@ File lists per subagent (paths, not globs).
|
|
|
56
56
|
|
|
57
57
|
Fresh-context subagents with file list + tech stack. **Pass `model` param** from `project.yml` model routing (`models.skills.reviewing` → `models.default` → parent). Display: *"Spawning reviewers with model: {model} (from {source})"*
|
|
58
58
|
|
|
59
|
+
The `reviewer` agent (3 modes below) is the **breadth pass** that gates the health report. For high-risk diffs (auth, payments, data-access, hot paths, public-facing docs) you may **additionally** spawn the confidence-gated specialists — `security-reviewer`, `performance-reviewer`, `doc-reviewer` (ADR-018) — for defensible single-axis depth. They report only Confidence ≥ 8 (security requires a concrete exploit scenario), so their output folds into the breadth findings without noise. Optional and additive — skip for low-risk changes.
|
|
60
|
+
|
|
59
61
|
### Part 1: Security Audit
|
|
60
62
|
|
|
61
63
|
| # | Category | Checks |
|
|
@@ -52,15 +52,15 @@ Read: .github/workflows/* → CI config (ci-check mode, analyst CI sub-check)
|
|
|
52
52
|
3. Agent appends actionable items to `.forge/refactor-backlog.yml`:
|
|
53
53
|
```yaml
|
|
54
54
|
- id: T-{NNN}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
category: flake | coverage-gap | anti-pattern | quarantine
|
|
55
|
+
milestone: {active milestone id}
|
|
56
|
+
category: flake | coverage-gap | anti-pattern | quarantine-candidate | ci-gap
|
|
58
57
|
file: "path/to/test.spec.ts"
|
|
59
58
|
lines: "NN-NN"
|
|
60
59
|
description: "What's wrong"
|
|
61
60
|
effort: quick | standard
|
|
62
61
|
suggested_approach: "How to fix"
|
|
63
62
|
status: pending
|
|
63
|
+
added: "YYYY-MM-DD"
|
|
64
64
|
```
|
|
65
65
|
ID numbering: read existing backlog, find max T-NNN, increment. No gaps.
|
|
66
66
|
4. Receive agent YAML output (`suite_audit:` block). Review for completeness.
|
|
@@ -110,6 +110,8 @@ Runs AFTER code-level verification commands pass. Skipped if no `e2e:true` stori
|
|
|
110
110
|
5. **Hash drift check** (run BEFORE prompting, every gate invocation): for each `e2e:true` FR with `validated: true`, recompute hash from current `observable_outcome`. If it differs from stored `observable_outcome_hash` → set `validated: false`, clear hash. Note auto-reset in verification report. Then prompt that FR as unvalidated.
|
|
111
111
|
6. Write per-FR validation outcomes into the verification report under section "E2E Validation".
|
|
112
112
|
|
|
113
|
+
**Exclusive-resource lease (concurrent streams + a shared physical device).** If this gate's manual walk needs an exclusive resource declared in `project.yml` `orchestration.exclusive_resources` (a physical device, a singleton emulator) **and** other streams are active, acquire the lease before deploying and release it after — otherwise a sibling stream's `device install` clobbers this build mid-walk and you verify the wrong binary (forge#15). Acquire = write `runtime.lease: {resource, acquired_at, operation: e2e-validation}` to this stream's file; the resource is busy → queue (or skip the device gate with a recorded reason) until it frees. Release = clear the field once the walk completes. Single checkout / no declared exclusive resources → no lease, proceed. See chief-of-staff → **Exclusive Resource Leases**; prefer per-stream isolation (bundle-ID suffix) over leasing when the resource can be split.
|
|
114
|
+
|
|
113
115
|
### Gate behavior
|
|
114
116
|
|
|
115
117
|
- **Advisory, not blocking.** Verifying still passes even if no stories validated — the hard gate is in `testing` skill author-mode (phase 16). This gate's job is to surface + record, not block.
|
|
@@ -302,18 +304,27 @@ If GAPS found, route back to planning in gap-closure mode. Context clear applies
|
|
|
302
304
|
Realizes producer-side continuous integration (FORGE.md → Stream Integration Checkpoints). Runs at Phase Handoff step 5, **only when all hold**:
|
|
303
305
|
|
|
304
306
|
- the phase's plan frontmatter has `integration_checkpoint: true`;
|
|
305
|
-
- the verdict is PASSED
|
|
307
|
+
- the verdict is PASSED — never publish unverified work. (`current.human_verified` gates milestone **close**, *not* per-checkpoint publish: a non-final checkpoint phase publishes on PASS alone; the milestone's **final** checkpoint coincides with close, where the Human Verification Gate applies — so in practice only the last checkpoint also requires `human_verified`.)
|
|
306
308
|
- the session is a **worktree** (`git rev-parse --git-dir` ≠ `--git-common-dir`). In a single checkout, skip — there is no main to fast-forward into.
|
|
307
309
|
|
|
308
|
-
|
|
310
|
+
**Strand guard — never push past the operator's local main (forge#13).** `git push origin HEAD:main` is fast-forward-only *relative to `origin/main`* — but `origin/main` is **not** the operator's canonical checkout. The local `main` ref (shared across this machine's worktrees via the common git dir) can carry commits that were never pushed — another stream's merge, a framework bump, the state-sync commits from step 4. If you push the worktree tip onto an `origin/main` that is *behind* local main, origin fast-forwards but **local main is left diverged** (it has commits origin's new tip lacks, and vice versa). The closing report then mis-reads this as "local main is behind — `git pull`", and `git pull --ff-only` *fails* on divergence while a bare `git pull` makes a surprise merge commit — the exact failure the merge discipline exists to prevent. So gate the push on local main being in the publish's direct line:
|
|
309
311
|
|
|
310
312
|
```bash
|
|
311
|
-
|
|
312
|
-
#
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
git fetch origin main 2>/dev/null || true # refresh origin/main (no-op without a remote)
|
|
314
|
+
# Publish is strand-safe only when local main is an ANCESTOR of this worktree's HEAD —
|
|
315
|
+
# i.e. local main has no commits this checkpoint doesn't already contain.
|
|
316
|
+
if git rev-parse --verify -q main >/dev/null 2>&1 \
|
|
317
|
+
&& ! git merge-base --is-ancestor main HEAD; then
|
|
318
|
+
: "STRANDED — local main carries commits this checkpoint omits. Do NOT push."
|
|
319
|
+
fi
|
|
315
320
|
```
|
|
316
321
|
|
|
317
|
-
- **
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
- **Strand-safe** (local main is an ancestor of HEAD, or no local `main` ref exists) → publish **fast-forward only**:
|
|
323
|
+
```bash
|
|
324
|
+
git push origin HEAD:main # remote present; git refuses a non-ff push (diverged origin surfaced, never forced)
|
|
325
|
+
# no remote → fast-forward the local main ref where possible, else report ready-to-ff
|
|
326
|
+
```
|
|
327
|
+
- **Success** → set the integration flag for **all** of this machine's checkouts (sibling worktrees *and* the primary main checkout): write `.git/forge/integration-pending` (in the git common dir) with the new main sha, this stream/milestone, and the shared surfaces this phase changed. Because local main was an ancestor, advancing it is a **true fast-forward** — report: *"Checkpoint published — phase {N} fast-forwarded onto main. Local main is now behind origin by this slice; it auto-fast-forwards on the next `/forge` boot, or `git merge --ff-only origin/main` from the primary checkout now."* (Here, and only here, is local main genuinely *behind* — the ff advice is correct.)
|
|
328
|
+
- **Rejected (non-ff — `origin/main` itself diverged)** → **do not force.** Surface: *"origin/main has advanced since this worktree branched — publish needs a manual merge/rebase first."* Leave the work on the branch; the Chief's Merge Cadence Check / a human resolves it.
|
|
329
|
+
- **Stranded** (local main has commits this checkpoint omits) → **do not push.** Integrating through origin would diverge local main. Surface: *"main has advanced locally ({N} commit(s) not in this checkpoint) since this worktree branched. Publishing direct to origin would strand your local main (diverged, not behind). Integrate through local main instead: from the primary checkout `git merge {branch}` (or rebase this worktree onto main, then publish). Do NOT `git pull --ff-only` — it fails on divergence."* Leave the work on the branch; a human integrates.
|
|
330
|
+
- Never auto-resolve a conflict, never force-push, never auto-rebase.
|