forge-orkes 0.62.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/create-forge.js +250 -99
  2. package/experimental/conventions/README.md +87 -0
  3. package/experimental/conventions/install.sh +179 -0
  4. package/experimental/conventions/source/rules/code-quality.md +17 -0
  5. package/experimental/conventions/source/rules/laravel-http.md +21 -0
  6. package/experimental/conventions/source/rules/laravel-jobs.md +19 -0
  7. package/experimental/conventions/source/rules/laravel-migrations.md +19 -0
  8. package/experimental/conventions/source/rules/laravel-services.md +21 -0
  9. package/experimental/conventions/source/rules/python.md +14 -0
  10. package/experimental/conventions/source/rules/testing.md +15 -0
  11. package/experimental/conventions/source/rules/vue-inertia.md +24 -0
  12. package/experimental/conventions/source/skills/laravel-conventions/SKILL.md +124 -0
  13. package/experimental/conventions/source/skills/python-conventions/SKILL.md +100 -0
  14. package/experimental/conventions/source/skills/testing-standards/SKILL.md +142 -0
  15. package/experimental/conventions/source/skills/vue-conventions/SKILL.md +92 -0
  16. package/experimental/conventions/uninstall.sh +97 -0
  17. package/experimental/m10/README.md +130 -0
  18. package/experimental/m10/install.sh +198 -0
  19. package/experimental/m10/source/hooks/forge-branch-guard.sh +61 -0
  20. package/experimental/m10/source/hooks/forge-claim-check-doctor.sh +77 -0
  21. package/experimental/m10/source/hooks/forge-claim-check.sh +113 -0
  22. package/experimental/m10/source/hooks/forge-session-id.sh +22 -0
  23. package/experimental/m10/source/mcp-server/README.md +74 -0
  24. package/experimental/m10/source/mcp-server/example.mcp.json +8 -0
  25. package/experimental/m10/source/mcp-server/index.js +460 -0
  26. package/experimental/m10/source/mcp-server/package.json +17 -0
  27. package/experimental/m10/source/skills/orchestrating/SKILL.md +223 -0
  28. package/experimental/m10/source/skills/orchestrating/bootstrap-checks.md +70 -0
  29. package/experimental/m10/uninstall.sh +69 -0
  30. package/package.json +3 -2
  31. package/template/.claude/settings.json +1 -7
  32. package/template/.claude/skills/upgrading/SKILL.md +105 -176
  33. package/template/.forge/FORGE.md +2 -2
  34. package/template/.forge/migrations/0.63.0-origin-first-upgrading.md +49 -0
