forge-orkes 0.42.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 +138 -1
- package/package.json +1 -1
- package/template/.claude/agents/doc-reviewer.md +115 -0
- package/template/.claude/agents/performance-reviewer.md +138 -0
- package/template/.claude/agents/security-reviewer.md +163 -0
- package/template/.claude/hooks/README.md +39 -0
- package/template/.claude/hooks/block-dangerous-commands.sh +158 -0
- package/template/.claude/hooks/forge-active-skill-guard.sh +44 -0
- package/template/.claude/hooks/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 +1 -1
- package/template/.claude/skills/chief-of-staff/SKILL.md +14 -0
- package/template/.claude/skills/executing/SKILL.md +16 -0
- package/template/.claude/skills/forge/SKILL.md +12 -1
- package/template/.claude/skills/initializing/SKILL.md +4 -0
- package/template/.claude/skills/planning/SKILL.md +13 -1
- package/template/.claude/skills/reviewing/SKILL.md +2 -0
- package/template/.claude/skills/verifying/SKILL.md +19 -8
- package/template/.forge/FORGE.md +16 -3
- package/template/.forge/migrations/0.43.0-safety-policy.md +60 -0
- package/template/.forge/migrations/0.44.0-desire-paths.md +57 -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,7 +28,7 @@ When an architectural decision conflicts with vertical slicing (e.g., a framewor
|
|
|
28
28
|
|
|
29
29
|
## Architecture Decision Record (ADR)
|
|
30
30
|
|
|
31
|
-
**Reserve the ADR number first (cross-worktree safety).** Before authoring a new ADR, allocate its number via the **ID Reservation Protocol** (FORGE.md)
|
|
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
32
|
|
|
33
33
|
Record every significant decision in `.forge/decisions/`:
|
|
34
34
|
|
|
@@ -164,6 +164,7 @@ existing M10 state.
|
|
|
164
164
|
pre-convention `git worktree add`; `git worktree move` it and update the
|
|
165
165
|
record."* Adopt the recorded path as-is regardless (advisory, never a block;
|
|
166
166
|
honors `orchestration.worktree_root`).
|
|
167
|
+
- **Submodules:** if the worktree was created manually (`git worktree add`, not via `orchestrating`) and the repo has submodules, they are empty until inited. Don't build from it blind — `executing`'s Workspace Prerequisites step inits them before the first build; surface this when handing the stream off so it isn't discovered as a mid-build failure.
|
|
167
168
|
- `runtime.worker_sessions` with the M10 session / claim ids
|
|
168
169
|
- `ownership.owned_surfaces`, `shared_surfaces`, and `read_only_surfaces`
|
|
169
170
|
as empty lists if unknown
|
|
@@ -238,11 +239,24 @@ Compare all `active`, `delegated`, and `ready` streams:
|
|
|
238
239
|
reservation, contract, or explicit merge order
|
|
239
240
|
- overlapping `shared_surfaces` requires a named contract owner
|
|
240
241
|
- overlapping `read_only_surfaces` is safe
|
|
242
|
+
- two streams holding the same `runtime.lease.resource` is an exclusive-resource conflict — serialize (see **Exclusive Resource Leases**)
|
|
241
243
|
- missing ownership is a conflict until clarified
|
|
242
244
|
|
|
243
245
|
Record conflicts in both the registry and the affected stream files. Do not let
|
|
244
246
|
workers resolve cross-stream conflicts directly.
|
|
245
247
|
|
|
248
|
+
## Exclusive Resource Leases
|
|
249
|
+
|
|
250
|
+
Surface conflict detection covers **files**. It does not cover a **single exclusive non-file resource** that two concurrent streams both need — a physical test device, a hardware port, a singleton bundle ID, a shared local DB/emulator. Two streams each `xcrun devicectl device install` to the same device, or deploy the same bundle ID, and each **clobbers the other's build**: a stream's device-verify gate opens the app and finds a *sibling's* build, the block under test absent (forge#15, canvaz m-AW01). Files don't collide here, so the surface check passes — and the clobber only shows up at device UAT.
|
|
251
|
+
|
|
252
|
+
Two complementary remedies; prefer (1), fall back to (2):
|
|
253
|
+
|
|
254
|
+
1. **Isolate the resource per stream (no lease needed).** When the resource *can* be made per-stream, do that instead of serializing. The canonical case: derive a **per-worktree bundle-ID suffix** from the worktree anchor (`co.example.app` → `co.example.app.m-AW01`) so every stream's build coexists on one device. This is **project-level config** (the iOS/CMake build reads the anchor from `git branch --show-current`), not Forge core — record it as a constitution/`project.yml` convention for the stack. Isolated → the streams never contend; skip the lease.
|
|
255
|
+
|
|
256
|
+
2. **Lease the resource (when it genuinely can't be split).** Declare exclusive resources in `project.yml` (`orchestration.exclusive_resources: [ios_device, …]`). A stream that needs one records a lease in **its own** stream file — `runtime.lease: {resource, acquired_at, operation}` (single-owner-mutable, the stream's driver writes it; no cross-stream contention). The owning skill (e.g. `verifying`'s device-verify gate) **acquires before** the deploy-and-verify, **releases after** (clears the field). The Chief detects double-claims the same way it detects surface overlap: scan stream files for two holding the same `runtime.lease.resource` → **serialize** them (one waits, or skips its device gate until the resource frees). Until a stream releases, siblings needing that resource queue.
|
|
257
|
+
|
|
258
|
+
Add the lease scan to **Detect Conflicts** (above) and the **Merge Cadence Check**: a held lease is a soft block on the holder's siblings' device gates, never on merge.
|
|
259
|
+
|
|
246
260
|
## Merge Safe Flow
|
|
247
261
|
|
|
248
262
|
Use "merge safe" to identify streams that can land:
|
|
@@ -10,9 +10,25 @@ description: "Build to plan with atomic commits, deviation rules, and context en
|
|
|
10
10
|
- [ ] Context.md locked decisions noted
|
|
11
11
|
- [ ] Constitution.md gates satisfied
|
|
12
12
|
- [ ] Milestone state updated to `status: executing`
|
|
13
|
+
- [ ] Workspace prerequisites met — submodules populated (see below)
|
|
13
14
|
- [ ] Baseline snapshot captured (see below)
|
|
14
15
|
- [ ] Plan anchors re-derived against HEAD (see below)
|
|
15
16
|
|
|
17
|
+
## Workspace Prerequisites
|
|
18
|
+
|
|
19
|
+
Run **first, before the baseline snapshot** — a missing prerequisite makes the baseline's build/test commands fail for reasons that have nothing to do with the plan.
|
|
20
|
+
|
|
21
|
+
**Submodules.** A freshly-created worktree carries submodule gitlinks but leaves the directories **empty** until `git submodule update --init` runs — the first build then fails partway through with a confusing error (a missing third-party target, an empty `external/` dir), not an obvious "submodules uninitialised". `orchestrating` inits submodules when *it* creates the worktree, but a worktree from any other origin (a manual `git worktree add`, a `chief-of-staff` stream, a fresh clone) does not. So assert it here, regardless of how the workspace was created:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# No-op when the repo has no submodules; a fast checkout when objects are already in the shared common git dir.
|
|
25
|
+
if [ -f .gitmodules ] && git submodule status | grep -q '^-'; then
|
|
26
|
+
git submodule update --init --recursive
|
|
27
|
+
fi
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Surface the init if it ran (*"populated N uninitialised submodule(s) before baseline"*). A `+`/`U` line (divergent/conflicted submodule) is **not** an init case — STOP under Rule 4; it signals real submodule movement. (Desire-path: canvaz m-TIDES01 — empty `external/JUCE` failed the first CMake configure mid-task.)
|
|
31
|
+
|
|
16
32
|
## Baseline Snapshot
|
|
17
33
|
|
|
18
34
|
Run **before the first task begins**. Makes failure causality mechanical — no self-assessment.
|
|
@@ -45,7 +45,14 @@ git worktree list --porcelain 2>/dev/null \
|
|
|
45
45
|
- `behind > 0` and the worktree has **no divergent local commits** (`git rev-list --count main..HEAD` is 0) → **fast-forward automatically**: `git merge --ff-only main`. A fast-forward is conflict-free by definition, so do it without asking; report *"pulled {N} commit(s) from main (checkpoint integration)."* Mark the sha seen.
|
|
46
46
|
- `behind > 0` **and** the worktree has diverged → **surface, do not auto-rebase**: *"main advanced on {surfaces} since this worktree branched — rebase onto main? (`git rebase main`)"* — rebase can conflict; the operator decides.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
**Integration flag check (consumer — main checkout).** When `in_main_checkout: true`, the same flag tells the operator's **canonical** checkout that a checkpoint pushed work to `origin/main` it hasn't pulled — the other half of the producer's strand-safe publish (FORGE.md → Stream Integration Checkpoints; forge#13). Without this, local main sits behind origin after every checkpoint and the next local commit silently diverges it. Same event-driven gate:
|
|
49
|
+
|
|
50
|
+
- Read `.git/forge/integration-pending`. Absent, or its sha already seen by this checkout → **skip** (no fetch). No flag → no work.
|
|
51
|
+
- Flag set with an unseen sha → `git fetch origin main` (if a remote), then `behind=$(git rev-list --count main..origin/main 2>/dev/null)` (local `main` vs the freshly-fetched `origin/main`). `behind` is 0 → mark seen, skip.
|
|
52
|
+
- `behind > 0` and local main has **not** diverged (`git rev-list --count origin/main..main` is 0) → **fast-forward automatically** (the producer's strand guard guarantees this is a true ff): `git merge --ff-only origin/main` on `main`. Report *"fast-forwarded local main +{N} from origin (checkpoint integration)."* Mark seen.
|
|
53
|
+
- `behind > 0` **and** local main has diverged → **surface, never auto-merge**: *"local main and origin/main have diverged ({A} local / {B} remote commits) — this shouldn't happen if checkpoints used the strand guard; reconcile with `git fetch` then `git merge origin/main` (inspect first). Do NOT `git pull --ff-only` (it fails on divergence)."* The operator integrates.
|
|
54
|
+
|
|
55
|
+
Cross-machine note: the flag file is machine-local, so a worktree (or the primary checkout) on another laptop relies on the opt-in `forge.worktree_rebase_check` / a periodic fetch instead — eventual, not instant.
|
|
49
56
|
|
|
50
57
|
**Worktree path convention check (advisory — main or worktree).** A recorded worktree path is trusted *verbatim* everywhere (FORGE.md → "Recorded path is authoritative"), so a path created **outside** the convention — a manual `git worktree add ../forge-worktrees/m115`, or pre-convention state — is honored unchallenged by the 1.1a gate and by `chief-of-staff` adoption. This check surfaces that drift. It never blocks and never moves anything.
|
|
51
58
|
|
|
@@ -54,6 +61,10 @@ Cross-machine note: the flag file is machine-local, so a worktree on another lap
|
|
|
54
61
|
- Mismatch → one advisory line: *"recorded worktree path `{path}` for m{id} is not under the convention root `{root}` — likely a manual `git worktree add` or pre-convention state. Fix: `git worktree move {path} {root}/{anchor}` then update the recorded path. (Advisory; honors `orchestration.worktree_root`.)"*
|
|
55
62
|
- Parent dir under the root, or no recorded path → silent. An explicit `orchestration.worktree_root` is honored by construction (the override **is** the root compared against), so a deliberately relocated root never false-warns.
|
|
56
63
|
|
|
64
|
+
**ID reservation preflight (advisory — main or worktree).** The cross-worktree ID Reservation Protocol (FORGE.md, ADR-016) only protects numbers if `.forge/reservations.yml` actually carries them. Two cheap checks, both advisory — never block:
|
|
65
|
+
- **Absent registry, but IDs already exist.** `.forge/reservations.yml` missing *and* the tree already holds allocated IDs (`requirements/*.yml` has `FR-`/`NFR-`/`DEF-`, or `.forge/decisions/` has `ADR-NNN`) → one-time nudge: *"No `reservations.yml` yet, but this project already allocates IDs. Concurrent worktrees will collide on the next number. Create it by reserving your next ID via the protocol (`planning`/`architecting` do this), or backfill existing IDs now."* (No IDs anywhere → silent; the first reservation creates the file lazily.)
|
|
66
|
+
- **Backfill scan (un-reserved landed IDs).** When `reservations.yml` exists, diff the IDs actually landed in the tree (`FR-`/`NFR-`/`DEF-` across `requirements/*.yml`; `ADR-NNN` across `.forge/decisions/`) against the ids recorded in `reservations.yml`. Any landed ID with **no** reservation entry → list them once: *"{N} ID(s) are in the tree but not in `reservations.yml` ({ids}) — landed before the protocol or via an un-reserved path. They are still respected (next = max(reserved, in-tree)+1), but appending backfill entries keeps the registry the single audit trail."* Advisory: surface, don't auto-write — backfilling is the operator's call.
|
|
67
|
+
|
|
57
68
|
**Stream Rollup (active.yml is derived).** If `.forge/streams/` has any stream files, regenerate `.forge/streams/active.yml` from them + active milestones per FORGE.md → Stream Rollup, the same way step 1.0 regenerates `index.yml`. **Run the 1.0 State Rollup (below) first** so `index.yml` is fresh — the Stream Rollup reads active milestones from it for implicit-row coverage, so on a boot where a just-merged milestone promotion lands, a stale `index.yml` would silently drop that milestone's coverage row. `active.yml` is derived — never read it as authoritative without rolling up first, and never hand-edit it. Runs in the main checkout (worktrees never write `active.yml`). No `streams/` dir → skip.
|
|
58
69
|
|
|
59
70
|
**Ready-to-merge nudge (main checkout / streams without checkpoints).** From the just-regenerated `active.yml`, scan for streams with `coordination: ready` or a non-empty `merge_queue` **that are not auto-publishing via checkpoints**. Any present → one line: *"{N} stream(s) ready to merge — run `/chief-of-staff` (merge safe) to integrate."* Pointer only — the cadence logic lives in the Chief's Merge Cadence Check; `forge` never merges. Nothing pending → silent.
|
|
@@ -270,6 +270,10 @@ No library + UI files → *"No component library. Design system or custom?"*
|
|
|
270
270
|
|
|
271
271
|
Present grouped. User confirms/adds/removes.
|
|
272
272
|
|
|
273
|
+
### Step 6.5: Rules Layer
|
|
274
|
+
|
|
275
|
+
The project ships with `.claude/rules/agent-discipline.md` (always-on AI-discipline floor). Point the user at the **rules layer** (`.claude/rules/README.md`) for codifying conventions that should auto-load by file path — cheaper than the constitution, lighter than a skill (ADR-018). If a stack was detected (Laravel/PHP, Python, Vue), offer the **opt-in convention pack**: *"Detected {stack}. Install the convention skills + path-scoped rules? `bash experimental/conventions/install.sh {stack}`"* (skip if the pack isn't present in this install). Stack conventions are opt-in — never auto-installed into the stack-agnostic core.
|
|
276
|
+
|
|
273
277
|
## Greenfield
|
|
274
278
|
|
|
275
279
|
### Step 1: Basics
|
|
@@ -76,7 +76,7 @@ Resolve current milestone ID from `.forge/state/index.yml` (active milestone) or
|
|
|
76
76
|
|
|
77
77
|
If missing, create from `.forge/templates/requirements.yml`:
|
|
78
78
|
1. Extract from user description + research
|
|
79
|
-
2. IDs: FR-001, FR-002... **Globally unique across milestones.** Allocate via the **ID Reservation Protocol** (FORGE.md): 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.
|
|
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)
|
|
@@ -207,6 +207,18 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
|
|
|
207
207
|
</task>
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
#### Overlay / Popover must_haves (when a plan adds a floating surface)
|
|
211
|
+
|
|
212
|
+
When a plan introduces an **overlay, popover, sheet, anchored panel, or any floating surface** (especially one anchored to a moving element like a canvas block), specifying its *content* (rows, keys, payload) is **not enough** — the *behavior* questions below only surface when a human touches the running UI, each costing a full build → deploy → UAT round. Add a `must_haves.truths` entry for every item that applies, phrased as a user-observable behavior:
|
|
213
|
+
|
|
214
|
+
1. **Placement + edge clamp** — where does it appear relative to its anchor, and does it stay fully on-screen when the anchor is near a viewport/canvas edge?
|
|
215
|
+
2. **Drag-tracking** — if the anchor can move (drag, scroll, reflow), does the overlay follow it?
|
|
216
|
+
3. **Live param re-sync** — while open, does it re-read the anchor's state so it reflects live changes (not a stale snapshot from open time)?
|
|
217
|
+
4. **Z-order + tap-order** — draw order *and* hit-test order versus every other overlay/popover that can be open in the same surface.
|
|
218
|
+
5. **Open/dismiss lifecycle** — what opens it, what dismisses it, and what happens if another overlay opens while it is already open?
|
|
219
|
+
|
|
220
|
+
Treat this as a checklist, not a template: drop the items that don't apply (a static centered modal has no anchor, so 1–3 collapse to "centered + on-screen"). The point is to decide each at plan time rather than discover it at device UAT. (Desire-path: canvaz m-SEQ01 — six overlay-behavior gaps, 5+ deploy-and-retest rounds.)
|
|
221
|
+
|
|
210
222
|
#### Integration Checkpoints (merge often and early)
|
|
211
223
|
|
|
212
224
|
Mark the phases where a verified slice should **land on main**, so a stream integrates continuously instead of batching at milestone end (FORGE.md → Stream Integration Checkpoints). Set `integration_checkpoint: true` in the plan frontmatter for those phases.
|
|
@@ -56,6 +56,8 @@ File lists per subagent (paths, not globs).
|
|
|
56
56
|
|
|
57
57
|
Fresh-context subagents with file list + tech stack. **Pass `model` param** from `project.yml` model routing (`models.skills.reviewing` → `models.default` → parent). Display: *"Spawning reviewers with model: {model} (from {source})"*
|
|
58
58
|
|
|
59
|
+
The `reviewer` agent (3 modes below) is the **breadth pass** that gates the health report. For high-risk diffs (auth, payments, data-access, hot paths, public-facing docs) you may **additionally** spawn the confidence-gated specialists — `security-reviewer`, `performance-reviewer`, `doc-reviewer` (ADR-018) — for defensible single-axis depth. They report only Confidence ≥ 8 (security requires a concrete exploit scenario), so their output folds into the breadth findings without noise. Optional and additive — skip for low-risk changes.
|
|
60
|
+
|
|
59
61
|
### Part 1: Security Audit
|
|
60
62
|
|
|
61
63
|
| # | Category | Checks |
|
|
@@ -110,6 +110,8 @@ Runs AFTER code-level verification commands pass. Skipped if no `e2e:true` stori
|
|
|
110
110
|
5. **Hash drift check** (run BEFORE prompting, every gate invocation): for each `e2e:true` FR with `validated: true`, recompute hash from current `observable_outcome`. If it differs from stored `observable_outcome_hash` → set `validated: false`, clear hash. Note auto-reset in verification report. Then prompt that FR as unvalidated.
|
|
111
111
|
6. Write per-FR validation outcomes into the verification report under section "E2E Validation".
|
|
112
112
|
|
|
113
|
+
**Exclusive-resource lease (concurrent streams + a shared physical device).** If this gate's manual walk needs an exclusive resource declared in `project.yml` `orchestration.exclusive_resources` (a physical device, a singleton emulator) **and** other streams are active, acquire the lease before deploying and release it after — otherwise a sibling stream's `device install` clobbers this build mid-walk and you verify the wrong binary (forge#15). Acquire = write `runtime.lease: {resource, acquired_at, operation: e2e-validation}` to this stream's file; the resource is busy → queue (or skip the device gate with a recorded reason) until it frees. Release = clear the field once the walk completes. Single checkout / no declared exclusive resources → no lease, proceed. See chief-of-staff → **Exclusive Resource Leases**; prefer per-stream isolation (bundle-ID suffix) over leasing when the resource can be split.
|
|
114
|
+
|
|
113
115
|
### Gate behavior
|
|
114
116
|
|
|
115
117
|
- **Advisory, not blocking.** Verifying still passes even if no stories validated — the hard gate is in `testing` skill author-mode (phase 16). This gate's job is to surface + record, not block.
|
|
@@ -305,15 +307,24 @@ Realizes producer-side continuous integration (FORGE.md → Stream Integration C
|
|
|
305
307
|
- the verdict is PASSED — never publish unverified work. (`current.human_verified` gates milestone **close**, *not* per-checkpoint publish: a non-final checkpoint phase publishes on PASS alone; the milestone's **final** checkpoint coincides with close, where the Human Verification Gate applies — so in practice only the last checkpoint also requires `human_verified`.)
|
|
306
308
|
- the session is a **worktree** (`git rev-parse --git-dir` ≠ `--git-common-dir`). In a single checkout, skip — there is no main to fast-forward into.
|
|
307
309
|
|
|
308
|
-
|
|
310
|
+
**Strand guard — never push past the operator's local main (forge#13).** `git push origin HEAD:main` is fast-forward-only *relative to `origin/main`* — but `origin/main` is **not** the operator's canonical checkout. The local `main` ref (shared across this machine's worktrees via the common git dir) can carry commits that were never pushed — another stream's merge, a framework bump, the state-sync commits from step 4. If you push the worktree tip onto an `origin/main` that is *behind* local main, origin fast-forwards but **local main is left diverged** (it has commits origin's new tip lacks, and vice versa). The closing report then mis-reads this as "local main is behind — `git pull`", and `git pull --ff-only` *fails* on divergence while a bare `git pull` makes a surprise merge commit — the exact failure the merge discipline exists to prevent. So gate the push on local main being in the publish's direct line:
|
|
309
311
|
|
|
310
312
|
```bash
|
|
311
|
-
|
|
312
|
-
#
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
git fetch origin main 2>/dev/null || true # refresh origin/main (no-op without a remote)
|
|
314
|
+
# Publish is strand-safe only when local main is an ANCESTOR of this worktree's HEAD —
|
|
315
|
+
# i.e. local main has no commits this checkpoint doesn't already contain.
|
|
316
|
+
if git rev-parse --verify -q main >/dev/null 2>&1 \
|
|
317
|
+
&& ! git merge-base --is-ancestor main HEAD; then
|
|
318
|
+
: "STRANDED — local main carries commits this checkpoint omits. Do NOT push."
|
|
319
|
+
fi
|
|
315
320
|
```
|
|
316
321
|
|
|
317
|
-
- **
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
- **Strand-safe** (local main is an ancestor of HEAD, or no local `main` ref exists) → publish **fast-forward only**:
|
|
323
|
+
```bash
|
|
324
|
+
git push origin HEAD:main # remote present; git refuses a non-ff push (diverged origin surfaced, never forced)
|
|
325
|
+
# no remote → fast-forward the local main ref where possible, else report ready-to-ff
|
|
326
|
+
```
|
|
327
|
+
- **Success** → set the integration flag for **all** of this machine's checkouts (sibling worktrees *and* the primary main checkout): write `.git/forge/integration-pending` (in the git common dir) with the new main sha, this stream/milestone, and the shared surfaces this phase changed. Because local main was an ancestor, advancing it is a **true fast-forward** — report: *"Checkpoint published — phase {N} fast-forwarded onto main. Local main is now behind origin by this slice; it auto-fast-forwards on the next `/forge` boot, or `git merge --ff-only origin/main` from the primary checkout now."* (Here, and only here, is local main genuinely *behind* — the ff advice is correct.)
|
|
328
|
+
- **Rejected (non-ff — `origin/main` itself diverged)** → **do not force.** Surface: *"origin/main has advanced since this worktree branched — publish needs a manual merge/rebase first."* Leave the work on the branch; the Chief's Merge Cadence Check / a human resolves it.
|
|
329
|
+
- **Stranded** (local main has commits this checkpoint omits) → **do not push.** Integrating through origin would diverge local main. Surface: *"main has advanced locally ({N} commit(s) not in this checkpoint) since this worktree branched. Publishing direct to origin would strand your local main (diverged, not behind). Integrate through local main instead: from the primary checkout `git merge {branch}` (or rebase this worktree onto main, then publish). Do NOT `git pull --ff-only` — it fails on divergence."* Leave the work on the branch; a human integrates.
|
|
330
|
+
- Never auto-resolve a conflict, never force-push, never auto-rebase.
|
package/template/.forge/FORGE.md
CHANGED
|
@@ -44,9 +44,9 @@ Use ordinary Quick/Standard/Full tiers inside a stream. Use Project Chief / Chie
|
|
|
44
44
|
Integration is **event-driven, not polled**: a stream publishes its work to main at plan-marked phase boundaries, and a lightweight flag tells sibling worktrees there is something to pull. No flag set → boot does no integration work.
|
|
45
45
|
|
|
46
46
|
- **Checkpoints (plan-marked).** `planning` marks selected phases as integration checkpoints (`integration_checkpoint: true` in the plan frontmatter) — the points where a verified slice should land on main. Default heuristic: the last phase, plus any phase another stream/phase depends on. Advisory — planning proposes, the operator confirms. **The mark is the opt-in:** a plan with no marked checkpoint never auto-publishes, so existing/unmarked work is unaffected.
|
|
47
|
-
- **Producer — publish on verified checkpoint (fast-forward only).** When `verifying` returns PASS for a checkpoint phase *and the session is a worktree*, it integrates that phase to main with a **fast-forward-only** push: `git push origin HEAD:main`. Git rejects a non-ff push — that *is
|
|
47
|
+
- **Producer — publish on verified checkpoint (fast-forward only, strand-guarded).** When `verifying` returns PASS for a checkpoint phase *and the session is a worktree*, it integrates that phase to main with a **fast-forward-only** push: `git push origin HEAD:main`. Git rejects a non-ff push (vs `origin/main`) — that surfaces a diverged *origin*, never force-pushed. But ff-vs-origin is not enough on its own: `origin/main` is not the operator's canonical checkout, and the local `main` ref can carry commits origin never saw (another stream's merge, a framework bump, state-sync commits). Pushing past a *behind* origin then leaves **local main diverged, not behind** — and the naive "you're behind, `git pull --ff-only`" advice *fails* on divergence (forge#13). So the producer **strand-guards** first: it publishes only when local `main` is an **ancestor of the worktree HEAD** (no local commits this checkpoint omits). Stranded (local main has such commits) → it does **not** push; it surfaces "integrate through local main first" and leaves the work on the branch. On a strand-safe success it sets the **integration flag**. (No remote → fast-forward local `main` where possible, else record ready-to-ff for the primary checkout.)
|
|
48
48
|
- **Flag (cheap, set by the producer).** A marker in the shared git common dir — `.git/forge/integration-pending` — records the new main sha, the publishing stream, and the shared surfaces that changed. It is shared across all of a repo's worktrees on a machine, so any boot reads it for free; absent or already-seen → skip. The `main` ref is the source of truth the marker points at. Cross-machine (the marker is machine-local) falls back to the opt-in `forge.worktree_rebase_check` / a periodic fetch — eventual, not instant.
|
|
49
|
-
- **Consumer — auto fast-forward, else prompt.** `forge` boot in a worktree
|
|
49
|
+
- **Consumer — auto fast-forward, else prompt (worktrees *and* the primary main checkout).** `forge` boot checks the flag in **both** checkout kinds. In a **worktree**: flag set (main advanced past this worktree's merge-base) and no divergent local commits → **fast-forward automatically** (`git merge --ff-only main`); diverged → **surface a rebase prompt** (never auto-rebase). In the **primary main checkout**: the strand guard guarantees local main is a clean ff of the new `origin/main`, so boot **fast-forwards local main automatically** (`git merge --ff-only origin/main`) — this is what keeps the operator's canonical checkout in lockstep with origin after every checkpoint, instead of drifting behind until the next local commit diverges it. Flag not set → no fetch, no scan, continue boot.
|
|
50
50
|
|
|
51
51
|
### Worktree Convention (base)
|
|
52
52
|
|
|
@@ -151,6 +151,14 @@ Skills load only when invoked. Active context is `.forge/context.md`; consult
|
|
|
151
151
|
`.forge/context-archive.md` only when older decisions matter. Skill details stay
|
|
152
152
|
on demand. Base context ~300 lines.
|
|
153
153
|
|
|
154
|
+
### Rules Layer (`.claude/rules/`)
|
|
155
|
+
Three policy weights, lightest-first (ADR-018):
|
|
156
|
+
- **`constitution.md`** — always-loaded architectural **gates** (heavy, project-wide, hard rules; size-gated 10 KB).
|
|
157
|
+
- **`.claude/rules/`** — lightweight ambient guidance. Always-on (no frontmatter, loads every session) or path-scoped (`paths:` glob frontmatter, auto-attaches only when editing a matching file — **zero context cost until you touch that code**).
|
|
158
|
+
- **Skills** — heavy, opt-in, loaded by task description.
|
|
159
|
+
|
|
160
|
+
Core ships one always-on rule: **`agent-discipline.md`** (the stack-agnostic AI failure-mode floor; restates the everyday version of the executor's Deviation Rules). **Stack-specific** path-scoped rules (Laravel/Vue/Python) and convention skills ship in the **opt-in `experimental/conventions/` pack**, not core — keeping the base stack-neutral. Authoring: keep each rule under ~20 lines, imperative-first; if it grows past ~25 lines it is a skill in disguise. See `.claude/rules/README.md`.
|
|
161
|
+
|
|
154
162
|
## Model Routing
|
|
155
163
|
|
|
156
164
|
Configure in `project.yml`:
|
|
@@ -179,8 +187,13 @@ models:
|
|
|
179
187
|
| `planner` | Planning + constitutional gates | Read + Write (plan files) | Planning |
|
|
180
188
|
| `executor` | Building + deviation rules | All dev tools | Execution |
|
|
181
189
|
| `verifier` | Goal-backward verification | Read + Bash (tests) | Verification |
|
|
182
|
-
| `reviewer` | Security + architecture + refactoring | Read, Bash, Grep, Glob | Reviewing |
|
|
190
|
+
| `reviewer` | Security + architecture + refactoring (breadth pass, gates the health report) | Read, Bash, Grep, Glob | Reviewing |
|
|
183
191
|
| `tester` | E2E/integration authoring + suite analysis | Dual-mode: author (Write/Edit/Bash/git) · analyst (Read/Glob/Grep/Bash read-only) | Testing skill |
|
|
192
|
+
| `security-reviewer` | Confidence-gated security depth (≥8, exploit-scenario required) | Read, Grep, Glob, Bash | Optional deep-dive (from `reviewing` or direct) |
|
|
193
|
+
| `performance-reviewer` | Real bottlenecks only (Impact = frequency × cost; ≥8 + ≥Medium) | Read, Grep, Glob, Bash | Optional deep-dive |
|
|
194
|
+
| `doc-reviewer` | Docs-vs-source accuracy (≥8; Blocker/Major/Minor) | Read, Grep, Glob, Bash | Optional deep-dive |
|
|
195
|
+
|
|
196
|
+
The three specialist reviewers **complement** the generalist `reviewer` — single-axis, defensible depth, read-only. They are pulled in deliberately (the `reviewing` skill can spawn them, or invoke directly); the 3-mode `reviewer` remains the breadth pass that gates the milestone health report.
|
|
184
197
|
|
|
185
198
|
## Project Init (First Run)
|
|
186
199
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Migration Guide: Safety & Policy Hardening (Forge 0.43.0)
|
|
2
|
+
|
|
3
|
+
Forge 0.43.0 adds two layers ported from a comparison against the Sendmarc internal framework:
|
|
4
|
+
|
|
5
|
+
- **[ADR-017](https://github.com/Attuned-Media/forge/blob/main/docs/decisions/ADR-017-deterministic-safety-guardrails.md)** — deterministic safety-guardrail hooks (`block-dangerous-commands`, `scan-secrets`, `protect-files`, `warn-large-files`), `format-on-save`, `permissions.deny` for secrets, and a hook test harness.
|
|
6
|
+
- **[ADR-018](https://github.com/Attuned-Media/forge/blob/main/docs/decisions/ADR-018-lightweight-policy-layer.md)** — the `.claude/rules/` layer (always-on `agent-discipline` rule), confidence-gated specialist reviewer agents, and an opt-in `experimental/conventions/` pack.
|
|
7
|
+
|
|
8
|
+
**Mostly automatic, backward-compatible.** Running `npx forge-orkes@latest upgrade` does the heavy lifting: it additively installs the guardrail hooks and the always-on rule, syncs the specialist agents, and merges the hook wiring + `deny` globs into your `settings.json` — **without** removing any hooks/rules you added (m10's `forge-claim-check` and pack-installed stack rules are preserved). Nothing breaks if you do nothing; the guardrails simply aren't active until the settings carry them.
|
|
9
|
+
|
|
10
|
+
## Detection
|
|
11
|
+
|
|
12
|
+
Prints `MIGRATE` when the project is missing the safety hooks. Silent + exit 0 once present.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
[ -d .forge ] || exit 0 # not a Forge project
|
|
16
|
+
[ -f .claude/hooks/block-dangerous-commands.sh ] && exit 0 # already migrated → silent
|
|
17
|
+
echo "MIGRATE — run 'npx forge-orkes@latest upgrade' to install the safety guardrails (ADR-017) + rules layer (ADR-018)"
|
|
18
|
+
exit 0
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Migration steps
|
|
22
|
+
|
|
23
|
+
### 1. Upgrade
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx forge-orkes@latest upgrade
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This installs/refreshes `.claude/hooks/` (guardrails + `tests/`), adds `.claude/rules/agent-discipline.md`, syncs the `security/performance/doc-reviewer` agents, `chmod +x`'s the hook scripts, and additively merges the hook groups + secret-`deny` globs into `.claude/settings.json` (idempotent — safe to re-run).
|
|
30
|
+
|
|
31
|
+
### 2. Ensure `jq` is installed
|
|
32
|
+
|
|
33
|
+
The guard hooks parse tool JSON with `jq` and **fail closed** (block with an install message) without it. It is already a Forge prerequisite:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
jq --version || brew install jq # macOS
|
|
37
|
+
jq --version || sudo apt install jq # Debian/Ubuntu
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 3. Verify
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
.claude/hooks/tests/run.sh # hook test suite — expect all green
|
|
44
|
+
ls -l .claude/hooks/*.sh # expect -rwxr-xr-x (executable)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If the scripts aren't executable (a copy can strip the bit): `chmod +x .claude/hooks/*.sh`.
|
|
48
|
+
|
|
49
|
+
## Notes & opt-outs
|
|
50
|
+
|
|
51
|
+
- **Protected-branch pushes are blocked by default** (`main`, `master`, + `git init.defaultBranch`). If a repo legitimately commits to `main`, set `CLAUDE_PROTECTED_BRANCHES=""` (empty) in your shell or `settings.json` `env` to disable *that check only* — force-push/filesystem/db/publish guards stay on. Add extra branches with `CLAUDE_PROTECTED_BRANCHES="main,develop,staging"`.
|
|
52
|
+
- **Editing hooks/settings** is blocked/prompted by `protect-files`. In a project you re-sync hooks via `/upgrading`, not by hand. To author them deliberately, set `FORGE_ALLOW_HOOK_EDITS=1`.
|
|
53
|
+
- **Describing a dangerous command in a commit message** (e.g. the literal text `git push --force`) can trip `block-dangerous-commands`, since it greps the whole Bash command string. Reword, or approve the override.
|
|
54
|
+
- **Opt-in stack conventions:** if your project is Laravel/PHP, Python, or Vue, install the convention skills + path-scoped rules deliberately — they are not in the base template (Forge core is stack-agnostic):
|
|
55
|
+
```bash
|
|
56
|
+
bash experimental/conventions/install.sh # all stacks
|
|
57
|
+
bash experimental/conventions/install.sh laravel # one stack
|
|
58
|
+
```
|
|
59
|
+
(Delivered via the dev repo / `upgrading` experimental route, like m10 — not the npm tarball.)
|
|
60
|
+
- **Specialist reviewers** are optional depth — the `reviewing` skill can spawn them for high-risk diffs, or invoke `security-reviewer` / `performance-reviewer` / `doc-reviewer` directly. The 3-mode `reviewer` is unchanged.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Migration Guide: Desire-Path Hardening (Forge 0.44.0)
|
|
2
|
+
|
|
3
|
+
Forge 0.44.0 batches six desire-path framework fixes (forge#11–#16) surfaced from real multi-worktree usage. **It is backward-compatible** — five of the six are skill/prose behaviour changes that take effect the moment the framework files are synced, with nothing to migrate per project. The only mechanical change is the active-skill PreToolUse hook (forge#12), and `npx forge-orkes upgrade` applies it for you.
|
|
4
|
+
|
|
5
|
+
## What changed (no action needed)
|
|
6
|
+
|
|
7
|
+
- **#11** — the ID Reservation Protocol is now a **blocking gate** in `planning`/`architecting`, plus a new `forge` boot preflight (absent-`reservations.yml` nudge + un-reserved-ID backfill scan). Advisory; surfaces, never blocks.
|
|
8
|
+
- **#13** — checkpoint publish (`verifying`) gains a **strand guard**: it pushes `origin HEAD:main` only when local `main` is an ancestor of the worktree HEAD, so a checkpoint can't leave your canonical main diverged. `forge` boot now also fast-forwards the **primary** main checkout from origin after a checkpoint.
|
|
9
|
+
- **#14** — `executing` Step 0 now inits git submodules for worktrees from **any** origin (not just `orchestrating`-created ones) before the first build.
|
|
10
|
+
- **#15** — `chief-of-staff` gains **Exclusive Resource Leases** (device/port/singleton) and `verifying`'s device gate acquires/releases them.
|
|
11
|
+
- **#16** — `planning` adds a block-anchored **overlay/popover must_have checklist**.
|
|
12
|
+
|
|
13
|
+
These ride in on the skill files synced by the upgrade. No per-project state changes.
|
|
14
|
+
|
|
15
|
+
## The one mechanical change (auto-applied): active-skill hook (#12)
|
|
16
|
+
|
|
17
|
+
The inline PreToolUse(Write\|Edit) "No active skill" guard in `.claude/settings.json` is replaced by `.claude/hooks/forge-active-skill-guard.sh`, which also allows edits when a milestone is durably `current.status: executing` (so a cleared marker no longer blocks a legitimate executing session). On upgrade, `create-forge.js` **strips the legacy inline guard** from your `settings.json` and the additive hooks sync installs + `chmod +x`'s the new script. Idempotent — re-running upgrade is a no-op.
|
|
18
|
+
|
|
19
|
+
## Detection
|
|
20
|
+
|
|
21
|
+
Prints `MIGRATE` only if a project still carries the **legacy inline** active-skill guard in `settings.json` (i.e. the new installer hasn't run yet). Silent + exit 0 once migrated.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
[ -d .forge ] || exit 0 # not a Forge project
|
|
25
|
+
[ -f .claude/settings.json ] || exit 0 # no settings → nothing to migrate
|
|
26
|
+
if grep -q 'if \[ ! -f' .claude/settings.json && grep -q 'No active skill' .claude/settings.json; then
|
|
27
|
+
echo "MIGRATE — settings.json still has the inline active-skill guard; re-run 'npx forge-orkes upgrade' to install forge-active-skill-guard.sh (forge#12)"
|
|
28
|
+
fi
|
|
29
|
+
exit 0
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Manual migration (only if Detection prints MIGRATE)
|
|
33
|
+
|
|
34
|
+
Normally unnecessary — the installer does this. If you hand-merge settings, replace the inline `PreToolUse` "Write|Edit" guard command:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
if [ ! -f "$(git rev-parse --show-toplevel 2>/dev/null)/.forge/.active-skill" ]; then echo "[Forge] No active skill. ..." >&2; exit 2; fi
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
with the script call, and ensure the script is present + executable:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# In .claude/settings.json, the PreToolUse Write|Edit hook command becomes:
|
|
44
|
+
# bash "$CLAUDE_PROJECT_DIR/.claude/hooks/forge-active-skill-guard.sh"
|
|
45
|
+
chmod +x .claude/hooks/forge-active-skill-guard.sh
|
|
46
|
+
git add .claude/settings.json .claude/hooks/forge-active-skill-guard.sh
|
|
47
|
+
git commit -m "chore(forge): migrate active-skill guard to script (0.44.0, forge#12)"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Validation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
[ -x .claude/hooks/forge-active-skill-guard.sh ] && echo "OK: guard script present + executable" || echo "FAIL: guard script missing/not executable"
|
|
54
|
+
grep -q 'forge-active-skill-guard.sh' .claude/settings.json && echo "OK: settings wired to script" || echo "FAIL: settings not wired"
|
|
55
|
+
grep -q 'if \[ ! -f' .claude/settings.json && echo "WARN: legacy inline guard still present" || echo "OK: legacy inline guard removed"
|
|
56
|
+
echo "validation complete"
|
|
57
|
+
```
|