forge-orkes 0.77.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-forge.js +27 -0
- package/experimental/m10/README.md +4 -4
- package/experimental/m10/install.sh +10 -9
- package/experimental/m10/source/hooks/protect-primary-checkout.sh +149 -0
- package/package.json +1 -1
- package/template/.claude/hooks/forge-context-migrate.sh +297 -0
- package/template/.claude/hooks/forge-size-gate.sh +175 -0
- package/template/.claude/hooks/forge-state-rollup.sh +135 -2
- package/template/.claude/hooks/protect-primary-checkout.sh +149 -0
- package/template/.claude/hooks/skill-gate-tiers.txt +6 -0
- package/template/.claude/hooks/tests/forge-context-migrate.test.sh +166 -0
- package/template/.claude/hooks/tests/forge-size-gate.test.sh +132 -0
- package/template/.claude/hooks/tests/protect-primary-checkout.test.sh +147 -0
- package/template/.claude/settings.json +14 -1
- package/template/.claude/skills/discussing/SKILL.md +30 -31
- package/template/.claude/skills/executing/SKILL.md +3 -3
- package/template/.claude/skills/forge/SKILL.md +112 -170
- package/template/.claude/skills/forge/references/manual-fallback-procedures.md +54 -0
- package/template/.claude/skills/initializing/SKILL.md +20 -1
- package/template/.claude/skills/planning/SKILL.md +11 -9
- package/template/.claude/skills/researching/SKILL.md +2 -0
- package/template/.claude/skills/reviewing/SKILL.md +17 -0
- package/template/.claude/skills/upgrading/SKILL.md +22 -2
- package/template/.claude/skills/verifying/SKILL.md +2 -2
- package/template/.forge/FORGE.md +81 -90
- package/template/.forge/bin/new-worktree.sh +61 -0
- package/template/.forge/bin/tests/new-worktree.test.sh +70 -0
- package/template/.forge/checks/forge-jarvis-awareness.sh +46 -2
- package/template/.forge/checks/forge-jarvis-relay.sh +64 -16
- package/template/.forge/checks/tests/forge-jarvis-awareness.test.sh +58 -0
- package/template/.forge/checks/tests/forge-jarvis-relay.test.sh +50 -0
- package/template/.forge/git-hooks/pre-commit +57 -0
- package/template/.forge/git-hooks/tests/pre-commit.test.sh +97 -0
- package/template/.forge/migrations/0.78.0-weld-primary-checkout.md +66 -0
- package/template/.forge/migrations/0.80.0-context-md-sharding.md +158 -0
- package/template/.forge/migrations/0.80.0-size-gate-wiring.md +145 -0
- package/template/.forge/templates/constitution.md +1 -1
- package/template/.forge/templates/context-milestone.md +30 -0
- package/template/.forge/templates/slice-runner/config.yml +7 -1
- package/template/.forge/templates/state/desire-path.yml +3 -0
- package/experimental/m10/source/hooks/forge-branch-guard.sh +0 -61
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Migration Guide: Size gate wired to boot (Forge 0.80.0)
|
|
2
|
+
|
|
3
|
+
Applies to projects upgrading to 0.80.0 or later. The size-gate check
|
|
4
|
+
(`.claude/hooks/forge-size-gate.sh`, issue #37) — which warns when a
|
|
5
|
+
context-engineering artifact (FORGE.md, context.md, a plan, a skill file, …)
|
|
6
|
+
crosses its declared size gate — is now invoked mechanically by the same
|
|
7
|
+
`SessionStart` hook that runs `forge-state-rollup.sh`. A boot session can no
|
|
8
|
+
longer skip the check and let an artifact silently grow past its gate until
|
|
9
|
+
someone notices by hand.
|
|
10
|
+
|
|
11
|
+
`/upgrading` ships the new hook file (`.claude/hooks/forge-size-gate.sh`) like
|
|
12
|
+
any other framework file. It does **not** wire the `SessionStart` invocation,
|
|
13
|
+
because `.claude/settings.json` is a **user-owned file** — upgrades never
|
|
14
|
+
overwrite it (FORGE.md → State Ownership, "Stable shared" class notes
|
|
15
|
+
settings.json is propagated only via explicit installer steps, never blanket
|
|
16
|
+
overwrite). Without this migration's one manual step, the hook ships but is
|
|
17
|
+
never invoked: the executable exists, nothing mechanically triggers it.
|
|
18
|
+
|
|
19
|
+
Projects that ran `initializing` fresh on 0.80.0+ (or already carry the
|
|
20
|
+
`forge-size-gate` reference in their `SessionStart` block from an earlier
|
|
21
|
+
partial adoption) need nothing further.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
1. On the new version's framework files (`npx forge-orkes upgrade`, or the
|
|
26
|
+
`upgrading` skill) — so `.claude/hooks/forge-size-gate.sh` is present and
|
|
27
|
+
executable.
|
|
28
|
+
2. Already migrated to 0.77.0's `forge-state-rollup.sh` SessionStart wiring
|
|
29
|
+
(`.forge/migrations/0.77.0-state-rollup-executable.md`) — this guide
|
|
30
|
+
assumes that hook block already exists in `.claude/settings.json` and
|
|
31
|
+
extends it, rather than creating a `SessionStart` block from scratch. If
|
|
32
|
+
that migration hasn't run yet, do it first.
|
|
33
|
+
3. Working tree clean or changes committed — this migration edits
|
|
34
|
+
`.claude/settings.json`, a tracked file.
|
|
35
|
+
|
|
36
|
+
## Detection
|
|
37
|
+
|
|
38
|
+
Keyed on **the executable existing while the SessionStart wiring is
|
|
39
|
+
absent** — the shipped-but-unwired gap. Uses `jq` to inspect
|
|
40
|
+
`.claude/settings.json`'s `hooks.SessionStart` array for an entry referencing
|
|
41
|
+
`forge-size-gate`; degrades to a plain-text grep if `jq` is not installed.
|
|
42
|
+
Silent in a non-Forge / non-git tree, silent once the hook is wired:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
46
|
+
[ -x .claude/hooks/forge-size-gate.sh ] || exit 0
|
|
47
|
+
|
|
48
|
+
settings=".claude/settings.json"
|
|
49
|
+
[ -f "$settings" ] || {
|
|
50
|
+
echo "MIGRATE — forge-size-gate.sh is present but $settings does not exist; add it with the SessionStart hook below"
|
|
51
|
+
exit 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if command -v jq >/dev/null 2>&1; then
|
|
55
|
+
wired="$(jq -r '
|
|
56
|
+
(.hooks.SessionStart // []) as $entries
|
|
57
|
+
| [$entries[].hooks[]?.command // "" | select(contains("forge-size-gate"))]
|
|
58
|
+
| length > 0
|
|
59
|
+
' "$settings" 2>/dev/null)"
|
|
60
|
+
if [ "$wired" != "true" ]; then
|
|
61
|
+
echo "MIGRATE — $settings has no SessionStart hook referencing forge-size-gate.sh; the executable shipped but nothing mechanically triggers it"
|
|
62
|
+
fi
|
|
63
|
+
else
|
|
64
|
+
if ! grep -q "forge-size-gate" "$settings" 2>/dev/null; then
|
|
65
|
+
echo "MIGRATE — $settings has no reference to forge-size-gate.sh (jq unavailable, ran plain-text check); add the SessionStart hook below"
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Migration steps
|
|
71
|
+
|
|
72
|
+
### 1. Add the size-gate command to the SessionStart hook block
|
|
73
|
+
|
|
74
|
+
Open `.claude/settings.json` and find the `hooks.SessionStart` array entry
|
|
75
|
+
(added by the 0.77.0 migration). Append a second command object to that
|
|
76
|
+
entry's `hooks` array, alongside the existing `forge-state-rollup.sh` command
|
|
77
|
+
— **not** a second `SessionStart` array entry:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"hooks": {
|
|
82
|
+
"SessionStart": [
|
|
83
|
+
{
|
|
84
|
+
"matcher": "startup|resume|clear",
|
|
85
|
+
"hooks": [
|
|
86
|
+
{
|
|
87
|
+
"type": "command",
|
|
88
|
+
"command": "[ -x \"$CLAUDE_PROJECT_DIR/.claude/hooks/forge-state-rollup.sh\" ] && \"$CLAUDE_PROJECT_DIR/.claude/hooks/forge-state-rollup.sh\" || true"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"type": "command",
|
|
92
|
+
"command": "[ -x \"$CLAUDE_PROJECT_DIR/.claude/hooks/forge-size-gate.sh\" ] && \"$CLAUDE_PROJECT_DIR/.claude/hooks/forge-size-gate.sh\" || true"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Both commands run on the same `startup|resume|clear` matcher, in order —
|
|
102
|
+
`forge-state-rollup.sh` first (so caches are fresh), then
|
|
103
|
+
`forge-size-gate.sh` (which only reads files, order doesn't matter
|
|
104
|
+
functionally, but mirroring this repo's own `.claude/settings.json` keeps the
|
|
105
|
+
two guides consistent).
|
|
106
|
+
|
|
107
|
+
If `hooks.SessionStart` already has other entries (e.g. from a project-local
|
|
108
|
+
customization) beyond the one 0.77.0 added, append the `forge-size-gate.sh`
|
|
109
|
+
command object to whichever entry's `hooks` array already contains
|
|
110
|
+
`forge-state-rollup.sh`, rather than creating a new array entry. If no
|
|
111
|
+
`SessionStart` block exists yet at all (0.77.0's migration was skipped), run
|
|
112
|
+
that migration first — this guide only adds a second command, it does not
|
|
113
|
+
construct the block from scratch.
|
|
114
|
+
|
|
115
|
+
The `[ -x ... ] && ... || true` guard means the hook is a no-op — never a
|
|
116
|
+
boot failure — on any checkout where the script is absent or not executable
|
|
117
|
+
(SessionStart hooks are non-blocking; `forge-size-gate.sh` itself also always
|
|
118
|
+
exits 0 — it's an advisory, not a gate that can fail a boot).
|
|
119
|
+
|
|
120
|
+
### 2. Commit the settings change
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git add .claude/settings.json
|
|
124
|
+
git commit -m "chore(forge): wire forge-size-gate SessionStart hook (0.80.0)"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Validation
|
|
128
|
+
|
|
129
|
+
- Start a new session, or run `/clear` — the boot summary line should include
|
|
130
|
+
a `[forge-size-gate] N artifact(s) over their declared gate: …` line if any
|
|
131
|
+
tracked artifact is currently over gate, or no `[forge-size-gate]` line at
|
|
132
|
+
all if everything is within gate (the hook is silent when there's nothing
|
|
133
|
+
to report — it does not print an "all clear" line). Either outcome means
|
|
134
|
+
the hook ran; the absence of a `[forge-size-gate]` clause is not the same
|
|
135
|
+
as the hook slot producing no output at all (compare to before this
|
|
136
|
+
migration, where the SessionStart summary had no size-gate clause because
|
|
137
|
+
nothing invoked the script).
|
|
138
|
+
- `jq '.hooks.SessionStart' .claude/settings.json` prints the array with both
|
|
139
|
+
the `forge-state-rollup` and `forge-size-gate` commands present in the same
|
|
140
|
+
entry's `hooks` list.
|
|
141
|
+
- Re-running the Detection block above now prints nothing (exits silently) —
|
|
142
|
+
the gap is closed.
|
|
143
|
+
- Manually run `.claude/hooks/forge-size-gate.sh` from the repo root — it
|
|
144
|
+
should produce the same output the boot's SessionStart line showed,
|
|
145
|
+
confirming the wired invocation matches a direct run.
|
|
@@ -130,4 +130,4 @@ Minimize framework token footprint. Compress prose, route models by task complex
|
|
|
130
130
|
|
|
131
131
|
## Amending the Constitution
|
|
132
132
|
|
|
133
|
-
Articles can be amended only by explicit user decision during a discuss phase. Amendments are recorded in `.forge/context.md` with rationale. No agent may unilaterally override a constitutional article.
|
|
133
|
+
Articles can be amended only by explicit user decision during a discuss phase. Amendments are recorded in the active milestone's `.forge/context/{id}.md` with rationale (`.forge/context.md` is a derived index, regenerated every boot — never a write target). No agent may unilaterally override a constitutional article.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Per-Milestone Locked Context — `.forge/context/{id}.md`
|
|
2
|
+
|
|
3
|
+
One file per milestone (`m-{N}.md`), holding exactly that milestone's slice
|
|
4
|
+
of what the old monolithic `.forge/context.md` used to carry inline. Same
|
|
5
|
+
append-only convention as before — strike through instead of rewriting when a
|
|
6
|
+
decision changes; add an Amendment Log entry in `.forge/context-log.md`.
|
|
7
|
+
|
|
8
|
+
The rendered `.forge/context.md` (derived index, `forge-state-rollup.sh`)
|
|
9
|
+
prefers a `<!-- summary: ... -->` marker on the line right after the heading
|
|
10
|
+
over the first bullet, when a hand-written one-liner is worth pinning.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
### m-{N} — {Topic} (locked {date})
|
|
15
|
+
|
|
16
|
+
<!-- summary: one-line hand-written summary for the derived index (optional) -->
|
|
17
|
+
|
|
18
|
+
Context: {why this milestone exists / what triggered it}.
|
|
19
|
+
|
|
20
|
+
- **[Topic]**: [Decision]. Reason: [Why this was chosen].
|
|
21
|
+
|
|
22
|
+
## Deferred
|
|
23
|
+
|
|
24
|
+
<!-- Deferred Ideas entries whose text starts with this milestone's id prefix
|
|
25
|
+
(e.g. "m-{N}: ...") land here during migration or ongoing work. -->
|
|
26
|
+
|
|
27
|
+
## Discretion
|
|
28
|
+
|
|
29
|
+
<!-- Discretion Areas entries whose text starts with this milestone's id
|
|
30
|
+
prefix land here during migration or ongoing work. -->
|
|
@@ -15,7 +15,13 @@ per_slice_budget_usd: 25
|
|
|
15
15
|
|
|
16
16
|
# Max agent turns per headless phase (passed to `claude -p --max-turns`). Bounds a
|
|
17
17
|
# single phase; cross-platform (unlike a `timeout` wall-clock, which macOS lacks).
|
|
18
|
-
|
|
18
|
+
# Raised 60 -> 120 (m-54): three consecutive error_max_turns halts on large
|
|
19
|
+
# content-editing phases (context.md shard/migration, FORGE.md trim), each attempt
|
|
20
|
+
# landing real incremental progress before hitting the wall — the phases themselves
|
|
21
|
+
# weren't stuck, 60 was just too tight for multi-file, iteratively-tested work.
|
|
22
|
+
# Budget (per_slice_budget_usd) already gates cost independently, so this has
|
|
23
|
+
# headroom: each halted attempt spent $2-6 of the $25 slice cap, nowhere near it.
|
|
24
|
+
max_turns_per_phase: 120
|
|
19
25
|
|
|
20
26
|
# RESERVED — not yet enforced. Intended as an optional per-phase wall-clock cap in
|
|
21
27
|
# seconds (0 = off); the runner does not read this key yet (macOS has no stock
|
|
@@ -7,6 +7,9 @@ type: "" # recurring_friction | agent_struggle | us
|
|
|
7
7
|
date: "" # ISO 8601 date the observation was made, e.g. "2026-07-23"
|
|
8
8
|
milestone: "" # milestone id, or a short label for non-milestone work (e.g. "quick-task (...)")
|
|
9
9
|
scope: project # project | framework — absent ⇒ project (this repo only; framework ⇒ upstream-eligible per ADR-012)
|
|
10
|
+
# resolves_issue: 26 # OPTIONAL — GitHub issue number this observation's fix resolves. The
|
|
11
|
+
# # close-the-loop check (reviewing) keys off it to surface the issue as
|
|
12
|
+
# # likely-closable once the fix ships. Omit when the observation resolves no issue.
|
|
10
13
|
suggestion_target: "" # the file/skill this observation would change if acted on
|
|
11
14
|
note: >
|
|
12
15
|
What was observed — concrete enough that a future review can judge recurrence
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Forge PreToolUse(Bash) guardrail — protect the shared main checkout's HEAD when
|
|
3
|
-
# parallel git worktrees are active.
|
|
4
|
-
#
|
|
5
|
-
# Blocks the "moved the shared HEAD under other sessions" catastrophe (canvaz, burned
|
|
6
|
-
# twice 2026-06-05): switching branches or hard-resetting in the MAIN checkout while
|
|
7
|
-
# sibling worktrees are branched off it. Orchestration-agnostic — depends only on git
|
|
8
|
-
# worktree state, so it holds whether coordination is M10, Agent Teams, or manual
|
|
9
|
-
# worktrees.
|
|
10
|
-
#
|
|
11
|
-
# Scope is deliberately narrow to avoid false positives:
|
|
12
|
-
# - fires ONLY when >1 worktree exists (single-worktree/solo work is never touched)
|
|
13
|
-
# - fires ONLY in the main checkout (linked worktrees own their own HEAD)
|
|
14
|
-
# - skips any command containing `cd` (can't reliably tell which tree it targets)
|
|
15
|
-
# - allows safe forms: file restore (`git checkout -- <path>`), branch create (`-b`)
|
|
16
|
-
#
|
|
17
|
-
# exit 0 = allow; exit 2 = deny. Override by running the command yourself outside Claude.
|
|
18
|
-
|
|
19
|
-
set -uo pipefail
|
|
20
|
-
|
|
21
|
-
PAYLOAD=$(cat)
|
|
22
|
-
CMD=$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.command // empty' 2>/dev/null) || exit 0
|
|
23
|
-
[ -z "$CMD" ] && exit 0
|
|
24
|
-
|
|
25
|
-
# Only git commands are interesting; and bail on `cd` (target tree is ambiguous).
|
|
26
|
-
case "$CMD" in
|
|
27
|
-
*git*) ;;
|
|
28
|
-
*) exit 0 ;;
|
|
29
|
-
esac
|
|
30
|
-
case "$CMD" in
|
|
31
|
-
*"cd "*) exit 0 ;;
|
|
32
|
-
esac
|
|
33
|
-
|
|
34
|
-
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
35
|
-
|
|
36
|
-
# Single worktree → no shared-HEAD hazard.
|
|
37
|
-
wt_count=$(git worktree list 2>/dev/null | wc -l | tr -d ' ')
|
|
38
|
-
[ "${wt_count:-1}" -le 1 ] && exit 0
|
|
39
|
-
|
|
40
|
-
# Main checkout iff --git-dir == --git-common-dir (linked worktrees differ).
|
|
41
|
-
[ "$(git rev-parse --git-dir 2>/dev/null)" = "$(git rev-parse --git-common-dir 2>/dev/null)" ] || exit 0
|
|
42
|
-
|
|
43
|
-
deny() { echo "[forge-branch-guard] $*" >&2; exit 2; }
|
|
44
|
-
|
|
45
|
-
# Branch switch (checkout/switch to a ref), excluding file-restore (`--`) and create (`-b`).
|
|
46
|
-
switches_branch=0
|
|
47
|
-
if printf '%s' "$CMD" | grep -qE '\bgit[[:space:]]+(checkout|switch)\b'; then
|
|
48
|
-
if ! printf '%s' "$CMD" | grep -qE '([[:space:]]--([[:space:]]|$))|(\bgit[[:space:]]+(checkout|switch)[[:space:]]+-[bB]\b)'; then
|
|
49
|
-
switches_branch=1
|
|
50
|
-
fi
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
# Hard/keep/merge reset rewrites HEAD + working tree.
|
|
54
|
-
hard_reset=0
|
|
55
|
-
printf '%s' "$CMD" | grep -qE '\bgit[[:space:]]+reset[[:space:]]+(--hard|--keep|--merge)\b' && hard_reset=1
|
|
56
|
-
|
|
57
|
-
if [ "$switches_branch" = "1" ] || [ "$hard_reset" = "1" ]; then
|
|
58
|
-
deny "Refusing to move the shared main HEAD — $wt_count worktrees are branched off it (the 'burned twice' failure). Switch/reset inside a worktree, or stop the other sessions first. Override: run the command yourself outside Claude."
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
exit 0
|