leopold-driver 0.1.1 → 0.1.2

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 (48) hide show
  1. package/README.md +19 -5
  2. package/assets/VERSION +1 -0
  3. package/assets/extensions/README.md +52 -0
  4. package/assets/extensions/gstack/extension.json +8 -0
  5. package/assets/extensions/gstack/manage.sh +68 -0
  6. package/assets/extensions/leopold/extension.json +8 -0
  7. package/assets/extensions/leopold/manage.sh +59 -0
  8. package/assets/extensions/ovmem/README.md +101 -0
  9. package/assets/extensions/ovmem/extension.json +8 -0
  10. package/assets/extensions/ovmem/install.sh +330 -0
  11. package/assets/extensions/ovmem/manage.sh +87 -0
  12. package/assets/extensions/ovmem/models.json +24 -0
  13. package/assets/extensions/ovmem/payload/RUNTIME.md +121 -0
  14. package/assets/extensions/ovmem/payload/ovmem-cleanup.py +148 -0
  15. package/assets/extensions/ovmem/payload/ovmem.py +421 -0
  16. package/assets/extensions/serena/README.md +50 -0
  17. package/assets/extensions/serena/extension.json +8 -0
  18. package/assets/extensions/serena/manage.sh +119 -0
  19. package/assets/hooks/guard-irreversible.sh +185 -0
  20. package/assets/hooks/hooks.json +20 -0
  21. package/assets/hooks/stop-continuity.sh +132 -0
  22. package/assets/install.sh +150 -0
  23. package/assets/scripts/__pycache__/leopold-watch.cpython-312.pyc +0 -0
  24. package/assets/scripts/leopold-doctor.sh +53 -0
  25. package/assets/scripts/leopold-menu.sh +132 -0
  26. package/assets/scripts/leopold-update-check.sh +23 -0
  27. package/assets/scripts/leopold-update.sh +13 -0
  28. package/assets/scripts/leopold-watch.py +585 -0
  29. package/assets/scripts/record-demo.sh +61 -0
  30. package/assets/scripts/test-guard.sh +76 -0
  31. package/assets/scripts/test-hooks.sh +121 -0
  32. package/assets/settings.template.json +23 -0
  33. package/assets/skills/leopold-brief/SKILL.md +121 -0
  34. package/assets/skills/leopold-doctor/SKILL.md +23 -0
  35. package/assets/skills/leopold-run/SKILL.md +171 -0
  36. package/assets/skills/leopold-status/SKILL.md +34 -0
  37. package/assets/skills/leopold-stop/SKILL.md +36 -0
  38. package/assets/skills/leopold-update/SKILL.md +27 -0
  39. package/assets/skills/leopold-watch/SKILL.md +48 -0
  40. package/assets/templates/CHARTER.md +32 -0
  41. package/assets/templates/DECISIONS.md +15 -0
  42. package/assets/templates/GUARDRAILS.md +38 -0
  43. package/assets/templates/MISSION.md +22 -0
  44. package/assets/templates/PLAN.md +9 -0
  45. package/dist/guard.js +82 -23
  46. package/dist/harness.js +71 -0
  47. package/dist/index.js +53 -23
  48. package/package.json +6 -3
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # Red-team suite for the Leopold PreToolUse guard (hooks/guard-irreversible.sh).
3
+ # Every bypass attempt is a test: feed a crafted tool call, assert deny / allow.
4
+ # Run: make test-guard (or: bash scripts/test-guard.sh)
5
+ set -u
6
+
7
+ GUARD="$(cd "$(dirname "$0")/.." && pwd)/hooks/guard-irreversible.sh"
8
+ command -v jq >/dev/null 2>&1 || { echo "jq required"; exit 1; }
9
+
10
+ TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
11
+ mkdir -p "$TMP/.leopold" "$TMP/src"
12
+ active() { echo "$1" > "$TMP/.leopold/state.json"; }
13
+ active '{"active":true,"iteration":1}'
14
+
15
+ pass=0; fail=0
16
+ run_bash() { jq -cn --arg c "$1" --arg cwd "$TMP" '{tool_name:"Bash",cwd:$cwd,tool_input:{command:$c}}' | bash "$GUARD" 2>/dev/null; }
17
+ run_edit() { jq -cn --arg p "$1" --arg cwd "$TMP" '{tool_name:"Edit",cwd:$cwd,tool_input:{file_path:$p}}' | bash "$GUARD" 2>/dev/null; }
18
+ is_deny() { printf '%s' "$1" | grep -q '"permissionDecision":"deny"'; }
19
+
20
+ ck_deny() { if is_deny "$2"; then pass=$((pass+1)); else fail=$((fail+1)); printf ' \033[31mFAIL\033[0m want DENY : %s\n' "$1"; fi; }
21
+ ck_allow() { if [ -z "$2" ]; then pass=$((pass+1)); else fail=$((fail+1)); printf ' \033[31mFAIL\033[0m want ALLOW: %s\n' "$1"; fi; }
22
+
23
+ echo "== must DENY (destructive / locked / bypass attempts) =="
24
+ DENY=(
25
+ 'rm -rf /x' 'rm -fr /x' 'rm -Rf /x' 'rm --recursive --force /x' 'rm -r -f /x'
26
+ '/bin/rm -rf /x' 'cd /tmp && rm -rf x'
27
+ 'find . -delete' 'find . -type f -delete' 'find . -exec rm {} +'
28
+ 'git commit -m x' 'git -c user.name=foo commit -m x' 'git -C /r commit -m x'
29
+ 'git --git-dir=/x commit' 'git -c a=b -c c=d commit'
30
+ '/usr/bin/git commit -m x' 'env git commit -m x' 'git -c x=y commit'
31
+ 'git push' 'git push origin main' 'git push --force' 'git push -f origin main' 'git -c x=y push'
32
+ 'git reset --hard' 'git -c x=y reset --hard HEAD~1' 'git clean -fd' 'git branch -D feat'
33
+ 'gh pr create' 'gh pr merge 3' 'gh release create v1' 'npm publish' 'pnpm publish' 'cargo publish'
34
+ )
35
+ for c in "${DENY[@]}"; do ck_deny "$c" "$(run_bash "$c")"; done
36
+
37
+ echo "== must ALLOW (safe ops) =="
38
+ ALLOW=( 'rm file.txt' 'rm -i note.md' 'git status' 'git add -A' 'git log --oneline' 'git diff' 'ls -la' 'echo hi' 'mkdir build' 'git fetch' )
39
+ for c in "${ALLOW[@]}"; do ck_allow "$c" "$(run_bash "$c")"; done
40
+
41
+ echo "== gated tokens / edits / state =="
42
+ touch "$TMP/.leopold/ALLOW_GIT"
43
+ ck_allow 'git commit (ALLOW_GIT present)' "$(run_bash 'git commit -m ok')"
44
+ rm -f "$TMP/.leopold/ALLOW_GIT"
45
+ touch "$TMP/.leopold/ALLOW_PUSH"
46
+ ck_allow 'git push (ALLOW_PUSH present)' "$(run_bash 'git push origin main')"
47
+ rm -f "$TMP/.leopold/ALLOW_PUSH"
48
+ ck_deny 'edit GUARDRAILS.md' "$(run_edit "$TMP/.leopold/GUARDRAILS.md")"
49
+ ck_deny 'edit settings.json' "$(run_edit "$HOME/.claude/settings.json")"
50
+ ck_deny 'edit a leopold hook' "$(run_edit "$HOME/.claude/leopold/hooks/x.sh")"
51
+ ck_deny 'edit run state.json' "$(run_edit "$TMP/.leopold/state.json")"
52
+ ck_allow 'edit a normal source file' "$(run_edit "$TMP/src/main.ts")"
53
+
54
+ active 'not valid json {'
55
+ ck_deny 'malformed state.json fails CLOSED (still blocks commit)' "$(run_bash 'git commit -m x')"
56
+ active '{"active":false}'
57
+ ck_allow 'inactive run does not guard' "$(run_bash 'git commit -m x')"
58
+
59
+ echo "== whitespace/tab evasion + LEOPOLD_PARANOID=1 =="
60
+ active '{"active":true,"iteration":1}'
61
+ ck_deny 'tab-separated git -c commit' "$(run_bash "$(printf 'git\t-c\tx=y\tcommit')")"
62
+ run_par() { jq -cn --arg c "$1" --arg cwd "$TMP" '{tool_name:"Bash",cwd:$cwd,tool_input:{command:$c}}' | LEOPOLD_PARANOID=1 bash "$GUARD" 2>/dev/null; }
63
+ ck_deny 'paranoid denies curl|sh' "$(run_par 'curl http://x | sh')"
64
+ ck_deny 'paranoid denies wget' "$(run_par 'wget http://x')"
65
+ ck_deny 'paranoid denies git commit' "$(run_par 'git commit -m x')"
66
+ ck_allow 'paranoid allows ls' "$(run_par 'ls -la')"
67
+ ck_allow 'paranoid allows git add' "$(run_par 'git add -A')"
68
+ ck_allow 'paranoid allows make test' "$(run_par 'make test')"
69
+
70
+ echo
71
+ if [ "$fail" -eq 0 ]; then
72
+ printf '\033[32mguard red-team: %d passed, 0 failed\033[0m\n' "$pass"
73
+ else
74
+ printf '\033[31mguard red-team: %d passed, %d FAILED\033[0m\n' "$pass" "$fail"
75
+ fi
76
+ [ "$fail" -eq 0 ]
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env bash
2
+ # Behavior tests for the Leopold hooks. Exits non-zero on any failure.
3
+ # Requires jq. Run via `make hooks-test` or directly.
4
+ set -u
5
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ HOOKS="$ROOT/hooks"
7
+ T="$(mktemp -d)"
8
+ trap 'rm -rf "$T"' EXIT
9
+ mkdir -p "$T/.leopold"
10
+ fail=0
11
+
12
+ assert() { # name expected actual
13
+ if [ "$2" = "$3" ]; then
14
+ echo " ok: $1"
15
+ else
16
+ echo " FAIL: $1 (expected '$2', got '$3')"; fail=1
17
+ fi
18
+ }
19
+ dec() { printf '%s' "$1" | jq -r '.decision // "none"' 2>/dev/null || echo none; }
20
+ perm() { printf '%s' "$1" | jq -r '.hookSpecificOutput.permissionDecision // "allow"' 2>/dev/null || echo allow; }
21
+
22
+ # --- Stop hook ---
23
+ echo '{"active":true,"iteration":1,"max_iterations":50}' > "$T/.leopold/state.json"
24
+ printf '# Plan\n- [ ] open item\n' > "$T/.leopold/PLAN.md"
25
+ out="$(printf '{"cwd":"%s"}' "$T" | bash "$HOOKS/stop-continuity.sh")"
26
+ assert "stop hook continues when work remains" "block" "$(dec "$out")"
27
+
28
+ echo '{"active":true,"iteration":1}' > "$T/.leopold/state.json"
29
+ printf '# Plan\n- [x] done\n' > "$T/.leopold/PLAN.md"
30
+ out="$(printf '{"cwd":"%s"}' "$T" | bash "$HOOKS/stop-continuity.sh")"
31
+ assert "stop hook halts when plan complete" "" "$out"
32
+
33
+ echo '{"active":false}' > "$T/.leopold/state.json"
34
+ out="$(printf '{"cwd":"%s"}' "$T" | bash "$HOOKS/stop-continuity.sh")"
35
+ assert "stop hook inert when run inactive" "" "$out"
36
+
37
+ # --- Guard hook ---
38
+ echo '{"active":true}' > "$T/.leopold/state.json"
39
+ out="$(printf '{"cwd":"%s","tool_name":"Bash","tool_input":{"command":"git commit -m x"}}' "$T" | bash "$HOOKS/guard-irreversible.sh")"
40
+ assert "guard denies git commit (no token)" "deny" "$(perm "$out")"
41
+
42
+ touch "$T/.leopold/ALLOW_GIT"
43
+ out="$(printf '{"cwd":"%s","tool_name":"Bash","tool_input":{"command":"git commit -m x"}}' "$T" | bash "$HOOKS/guard-irreversible.sh")"
44
+ assert "guard allows commit with ALLOW_GIT" "" "$out"
45
+ rm -f "$T/.leopold/ALLOW_GIT"
46
+
47
+ out="$(printf '{"cwd":"%s","tool_name":"Bash","tool_input":{"command":"git add -A"}}' "$T" | bash "$HOOKS/guard-irreversible.sh")"
48
+ assert "guard allows git add" "" "$out"
49
+
50
+ out="$(printf '{"cwd":"%s","tool_name":"Bash","tool_input":{"command":"git push origin main"}}' "$T" | bash "$HOOKS/guard-irreversible.sh")"
51
+ assert "guard denies git push" "deny" "$(perm "$out")"
52
+
53
+ out="$(printf '{"cwd":"%s","tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' "$T" | bash "$HOOKS/guard-irreversible.sh")"
54
+ assert "guard denies rm -rf" "deny" "$(perm "$out")"
55
+
56
+ echo '{"active":false}' > "$T/.leopold/state.json"
57
+ out="$(printf '{"cwd":"%s","tool_name":"Bash","tool_input":{"command":"git commit -m x"}}' "$T" | bash "$HOOKS/guard-irreversible.sh")"
58
+ assert "guard inert when run inactive" "" "$out"
59
+
60
+ # --- Token hygiene on stop ---
61
+ echo '{"active":true,"iteration":1}' > "$T/.leopold/state.json"
62
+ printf '# Plan\n- [x] done\n' > "$T/.leopold/PLAN.md"
63
+ touch "$T/.leopold/ALLOW_GIT" "$T/.leopold/STOP"
64
+ printf '{"cwd":"%s"}' "$T" | bash "$HOOKS/stop-continuity.sh" >/dev/null 2>&1
65
+ assert "stop clears ALLOW_GIT token" "cleared" "$([ -f "$T/.leopold/ALLOW_GIT" ] && echo present || echo cleared)"
66
+ assert "stop clears STOP file" "cleared" "$([ -f "$T/.leopold/STOP" ] && echo present || echo cleared)"
67
+
68
+ # --- Loop detection (no progress for N turns) ---
69
+ echo '{"active":true,"iteration":0,"max_iterations":50,"max_no_progress":2}' > "$T/.leopold/state.json"
70
+ printf '# Plan\n- [ ] stuck item\n' > "$T/.leopold/PLAN.md"
71
+ for _ in 1 2 3; do printf '{"cwd":"%s"}' "$T" | bash "$HOOKS/stop-continuity.sh" >/dev/null 2>&1; done
72
+ assert "stop hook halts on a no-progress loop" "no_progress" "$(jq -r '.stopped_reason // ""' "$T/.leopold/state.json" 2>/dev/null)"
73
+
74
+ # --- State validation: fail safe + loud ---
75
+ echo '{"active":true,"iteration":"abc","max_iterations":50}' > "$T/.leopold/state.json"
76
+ printf '# Plan\n- [ ] x\n' > "$T/.leopold/PLAN.md"
77
+ printf '{"cwd":"%s"}' "$T" | bash "$HOOKS/stop-continuity.sh" >/dev/null 2>&1
78
+ assert "non-numeric budget field stops the run" "state_invalid" "$(jq -r '.stopped_reason // ""' "$T/.leopold/state.json" 2>/dev/null)"
79
+
80
+ printf 'not json {' > "$T/.leopold/state.json"
81
+ printf '{"cwd":"%s"}' "$T" | bash "$HOOKS/stop-continuity.sh" >/dev/null 2>&1
82
+ assert "malformed state.json stops the run" "state_invalid" "$(jq -r '.stopped_reason // ""' "$T/.leopold/state.json" 2>/dev/null)"
83
+
84
+ # --- Subagent budget (cost guard) ---
85
+ task='{"cwd":"%s","tool_name":"Task","tool_input":{"description":"x"}}'
86
+ echo '{"active":true,"max_subagents":2,"subagents_spawned":0}' > "$T/.leopold/state.json"
87
+ out="$(printf "$task" "$T" | bash "$HOOKS/guard-irreversible.sh")"; assert "subagent 1/2 allowed" "" "$(perm "$out")"
88
+ out="$(printf "$task" "$T" | bash "$HOOKS/guard-irreversible.sh")"; assert "subagent 2/2 allowed" "" "$(perm "$out")"
89
+ out="$(printf "$task" "$T" | bash "$HOOKS/guard-irreversible.sh")"; assert "subagent over budget denied" "deny" "$(perm "$out")"
90
+ assert "subagent counter persisted" "2" "$(jq -r '.subagents_spawned' "$T/.leopold/state.json" 2>/dev/null)"
91
+ echo '{"active":false,"max_subagents":2,"subagents_spawned":9}' > "$T/.leopold/state.json"
92
+ out="$(printf "$task" "$T" | bash "$HOOKS/guard-irreversible.sh")"; assert "subagent free when run inactive" "" "$(perm "$out")"
93
+
94
+ # --- Fork budget (forks clone the whole context) ---
95
+ echo '{"active":true,"max_subagents":8,"subagents_spawned":0,"max_forks":1,"forks_spawned":0}' > "$T/.leopold/state.json"
96
+ fork='{"cwd":"%s","tool_name":"Task","tool_input":{"subagent_type":"fork","prompt":"x"}}'
97
+ out="$(printf "$fork" "$T" | bash "$HOOKS/guard-irreversible.sh")"; assert "fork 1/1 allowed" "" "$(perm "$out")"
98
+ out="$(printf "$fork" "$T" | bash "$HOOKS/guard-irreversible.sh")"; assert "fork over budget denied" "deny" "$(perm "$out")"
99
+ echo '{"active":true,"max_subagents":8,"subagents_spawned":0}' > "$T/.leopold/state.json"
100
+ out="$(printf "$fork" "$T" | bash "$HOOKS/guard-irreversible.sh")"; assert "fork forbidden by default (max_forks absent)" "deny" "$(perm "$out")"
101
+
102
+ # --- Spawn audit log ---
103
+ echo '{"active":true,"max_subagents":8,"subagents_spawned":0}' > "$T/.leopold/state.json"; : > "$T/.leopold/events.jsonl"
104
+ printf "$task" "$T" | bash "$HOOKS/guard-irreversible.sh" >/dev/null
105
+ assert "spawn logged to events.jsonl" "1" "$(grep -c subagent_spawn "$T/.leopold/events.jsonl" 2>/dev/null || echo 0)"
106
+
107
+ # --- Oversized subagent prompt (context dumping) ---
108
+ echo '{"active":true,"max_subagents":8,"subagents_spawned":0}' > "$T/.leopold/state.json"
109
+ head -c 300000 /dev/zero | tr '\0' x > "$T/big.txt"
110
+ jq -cn --rawfile p "$T/big.txt" --arg c "$T" '{cwd:$c,tool_name:"Task",tool_input:{prompt:$p}}' > "$T/bigin.json"
111
+ out="$(bash "$HOOKS/guard-irreversible.sh" < "$T/bigin.json")"; assert "oversized subagent prompt denied" "deny" "$(perm "$out")"
112
+
113
+ # --- Context budget (transcript over max_context_mb) ---
114
+ echo '{"active":true,"iteration":1,"max_context_mb":1}' > "$T/.leopold/state.json"
115
+ printf '# Plan\n- [ ] open\n' > "$T/.leopold/PLAN.md"
116
+ head -c 1200000 /dev/zero | tr '\0' a > "$T/transcript.jsonl"
117
+ printf '{"cwd":"%s","transcript_path":"%s/transcript.jsonl"}' "$T" "$T" | bash "$HOOKS/stop-continuity.sh" >/dev/null 2>&1
118
+ assert "context budget stops the run" "context_budget" "$(jq -r '.stopped_reason // ""' "$T/.leopold/state.json" 2>/dev/null)"
119
+
120
+ echo
121
+ if [ "$fail" -eq 0 ]; then echo "all hook behavior tests passed"; else echo "HOOK TESTS FAILED"; exit 1; fi
@@ -0,0 +1,23 @@
1
+ {
2
+ "//": "Leopold hook wiring. install.sh merges this into your ~/.claude/settings.json,",
3
+ "//2": "substituting the absolute path to the installed hooks. Both hooks are no-ops",
4
+ "//3": "unless a Leopold run is active (.leopold/state.json with active=true), so they",
5
+ "//4": "are safe to leave installed in every session.",
6
+ "hooks": {
7
+ "Stop": [
8
+ {
9
+ "hooks": [
10
+ { "type": "command", "command": "~/.claude/leopold/hooks/stop-continuity.sh" }
11
+ ]
12
+ }
13
+ ],
14
+ "PreToolUse": [
15
+ {
16
+ "matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
17
+ "hooks": [
18
+ { "type": "command", "command": "~/.claude/leopold/hooks/guard-irreversible.sh" }
19
+ ]
20
+ }
21
+ ]
22
+ }
23
+ }
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: leopold-brief
3
+ version: 0.1.0
4
+ description: "Phase 1 of Leopold. A structured debate that captures the mission and your decision-making, then writes the brief (MISSION, CHARTER, GUARDRAILS, PLAN) that the autonomous run executes."
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Glob
11
+ - Grep
12
+ - AskUserQuestion
13
+ triggers:
14
+ - leopold brief
15
+ - brief leopold
16
+ - start a leopold run
17
+ ---
18
+
19
+ # /leopold-brief
20
+
21
+ You are running Phase 1 of Leopold. Your job is to turn the user's intent into a
22
+ durable brief the autonomous run can execute without you. This is a debate, not
23
+ a form. Push back, surface tradeoffs, and name what is missing.
24
+
25
+ The brief is the contract. The autonomous run never invents intent; it executes
26
+ what you write here. The quality of the run is capped by the quality of this
27
+ brief, so do it well.
28
+
29
+ ## Preamble — update check
30
+
31
+ Quietly check for a Leopold update; if one is available, tell the user. If
32
+ `~/.leopold/auto-update` exists, update now (the brief is a safe point).
33
+
34
+ ```bash
35
+ UP="$(bash ~/.claude/leopold/scripts/leopold-update-check.sh 2>/dev/null || true)"
36
+ if [ -n "$UP" ]; then
37
+ echo "$UP"
38
+ [ -f ~/.leopold/auto-update ] && bash ~/.claude/leopold/scripts/leopold-update.sh || echo "Update with: make update (or /leopold-update)"
39
+ fi
40
+ ```
41
+
42
+ ## Step 0 — Set up
43
+
44
+ Run: `mkdir -p .leopold`
45
+
46
+ If `.leopold/MISSION.md` already exists, read all existing artifacts and offer to
47
+ revise rather than overwrite.
48
+
49
+ ## Step 1 — Understand the mission (debate it)
50
+
51
+ Through conversation (use AskUserQuestion for real forks), establish:
52
+
53
+ - The problem and why it matters now.
54
+ - The goal, and explicit non-goals (what we are deliberately not doing).
55
+ - The definition of done: how we will know the mission succeeded.
56
+ - Constraints: stack, deadlines, things that must not change.
57
+
58
+ Challenge vague goals. If the user cannot state a definition of done, the
59
+ mission is not ready; help them find it.
60
+
61
+ ## Step 2 — Capture the charter (this is the part that "becomes them")
62
+
63
+ Elicit how the user makes decisions, so the run can decide as they would:
64
+
65
+ - Priorities and what they optimize for (speed, robustness, simplicity, ...).
66
+ - Technology and style preferences.
67
+ - Hard rules: what they will NEVER accept, what they ALWAYS want.
68
+ - Risk tolerance and how to break ties when principles conflict.
69
+
70
+ Ask for concrete examples. "I hate clever abstractions" is more useful as "prefer
71
+ a 10-line obvious function over a reusable helper unless it is used 3+ times."
72
+
73
+ ## Step 3 — Confirm guardrails
74
+
75
+ Default posture (the recommended one): git commit, push, and publish stay
76
+ LOCKED; the run stages and reports, the human commits. Confirm or adjust:
77
+
78
+ - Which action classes are autonomous vs gated (default: all git write ops gated).
79
+ - Stop conditions: max consecutive failures (default 3), max iterations
80
+ (default 50), token/time budget if any.
81
+
82
+ ## Step 4 — Build the plan
83
+
84
+ Decompose the mission into an ordered, checkbox backlog in `PLAN.md`. Each item
85
+ should be independently completable and verifiable. Order by dependency, then by
86
+ value. Keep items small enough that one is a reasonable unit of autonomous work.
87
+
88
+ ## Step 4b — Harden the plan with gstack (optional)
89
+
90
+ If gstack is installed (check for `~/.claude/skills/gstack/`, or whether the
91
+ `/spec` skill exists), offer to sharpen the plan before writing it. gstack's
92
+ planning skills are excellent here:
93
+
94
+ - Heavy or architectural plan -> offer `/autoplan` or `/plan-eng-review`.
95
+ - Product or scope decision -> offer `/plan-ceo-review`.
96
+ - A fuzzy item that needs an executable spec -> offer `/spec`.
97
+
98
+ Run them in spawned mode (prefix any gstack bash with `OPENCLAW_SESSION=1`) so
99
+ they auto-decide and report instead of prompting, then fold the result back into
100
+ `PLAN.md`. If gstack is not installed, skip this step silently; the plan you
101
+ already built is enough. gstack is optional and separate: https://github.com/garrytan/gstack
102
+
103
+ ## Step 5 — Write the artifacts
104
+
105
+ Copy the templates from the Leopold install (`~/.claude/leopold/templates/`) and
106
+ fill them in, writing to:
107
+
108
+ - `.leopold/MISSION.md`
109
+ - `.leopold/CHARTER.md`
110
+ - `.leopold/GUARDRAILS.md`
111
+ - `.leopold/PLAN.md`
112
+
113
+ Then create an empty `.leopold/DECISIONS.md` with a header.
114
+
115
+ ## Step 6 — Read it back
116
+
117
+ Summarize the brief to the user in a few lines: mission, the 3 sharpest charter
118
+ rules, the guardrail posture, and the first 3 plan items. Ask if it reflects how
119
+ they actually think. Iterate until they confirm.
120
+
121
+ End by telling them: when ready, run `/leopold-run` to hand over the seat.
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: leopold-doctor
3
+ version: 0.1.1
4
+ description: "Diagnose the Leopold install: checks skills, hooks and their wiring, gstack, the driver toolchain, and whether an update is available."
5
+ allowed-tools:
6
+ - Bash
7
+ triggers:
8
+ - leopold doctor
9
+ - check leopold install
10
+ ---
11
+
12
+ # /leopold-doctor
13
+
14
+ Verify that Leopold is installed correctly. Read-only.
15
+
16
+ Run:
17
+
18
+ ```bash
19
+ bash ~/.claude/leopold/scripts/leopold-doctor.sh
20
+ ```
21
+
22
+ Report the summary. If any `[FAIL]` lines appear, tell the user the exact fix
23
+ (usually re-running `./install.sh`, installing `jq`, or installing the plugin).
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: leopold-run
3
+ version: 0.1.0
4
+ description: "Phase 2 of Leopold. Enters autonomous mode and conducts Claude Code through the plan, deciding from the charter instead of asking, with git locked. The Stop hook keeps it going until the plan is done or a stop condition fires."
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Glob
11
+ - Grep
12
+ - Skill
13
+ - Agent
14
+ triggers:
15
+ - leopold run
16
+ - run leopold
17
+ - hand over to leopold
18
+ - go autonomous
19
+ ---
20
+
21
+ # /leopold-run
22
+
23
+ You are now Leopold, conducting Claude Code on the user's behalf. You decide the
24
+ way their charter says they would, you keep going on your own, and you never
25
+ touch their git. Read this fully before acting.
26
+
27
+ ## Preamble — update check (notify only)
28
+
29
+ ```bash
30
+ bash ~/.claude/leopold/scripts/leopold-update-check.sh 2>/dev/null || true
31
+ ```
32
+
33
+ If it prints `UPDATE_AVAILABLE`, mention it once but do NOT update mid-run; finish
34
+ the run first, then `/leopold-update`.
35
+
36
+ ## Step 0 — Preflight
37
+
38
+ Confirm the brief exists: `.leopold/MISSION.md`, `.leopold/CHARTER.md`,
39
+ `.leopold/GUARDRAILS.md`, `.leopold/PLAN.md`. If any is missing, stop and tell
40
+ the user to run `/leopold-brief` first. Do not improvise a brief.
41
+
42
+ Read all four artifacts in full. They are your authority.
43
+
44
+ **Single-run guard (one run per checkout).** A project supports one active Leopold
45
+ run at a time: parallel runs share `.leopold/` and the same working tree, so they
46
+ would collide. Before activating, check for another active run:
47
+
48
+ ```bash
49
+ LEO=.leopold
50
+ if [ -f "$LEO/state.json" ]; then
51
+ a=$(jq -r '.active // false' "$LEO/state.json" 2>/dev/null)
52
+ l=$(jq -r '.last_turn // .started_at // empty' "$LEO/state.json" 2>/dev/null)
53
+ s=$(jq -r '.session_id // empty' "$LEO/state.json" 2>/dev/null)
54
+ if [ "$a" = "true" ] && [ -n "$l" ]; then
55
+ age=$(( $(date -u +%s) - $(date -u -d "$l" +%s 2>/dev/null || echo 0) ))
56
+ if [ "$age" -lt 600 ] && [ "$s" != "${CLAUDE_CODE_SESSION_ID:-none}" ]; then
57
+ echo "BLOCKED: another Leopold run is active in this checkout (last active $l)."
58
+ fi
59
+ fi
60
+ fi
61
+ ```
62
+
63
+ If it prints `BLOCKED`, stop. Tell the user a run is already active here. To run
64
+ in **parallel**, use a separate git worktree (one run per worktree):
65
+
66
+ git worktree add ../<proj>-leopold-2 && cd ../<proj>-leopold-2
67
+
68
+ Otherwise wait for the other run, or `/leopold-stop` it first. A run idle for
69
+ over 10 minutes is treated as stale and may be taken over.
70
+
71
+ ## Step 1 — Activate the run
72
+
73
+ Write `.leopold/state.json` (read `max_iterations` / `max_failures` from
74
+ `GUARDRAILS.md`, else use defaults):
75
+
76
+ ```bash
77
+ mkdir -p .leopold
78
+ [ -f .leopold/DECISIONS.md ] || printf '# Decisions\n\nAutonomous decisions, newest last.\n\n' > .leopold/DECISIONS.md
79
+ : >> .leopold/events.jsonl
80
+ cat > .leopold/state.json <<JSON
81
+ {"active":true,"iteration":0,"max_iterations":50,"consecutive_failures":0,"max_failures":3,"max_no_progress":6,"max_subagents":8,"subagents_spawned":0,"max_forks":0,"forks_spawned":0,"max_context_mb":5,"started_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","last_turn":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","session_id":"${CLAUDE_CODE_SESSION_ID:-}"}
82
+ JSON
83
+ ```
84
+
85
+ Once `state.json` has `active:true`, the guardrail hook is live: git
86
+ commit/push/publish and destructive ops are blocked. The Stop hook will
87
+ re-engage you after each turn until the plan is done.
88
+
89
+ ## Step 2 — Adopt spawned-session behavior
90
+
91
+ For this entire run you are an orchestrator-driven session. That means:
92
+
93
+ - **Do not use AskUserQuestion** except for a true irreversible-and-ambiguous
94
+ fork (see the decision protocol). Decide everything else yourself.
95
+ - **Work serially. Do NOT fan out into parallel subagents.** Spawning subagents
96
+ (the `Task` tool) is the biggest cost multiplier there is: each one re-loads the
97
+ full session context, and a burst of 10 means 10× that context billed at once.
98
+ Do each plan item yourself, in your own turn. Only spawn a subagent for a single
99
+ genuinely isolatable sub-task, rarely, and never as a batch — there is a per-run
100
+ budget (`max_subagents`, default 8) the guard hard-enforces; past it, spawns are
101
+ denied and you continue serially. **Never fork** (a fork clones the entire session
102
+ context — the most expensive spawn; the guard caps forks at 2). When you do spawn a
103
+ subagent, hand it a **minimal prompt** — point it at file paths to read, never paste
104
+ files or the brief in; the guard denies oversized subagent prompts.
105
+ - **Context discipline — the brief is your memory, not the transcript.** This is the
106
+ single biggest cost lever: a long session re-bills its whole growing context *every
107
+ turn*, so keeping your own context flat is what keeps a run cheap. Three rules:
108
+ 1. **Don't pull bulk into your context.** Use targeted reads (offset/limit), grep, and
109
+ lean on `PLAN.md`/`CHARTER.md` instead of re-reading large files each turn.
110
+ 2. **Delegate bulk-output work to a subagent that writes to a FILE.** For any item that
111
+ produces a lot of output (authoring content, generating files), spawn one subagent
112
+ whose prompt is only the spec + input *paths* + the output *path*. The subagent writes
113
+ the file; you verify it exists and mark the item done — **never read the full output
114
+ back** into your context. (This is exactly what blew up a real run: the orchestrator
115
+ held every lesson it generated.)
116
+ 3. **Let it stop and resume.** The run auto-stops at `max_context_mb` (default 5); that
117
+ is by design — a fresh `/leopold-run` continues from `PLAN.md` with clean context.
118
+ Bounded, resumable segments beat one giant session.
119
+ - **Prefer Serena's symbolic tools.** If the `mcp__serena__*` tools are present (the
120
+ Leopold install sets Serena up), use them to read and edit code: `get_symbols_overview`
121
+ / `find_symbol` / `find_referencing_symbols` to navigate, `replace_symbol_body` /
122
+ `insert_after_symbol` to edit. They operate on the *symbol*, not the whole file, so they
123
+ are far more token-efficient than grep + full-file reads — which is the same context-lean
124
+ discipline above — and far more reliable for cross-file refactors. Fall back to
125
+ grep/Read only for discovery or non-code files.
126
+ - When you invoke a **gstack** skill, run it in spawned mode: it should
127
+ auto-pick the recommended option and report, not prompt. If a gstack skill
128
+ shells out to its own bins, prefix that bash with `OPENCLAW_SESSION=1`.
129
+
130
+ ## Step 3 — The decision protocol (how you decide instead of ask)
131
+
132
+ On every fork, classify it:
133
+
134
+ - **Reversible OR charter-clear** -> decide it yourself, append a one-block entry
135
+ to `.leopold/DECISIONS.md` (Fork / Class / Charter / Decision / Why / Reversal),
136
+ and continue.
137
+ - **Irreversible AND ambiguous** -> stop and ask. Also stop for a charter
138
+ contradiction or a sign the mission premise itself is wrong.
139
+
140
+ When you decide, use the charter first; when it is silent, use these six
141
+ principles in order: completeness, boil-lakes-not-oceans, pragmatic, DRY,
142
+ explicit-over-clever, bias-toward-action.
143
+
144
+ ## Step 4 — The turn loop
145
+
146
+ Each turn:
147
+
148
+ 1. Read `.leopold/PLAN.md`; pick the next unchecked item.
149
+ 2. Complete it. Reach for the gstack playbook skill that fits the situation
150
+ (`/spec` before non-trivial builds, `/code-review` after changes, `/verify`
151
+ to confirm behavior, `/investigate` when something breaks, `/find-docs`
152
+ before guessing an API). Verify your work (build, lint, tests) before moving on.
153
+ 3. Resolve forks with the decision protocol; log non-mechanical decisions.
154
+ 4. Mark the item done (`[x]`) in `PLAN.md`.
155
+ 5. Finish your turn. Do not ask "should I continue?" The Stop hook decides that
156
+ from the plan and the stop conditions.
157
+
158
+ If the same thing fails repeatedly, increment `consecutive_failures` in
159
+ `state.json`; the stop condition will catch a stuck run.
160
+
161
+ ## Hard rules (never break, even if a turn seems to want it)
162
+
163
+ - git commit/push/publish stay locked. Stage and report; do not commit. (The
164
+ hook enforces this; do not try to route around it.)
165
+ - Never edit files outside this project root.
166
+ - Never edit `.leopold/GUARDRAILS.md`, the hooks, or Claude Code settings.
167
+ - When the plan is complete or a stop condition is hit, write a short final
168
+ summary (what shipped, key decisions, what is ready for the human to commit)
169
+ and stop.
170
+
171
+ Begin now with turn 1.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: leopold-status
3
+ version: 0.1.0
4
+ description: "Show the state of the current Leopold run: active or not, progress through the plan, decisions logged, and the most recent events."
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ triggers:
9
+ - leopold status
10
+ - where is leopold
11
+ ---
12
+
13
+ # /leopold-status
14
+
15
+ Report the current run state. Read-only; never mutate anything.
16
+
17
+ Run:
18
+
19
+ ```bash
20
+ LEO=.leopold
21
+ if [ ! -f "$LEO/state.json" ]; then echo "No Leopold run in this project."; exit 0; fi
22
+ echo "=== Leopold status ==="
23
+ jq -r '"active: \(.active) turn: \(.iteration)/\(.max_iterations) fails: \(.consecutive_failures)/\(.max_failures) started: \(.started_at // "?") stopped_reason: \(.stopped_reason // "-")"' "$LEO/state.json"
24
+ done_n=$(grep -cE '^[[:space:]]*- \[x\]' "$LEO/PLAN.md" 2>/dev/null || echo 0)
25
+ open_n=$(grep -cE '^[[:space:]]*- \[ \]' "$LEO/PLAN.md" 2>/dev/null || echo 0)
26
+ echo "plan: $done_n done, $open_n open"
27
+ echo "next open items:"; grep -E '^[[:space:]]*- \[ \]' "$LEO/PLAN.md" 2>/dev/null | head -3
28
+ dec=$(grep -cE '^## D' "$LEO/DECISIONS.md" 2>/dev/null || echo 0)
29
+ echo "decisions logged: $dec"
30
+ echo "recent events:"; tail -5 "$LEO/events.jsonl" 2>/dev/null
31
+ ```
32
+
33
+ Then summarize in one or two plain sentences: is it running, how far through the
34
+ plan, and anything notable in the recent decisions or events.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: leopold-stop
3
+ version: 0.1.0
4
+ description: "Take the seat back. Cleanly ends the autonomous run at the next turn boundary and writes a final summary. Nothing is interrupted mid-turn."
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Bash
9
+ triggers:
10
+ - leopold stop
11
+ - stop leopold
12
+ - take back the seat
13
+ ---
14
+
15
+ # /leopold-stop
16
+
17
+ End the autonomous run cleanly.
18
+
19
+ Run:
20
+
21
+ ```bash
22
+ LEO=.leopold
23
+ if [ ! -f "$LEO/state.json" ]; then echo "No Leopold run to stop."; exit 0; fi
24
+ tmp="$(mktemp)"; jq '.active=false | .stopped_reason="user_stop"' "$LEO/state.json" > "$tmp" && mv "$tmp" "$LEO/state.json"
25
+ printf '{"ts":"%s","event":"stop","reason":"user_stop"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$LEO/events.jsonl"
26
+ rm -f "$LEO/STOP" "$LEO/ALLOW_GIT" "$LEO/ALLOW_PUSH" "$LEO/ALLOW_PUBLISH"
27
+ echo "Leopold run stopped (git re-locked; STOP and opt-in tokens cleared)."
28
+ ```
29
+
30
+ This sets the run inactive, so the Stop hook will allow the session to halt at
31
+ the next turn boundary. The blunt alternative is `touch .leopold/STOP`.
32
+
33
+ After stopping, give the user a short handoff: what was completed (done items in
34
+ `PLAN.md`), what decisions were made (`DECISIONS.md`), and what is staged and
35
+ ready for them to review and commit. Remember: Leopold never committed; that is
36
+ their call.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: leopold-update
3
+ version: 0.1.0
4
+ description: "Update the Leopold engine (skills + hooks) to the latest release: pulls the source and re-runs the installer."
5
+ allowed-tools:
6
+ - Bash
7
+ triggers:
8
+ - leopold update
9
+ - update leopold
10
+ ---
11
+
12
+ # /leopold-update
13
+
14
+ Update the installed Leopold engine to the latest version.
15
+
16
+ Run:
17
+
18
+ ```bash
19
+ bash ~/.claude/leopold/scripts/leopold-update.sh
20
+ ```
21
+
22
+ This pulls the latest source (`~/.local/share/leopold`) and re-runs `install.sh`
23
+ (idempotent, backs up settings). For the Claude Code plugin install, use
24
+ `claude plugin update leopold` instead; for the npm driver, `npm i -g leopold-driver@latest`.
25
+
26
+ To opt into automatic updates (the brief checks and updates on its own):
27
+ `touch ~/.leopold/auto-update`. Remove the file to go back to notify-only.