@@ -0,0 +1,223 @@
1
+ ---
2
+ name: orchestrating
3
+ description: "[Experimental — M10] Optional backend for Forge stream isolation. Bootstrap worktrees, coordinate merge queue, and optionally use MCP file claims. Prefer Chief/Streams as the user-facing orchestration model."
4
+ ---
5
+
6
+ # Orchestrating
7
+
8
+ Multi-agent session lifecycle substrate. Worktree isolation + merge queue remain
9
+ useful backend mechanics for Forge Streams. MCP/SQLite file claims are optional
10
+ and experimental defense-in-depth, not the primary coordination model.
11
+
12
+ ## M16 Positioning
13
+
14
+ Chief/Streams is the preferred user-facing orchestration model. The Project
15
+ Chief decides when a stream needs M10 mechanics such as worktree isolation,
16
+ branch anchoring, merge queue discipline, or claim checks. Users should normally
17
+ ask to start, pause, delegate, conflict-check, merge, or close streams; they
18
+ should not have to invoke M10 directly.
19
+
20
+ M10 still matters when concurrent streams need hard workspace separation or a
21
+ controlled merge path. It is a backend/substrate, not the project manager.
22
+
23
+ ## When to use
24
+
25
+ - The Project Chief selects M10 for a stream that needs worktree isolation,
26
+ merge-queue support, or optional file-claim checks.
27
+ - User explicitly invokes multi-agent mode (`/forge` argument selects multi-agent, or direct skill invocation).
28
+ - Legacy installs may route Full tier work through this skill when
29
+ `orchestration.auto` remains enabled.
30
+
31
+ Skip on Quick tier, single-phase work, or when bootstrap checks fail.
32
+
33
+ ## Enforcement scope — what claims actually protect
34
+
35
+ Claim coordination (`forge_claim_files` + the PreToolUse gate) works for **one** mode only: a **single parent session** operating across a Forge-managed sibling worktree dir (default `../<repo>-worktrees/{anchor}`, configurable via `orchestration.worktree_root`), where main's hooks + `.session-id` apply. **It does NOT reach `Agent isolation:"worktree"` sub-agents** — those run in `.claude/worktrees/agent-*` with no hook config (`.claude` is gitignored), so they fire no SessionStart, claim under nothing, and their edits are **not gated**. For within-milestone fan-out, **worktree isolation + disjoint files behind a frozen contract is the safety — not claims.** Never assume a fan-out sub-agent is claim-protected; it is not. (See ADR-007.)
36
+
37
+ ## Step 1: Bootstrap
38
+
39
+ Run all checks in `bootstrap-checks.md`:
40
+
41
+ 1. Git version ≥ 2.48
42
+ 2. LFS version ≥ 3.6 (skip if not installed)
43
+ 3. No divergent submodules — clean pinned submodules pass (auto-inited per worktree in Step 2); refuse only on `-`/`+`/`U` entries (see `bootstrap-checks.md` "Submodule handling")
44
+ 4. `core.hooksPath` empty or resolves inside worktree
45
+ 5. `git hook run pre-commit` smoke test in fresh worktree
46
+ 6. **Enforcement canary (advisory)** — `bash .claude/hooks/forge-claim-check-doctor.sh` reports `gate enforces`. On `GATE IS INERT`, warn loudly that claims provide no protection this session (worktree isolation still applies); do **not** refuse on this one — it never blocks, only surfaces.
47
+
48
+ **Any check 1–5 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. Check 6 is advisory — it warns, never refuses.
49
+
50
+ ## Step 2: Session ID + worktree
51
+
52
+ ```bash
53
+ session_id=$(uuidgen | cut -c1-8)
54
+ # Anchor the worktree name to the milestone id so the branch is self-describing:
55
+ # the `m-{id}` token lets `forge` resume re-derive the milestone from the branch
56
+ # name alone (its id-token fallback), not only from lifecycle.worktree_branch
57
+ # state — which a moved/redone worktree can leave stale. The uuid suffix keeps the
58
+ # name collision-resistant and unique per session; the `forge/` prefix keeps the GC
59
+ # sweep simple. Milestone ids are dash-free, so `m-{id}` tokenizes cleanly.
60
+ anchor="m-${milestone_id}-${session_id}"
61
+
62
+ # Worktree root — configurable, repo-scoped by default (ADR-010).
63
+ # Read `orchestration.worktree_root` from .forge/project.yml; if unset, default
64
+ # to a per-repo sibling `../<repo-basename>-worktrees`. Per-repo siblings keep
65
+ # each repo's worktrees in their own dir (no cross-repo mixing when multiple
66
+ # repos share a parent), and resolve deterministically against the repo root
67
+ # rather than the shell cwd. Relative roots resolve against repo root;
68
+ # absolute roots and a leading `~` are honored as-is.
69
+ repo_root=$(git rev-parse --show-toplevel)
70
+ wt_root=$(sed -n '/^orchestration:/,/^[^[:space:]#]/p' "$repo_root/.forge/project.yml" 2>/dev/null \
71
+ | sed -n 's/^[[:space:]]*worktree_root:[[:space:]]*//p' | tr -d "\"'" | head -1)
72
+ wt_root="${wt_root:-../$(basename "$repo_root")-worktrees}"
73
+ case "$wt_root" in
74
+ /*) : ;; # absolute — use as-is
75
+ "~"*) wt_root="$HOME${wt_root#\~}" ;; # expand leading ~
76
+ *) wt_root="$repo_root/$wt_root" ;; # relative → resolve against repo root
77
+ esac
78
+ mkdir -p "$wt_root"
79
+
80
+ git worktree prune
81
+ git worktree add -b forge/${anchor} --lock --reason "forge session m-${milestone_id}" "$wt_root/${anchor}" main
82
+ wt="$wt_root/${anchor}"
83
+ # Submodules don't auto-populate in a new worktree — init them if present. Reuses the
84
+ # superproject's shared .git/modules object store (no re-clone, just a working-tree
85
+ # checkout); ccache absorbs the subsequent rebuild cost across worktrees.
86
+ if [ -n "$(git -C "$wt" submodule status 2>/dev/null)" ]; then
87
+ ( cd "$wt" && git submodule update --init --recursive )
88
+ fi
89
+ ( cd "$wt" && git hook run pre-commit || true )
90
+ ```
91
+
92
+ Verify worktree dir exists, branch locked, and (if applicable) submodule dirs are non-empty. On failure → cleanup partial state, refuse mode.
93
+
94
+ `anchor` (= `m-{milestone_id}-{session_id}`) names the worktree dir **and** branch from here on; every reference below that used to be `{session_id}` is now `{anchor}`. `session_id` (the bare uuid) is retained only as the collision key + audit id in state.
95
+
96
+ The **absolute** path Forge resolved (`$wt_root/${anchor}`) is recorded in `lifecycle.worktree_path` (Step 3). Every later operation — Step 4 handback, Step 5 teardown, Step 6 crash-recovery re-attach — uses that recorded path verbatim, never re-deriving it. This decouples in-flight worktrees from the config: changing `orchestration.worktree_root` later (or moving a worktree on disk) does not invalidate them.
97
+
98
+ ## Step 3: State update
99
+
100
+ Write into `.forge/state/milestone-{id}.yml`:
101
+
102
+ ```yaml
103
+ lifecycle:
104
+ session_id: "{session_id}" # short uuid — collision key + audit id only (not a name, not a claim id)
105
+ worktree_anchor: "m-{id}-{session_id}" # milestone-anchored name of the worktree dir + branch
106
+ claim_session_id: "{claim_session_id}" # Claude session id — owner of all file claims (see Identity)
107
+ worktree_path: "{absolute path Forge resolved in Step 2 — $wt_root/m-{id}-{session_id}}"
108
+ worktree_branch: "forge/m-{id}-{session_id}"
109
+ worktree_mode: "active"
110
+ started_at: "{ISO8601}"
111
+ ```
112
+
113
+ Set `current.last_updated` in the milestone file. Do **not** write `state/index.yml` directly — it is a derived registry, regenerated by the `forge` rollup. (The orchestrator owns index.yml; worktree agents never touch it.)
114
+
115
+ ## Step 4: Hand back
116
+
117
+ Relocate the session into the worktree, then return control to caller (typically `executing`) — instead of only *assuming* the caller is already inside `lifecycle.worktree_path`. Apply the canonical **Native Worktree Entry** contract (identical to the forge ownership-gate option 1):
118
+
119
+ - **If the `EnterWorktree` tool is available**, call `EnterWorktree({path: {lifecycle.worktree_path}})` to relocate the session into the worktree before returning control, then log `Relocated into worktree {lifecycle.worktree_path} ({lifecycle.worktree_branch})` (independent of any prompt, so the cwd move is visible).
120
+ - **If `EnterWorktree` is unavailable** (older Claude Code CLI, or a non-Claude-Code adapter), fall back to instructing the caller to `cd {path}` (here `cd {lifecycle.worktree_path}`) before continuing — today's implicit assumption stated explicitly.
121
+ - Exit stays `ExitWorktree({action: "keep"})`; teardown remains Step 5 / `git worktree remove` — the native tool never removes the worktree.
122
+
123
+ All subsequent work runs inside `lifecycle.worktree_path`. Caller honors claim convention.
124
+
125
+ ### Claim convention (executing-skill contract)
126
+
127
+ State-file ownership: each session writes **only** its own `.forge/state/milestone-{own_id}.yml` and **appends** to `.forge/state/desire-paths/` (new files, never edited). It never writes `state/index.yml` — the registry is regenerated by rollup at teardown. Different milestones = different files, so these writes need no claim and never conflict across worktrees. Same-milestone parallelism is out of scope (one session per milestone — see the concurrent-invocation refusal in Edge Cases).
128
+
129
+ **Identity (critical — see ADR-007).** File claims are owned by the **Claude session id**, NOT the worktree uuid. The worktree uuid (`session_id` above) only feeds the worktree name (via `anchor`) and serves as the collision/audit key. Read the claim identity from the file the `forge-session-id.sh` SessionStart hook writes:
130
+
131
+ ```bash
132
+ claim_session_id=$(cat "${CLAUDE_PROJECT_DIR:-.}/.forge/.mcp-server/.session-id" 2>/dev/null)
133
+ ```
134
+
135
+ This is the same id the `forge-claim-check.sh` enforcement hook reads from PreToolUse stdin, so a session's own claims are recognized and other sessions' claims block. If the file is missing/empty the hook fail-opens (no enforcement) — surface that rather than proceeding silently in genuine multi-agent work.
136
+
137
+ Before any `Edit`, `Write`, `MultiEdit`, or `NotebookEdit` on a file outside `.forge/state/milestone-{own_id}.yml`:
138
+
139
+ 1. Call `forge_claim_files` with `{ session_id: claim_session_id, files: [...], ttl_seconds: 900 }`.
140
+ 2. On `granted` → proceed with edit.
141
+ 3. On `conflict: { holder_session, files: [...] }` → surface holder + files to user. Options:
142
+ - **wait** → poll `forge_claim_status` until released or TTL expiry.
143
+ - **skip** → drop the conflicted file from the task scope, continue with rest.
144
+ - **steal** → only if holder session is provably dead (PreToolUse hook validates). Otherwise refused.
145
+ 4. After edit batch → claim auto-extends on continued use; explicit `forge_release_claims` on plan-complete.
146
+
147
+ PreToolUse hook (installed by plan-03) enforces this — uncaught violations block at hook level, not skill level.
148
+
149
+ ## Step 5: Teardown
150
+
151
+ Triggered when caller signals work complete OR user requests teardown.
152
+
153
+ ### Step 5.0: Human Verification Gate (hard block — runs first)
154
+
155
+ Before any merge or worktree removal, read `current.human_verified` from `.forge/state/milestone-{id}.yml`.
156
+
157
+ - **Present** (`{at, method, ...}`, including a recorded `override`) → gate satisfied, proceed to the merge below.
158
+ - **Absent / null** → **REFUSE teardown.** Code-clean + FF-clean is **not** enough to close a milestone (FORGE.md → Human Verification Gate). Do **not** present teardown as an option, and never offer it as a peer alternative to the outstanding verification. Respond with what's still unverified and offer exactly two paths:
159
+ 1. **Verify now** → run the human verification (device session / visual / e2e walk). Have `verifying` (or this step) record `current.human_verified`, then re-invoke teardown.
160
+ 2. **Record override** → only on an explicit human instruction to close without verifying: write `current.human_verified: {at: "<ISO 8601>", method: "override", override: true, reason}` to the milestone file, surface it, then proceed.
161
+
162
+ Never merge + remove a worktree while `human_verified` is unset.
163
+
164
+ ```
165
+ forge_queue_commit(branch=forge/m-{id}-{session_id}, base_sha={merge_base})
166
+ ```
167
+
168
+ Branch on response status:
169
+
170
+ - **`merged`** (response has `new_main_sha`) → **reconcile the working tree first** — `forge_queue_commit` merges *ref-only* (`update-ref`), so the main checkout is now behind HEAD: `git checkout HEAD -- $(git diff --name-only {base_sha} {new_main_sha})` — surgical, merged files only; **never** `git checkout HEAD -- .` (would clobber unrelated WIP) → `forge_release_claims(claim_session_id)` (the Claude session id, not the worktree uuid) → `git worktree remove --force --force "$(yq '.lifecycle.worktree_path' .forge/state/milestone-{id}.yml)"` (double `--force`: Step 2 created the worktree `--lock`ed, and a single `--force` refuses to remove a locked worktree; read the path from state rather than re-deriving it so a moved/non-default worktree still removes cleanly) → `git branch -d forge/m-{id}-{session_id}` → clear `lifecycle.*` (set `worktree_mode: complete`, retain `session_id` for audit) → **State reconcile = Rollup:** regenerate `state/index.yml` from the now-merged `milestone-*.yml` files (`forge` Rollup 1.0) and commit it (`chore(forge): rollup state after teardown — {session_id}`). The merged milestone files are the source of truth; index.yml is never hand-merged.
171
+ - **`conflict`** → invoke `Skill(debugging)` with payload `{ conflicted_files, base_sha, messages, branch }`. Teardown blocks until debugging signals resolution (re-invoke teardown after fix).
172
+ - **`stale_base`** → caller rebases worktree branch onto `current_target_sha` from response, retries `forge_queue_commit`. Max 3 retries → escalate to conflict path.
173
+
174
+ ## Step 6: Crash recovery (next session start)
175
+
176
+ If no clean teardown happened previously:
177
+
178
+ 1. `git worktree prune` — drops stale admin dirs.
179
+ 2. `git branch --list 'forge/*'` — for each branch with no live worktree, prompt user. The branch suffix is the `anchor` (`m-{id}-{session_id}`); the milestone id is the `m-…` token, recoverable from the name alone even if state is stale:
180
+ - **resume** → re-attach at the path recorded in `lifecycle.worktree_path` (read from `state/milestone-{id}.yml` — the recorded absolute path survives a `worktree_root` config change after the fact). State missing the path → fall back to the current default (`<repo_root>/../<repo-basename>-worktrees/{anchor}`) and warn. `git worktree add <path> forge/{anchor}` and restore lifecycle state.
181
+ - **delete** → `git branch -D forge/{anchor}`.
182
+ 3. MCP server startup handles pidfile takeover + claim TTL expiry independently (see ADR-003).
183
+
184
+ ## Failure modes & operator notes
185
+
186
+ - **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.
187
+ - **Disk full during worktree add** — `git worktree add` will error. Cleanup any partial `$wt_root/m-{id}-{session_id}/` dir, refuse mode, fall back.
188
+ - **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.
189
+ - **Worktree path collision** — UUIDv4 short (8 chars) collision negligible at <100 concurrent sessions. If `$wt_root/m-{id}-{session_id}/` already exists → regenerate session_id (the `anchor` recomputes with it), retry up to 3 times.
190
+ - **`worktree_root` changed mid-flight** — Step 3 records the resolved absolute `lifecycle.worktree_path`; teardown + crash-recovery read it from state, never re-derive. Editing `orchestration.worktree_root` in `project.yml` only affects newly created worktrees. Do not relocate live worktrees on disk by hand — `git worktree move` is the supported way; bare `mv` leaves the gitdir pointer stale.
191
+
192
+ ## Example: clean session
193
+
194
+ ```
195
+ user: /forge multi-agent
196
+ forge → orchestrating
197
+ orchestrating: bootstrap OK → session_id=a1b2c3d4 → anchor=m-9-a1b2c3d4 → worktree created at ../<repo>-worktrees/m-9-a1b2c3d4 → state written
198
+ orchestrating → executing (working dir: ../<repo>-worktrees/m-9-a1b2c3d4, branch: forge/m-9-a1b2c3d4)
199
+ executing: claim files → edit → commit (× N tasks)
200
+ executing → verifying (captures current.human_verified on human sign-off) → reviewing (all inside worktree)
201
+ reviewing → orchestrating (teardown)
202
+ orchestrating: Step 5.0 gate — current.human_verified present → forge_queue_commit → merged → release claims → remove worktree → delete branch
203
+ done.
204
+ ```
205
+
206
+ ## Example: conflict path
207
+
208
+ ```
209
+ orchestrating: forge_queue_commit → conflict { files: [src/auth.ts] }
210
+ orchestrating → debugging { conflicted_files, base_sha, branch }
211
+ debugging: user resolves → signal resolved
212
+ orchestrating: retry forge_queue_commit → merged → cleanup
213
+ ```
214
+
215
+ ## References
216
+
217
+ - ADR-001 — experimental track / opt-in carve-out
218
+ - ADR-002 — worktrees as isolation substrate
219
+ - ADR-003 — MCP server + per-repo SQLite
220
+ - ADR-004 — merge queue (forge_queue_commit status semantics)
221
+ - ADR-005 — session lifecycle (this skill realizes it)
222
+ - `.forge/research/milestone-10.md` — spike findings
223
+ - `bootstrap-checks.md` — bootstrap check matrix + fallback
@@ -0,0 +1,70 @@
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 OR every entry clean (` ` prefix, in-sync with recorded pin) | refuse only on divergent (`+`) / conflicted (`U`) entries — see "Submodule handling" below | Clean pins are safe: every worktree wants the same SHA, so the shared `.git/modules` HEAD never contends. Divergent working state is the real hazard. New worktrees auto-init submodules (SKILL Step 2) |
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
+ | Enforcement canary (advisory) | `bash .claude/hooks/forge-claim-check-doctor.sh` | canary line reports `gate enforces` | **warn, do not refuse** | Proves the claim gate actually denies a foreign claim — catches the inert-gate class (identity read from wrong source / hook unwired) that prereqs can't see |
13
+
14
+ ## Fallback message
15
+
16
+ On any check failure, emit verbatim to user:
17
+
18
+ ```
19
+ M10 worktree mode unavailable: <reason>. Continuing in single-agent mode. See ADR-005 for compatibility matrix.
20
+ ```
21
+
22
+ Replace `<reason>` with the failing check name + observed value (e.g., "Git version 2.45.1 < required 2.48").
23
+
24
+ ## Submodule handling (conditional)
25
+
26
+ Submodules do **not** auto-populate in a fresh worktree — `git worktree add` carries the gitlinks but leaves the directories empty until `git submodule update --init` runs (SKILL Step 2 does this). The historic blanket refusal was over-conservative; the real hazard is two worktrees fighting over the shared `.git/modules` HEAD/index when they point a submodule at *different* SHAs. Pinned third-party deps (e.g. JUCE, nanovg) never trigger that — every worktree converges on the one recorded SHA.
27
+
28
+ Classify `git submodule status` output:
29
+
30
+ | Observed | Meaning | Action |
31
+ |----------|---------|--------|
32
+ | empty | no submodules | pass |
33
+ | every line ` ` (space) prefix | all clean, in-sync with recorded pin | **warn + proceed** — Step 2 will `submodule update --init --recursive` |
34
+ | any line `-` (uninitialized) in main repo | superproject itself hasn't inited | refuse — init in the main checkout first (`git submodule update --init`) |
35
+ | any line `+` (divergent SHA) | working tree ahead/behind recorded pin | refuse — uncommitted submodule movement is the shared-`.git/modules` hazard |
36
+ | any line `U` (merge conflict) | unresolved submodule conflict | refuse |
37
+
38
+ Warn-and-proceed message:
39
+
40
+ ```
41
+ M10: <N> submodule(s) present and clean — proceeding. Each worktree auto-inits them
42
+ (shared .git/modules object store, no re-clone). Ensure builds tolerate the one-time init.
43
+ ```
44
+
45
+ ## State write on refusal
46
+
47
+ ```yaml
48
+ lifecycle:
49
+ worktree_mode: refused
50
+ refused_reason: "<check name>: <observed>"
51
+ refused_at: "{ISO8601}"
52
+ ```
53
+
54
+ ## Check execution order
55
+
56
+ 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.
57
+
58
+ ## Remediation hints
59
+
60
+ | Failing check | User remediation |
61
+ |---------------|------------------|
62
+ | Git version | Upgrade Git to ≥ 2.48 (Homebrew: `brew upgrade git`; apt: backports or PPA) |
63
+ | LFS version | Upgrade Git LFS to ≥ 3.6 OR uninstall if not actually used |
64
+ | Submodule scan | Only fails on uninitialized (`-`), divergent (`+`), or conflicted (`U`) submodules. Run `git submodule update --init --recursive` in the main checkout so every submodule sits at its recorded pin, then retry. Clean pinned submodules are supported |
65
+ | `core.hooksPath` | Either unset (`git config --unset core.hooksPath`) or move hook dir inside repo tree so it resolves under each worktree |
66
+ | 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` |
67
+
68
+ ## Re-running checks
69
+
70
+ 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.
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bash
2
+ # Forge M10 experimental uninstaller.
3
+ # Preserves claims.db by default. Pass --purge to remove it.
4
+ # Usage: bash uninstall.sh [--purge] /path/to/forge-repo
5
+
6
+ set -euo pipefail
7
+
8
+ PURGE=0
9
+ TARGET=""
10
+
11
+ while [[ $# -gt 0 ]]; do
12
+ case "$1" in
13
+ --purge) PURGE=1; shift ;;
14
+ -h|--help) echo "Usage: $0 [--purge] /path/to/forge-repo"; exit 0 ;;
15
+ *) TARGET="$1"; shift ;;
16
+ esac
17
+ done
18
+
19
+ [[ -z "$TARGET" ]] && { echo "ERROR: target repo path required" >&2; exit 2; }
20
+ [[ -d "$TARGET" ]] || { echo "ERROR: target '$TARGET' does not exist" >&2; exit 2; }
21
+
22
+ say() { echo " $*"; }
23
+
24
+ echo "==> M10 experimental uninstaller"
25
+ echo " target: $TARGET"
26
+ [[ $PURGE -eq 1 ]] && echo " mode: --purge (claims.db will be removed)"
27
+ echo
28
+
29
+ # Remove MCP server dir
30
+ if [[ -d "$TARGET/.forge/.mcp-server" ]]; then
31
+ if [[ $PURGE -eq 0 && -f "$TARGET/.forge/.mcp-server/claims.db" ]]; then
32
+ say "preserving claims.db; removing rest of .mcp-server/"
33
+ find "$TARGET/.forge/.mcp-server" -mindepth 1 -not -name claims.db -delete
34
+ else
35
+ rm -rf "$TARGET/.forge/.mcp-server"
36
+ say "removed .forge/.mcp-server/"
37
+ fi
38
+ fi
39
+
40
+ # Remove hooks
41
+ for h in forge-claim-check.sh forge-claim-check-doctor.sh; do
42
+ [[ -f "$TARGET/.claude/hooks/$h" ]] && { rm -f "$TARGET/.claude/hooks/$h"; say "removed hook $h"; }
43
+ done
44
+
45
+ # Remove orchestrating skill
46
+ [[ -d "$TARGET/.claude/skills/orchestrating" ]] && {
47
+ rm -rf "$TARGET/.claude/skills/orchestrating"
48
+ say "removed orchestrating skill"
49
+ }
50
+
51
+ # Strip .mcp.json entry
52
+ if [[ -f "$TARGET/.mcp.json" ]] && jq -e '.mcpServers["forge-orchestrator"]' "$TARGET/.mcp.json" >/dev/null 2>&1; then
53
+ tmp=$(mktemp)
54
+ jq 'del(.mcpServers["forge-orchestrator"])' "$TARGET/.mcp.json" > "$tmp"
55
+ mv "$tmp" "$TARGET/.mcp.json"
56
+ say "removed forge-orchestrator from .mcp.json"
57
+ fi
58
+
59
+ # Strip settings.json hook entry
60
+ if [[ -f "$TARGET/.claude/settings.json" ]]; then
61
+ tmp=$(mktemp)
62
+ jq '.hooks.PreToolUse = ((.hooks.PreToolUse // []) | map(select((.hooks // []) | all(.command != ".claude/hooks/forge-claim-check.sh"))))' \
63
+ "$TARGET/.claude/settings.json" > "$tmp"
64
+ mv "$tmp" "$TARGET/.claude/settings.json"
65
+ say "removed claim-check hook from settings.json"
66
+ fi
67
+
68
+ echo
69
+ echo "==> Done."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-orkes",
3
- "version": "0.62.0",
3
+ "version": "0.63.0",
4
4
  "description": "Set up the Forge meta-prompting framework for Claude Code in your project",
5
5
  "bin": {
6
6
  "create-forge": "./bin/create-forge.js"
@@ -11,7 +11,8 @@
11
11
  },
12
12
  "files": [
13
13
  "bin/",
14
- "template/"
14
+ "template/",
15
+ "experimental/"
15
16
  ],
16
17
  "keywords": [
17
18
  "claude",
@@ -47,13 +47,7 @@
47
47
  "Edit(**/secrets/**)",
48
48
  "Edit(**/.secrets/**)",
49
49
  "Edit(**/*.pem)",
50
- "Edit(**/*.key)",
51
- "Write(**/.env)",
52
- "Write(**/.env.*)",
53
- "Write(**/secrets/**)",
54
- "Write(**/.secrets/**)",
55
- "Write(**/*.pem)",
56
- "Write(**/*.key)"
50
+ "Edit(**/*.key)"
57
51
  ]
58
52
  },
59
53
  "hooks": {