@oneie/claude 0.2.1 → 0.3.1
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/commands/do.md +4 -2
- package/commands/skill-create.md +100 -0
- package/hooks/hooks.json +33 -0
- package/hooks/lib/hook.sh +69 -0
- package/hooks/scripts/gate-guard.sh +84 -0
- package/hooks/scripts/read-tracker.sh +27 -0
- package/hooks/scripts/stop-reflect.sh +12 -0
- package/package.json +21 -5
- package/scripts/do-auto.sh +86 -6
package/commands/do.md
CHANGED
|
@@ -122,7 +122,7 @@ For each **enabled** stop (per the pruned spine), in order: check if its artifac
|
|
|
122
122
|
| ↳ *ANALYZE* | — | `do-analyze.sh plans/<slug>-todo.md` — CRITICAL exit 1 blocks BUILD | bash · none |
|
|
123
123
|
| **code** | survey verdict = `build` (≥70% match → `expose`/`extend` instead) | the BUILD engine (Step 3) | sonnet · low–medium |
|
|
124
124
|
| **tests** | test file in the repo folder | test-first, one assertion per deliverable | sonnet · low |
|
|
125
|
-
| **proof** | proof artifact captured | `do-prove.sh` (browser / curl / contract / sync) + `accessibility`; **promise-check** the shipped thing against `text/<slug>.md
|
|
125
|
+
| **proof** | proof artifact captured | `do-prove.sh` (browser / curl / contract / sync) + `accessibility`; **promise-check** the shipped thing against `text/<slug>.md`. A multi-cycle plan already built on its own `do/<slug>` branch (worktree isolation — see Step 2); PROVE is the gate that branch must clear before a human merges it to trunk | sonnet · low |
|
|
126
126
|
| **docs** | feature doc + runbook exist | `tutorial` + `writer` — written against the *proven* behavior | sonnet · medium |
|
|
127
127
|
| **release** | changelog / README row | `/release` + adoption signal | sonnet · low |
|
|
128
128
|
|
|
@@ -163,6 +163,8 @@ bun .claude/scripts/do-auto.sh <slug>
|
|
|
163
163
|
|
|
164
164
|
`do-auto.sh` loops: each iteration spawns a clean `/do <slug> --next-cycle` that runs exactly one batch (W1→W4), ticks its boxes, writes state, and exits. Because each cycle starts from near-zero context, prior-cycle recon/edit/verify chatter never accumulates. The main session just kicks off the loop and reports the close.
|
|
165
165
|
|
|
166
|
+
**Worktree isolation comes free with the loop.** The loop's existence *is* the trigger — `do-auto.sh` only runs for multi-cycle plans, which is exactly the FEATURE/SCHEMA work that must not land half-built on trunk. So before the first cycle it cuts a worktree `.do-worktrees/<slug>` on branch `do/<slug>` (reusing it on resume), syncs the plan's spine artifacts into it, and runs **every cycle inside it** — each cycle's edits and box-ticks commit to that branch. Trunk never moves while the loop runs; a halt (trust `cautious` / stall / max-cycles) leaves a clean, inspectable WIP branch, and re-running `/do <slug>` resumes in the same worktree. On plan-complete the loop **reports** the merge (`git merge --no-ff do/<slug>`) rather than running it — landing to trunk is a human decision (commit only when asked), and PROVE is the gate that branch cleared to earn the merge. PATCH/FIX never reach the loop, so they run inline with no worktree.
|
|
167
|
+
|
|
166
168
|
A **single** incomplete cycle (or a PATCH/FIX) runs inline — there's nothing to isolate from, and spawning a subprocess would cost more than it saves.
|
|
167
169
|
|
|
168
170
|
**`--next-cycle` is internal.** It means "run one batch, tick boxes, exit — do **not** loop." Only `do-auto.sh` passes it; it is the recursion guard that keeps a fresh `/do` from re-entering the loop. A human never types it.
|
|
@@ -266,7 +268,7 @@ PATCH clears {1,2,3,8}. FEATURE clears all eight.
|
|
|
266
268
|
|
|
267
269
|
## Available to /do — the toolbox
|
|
268
270
|
|
|
269
|
-
**Scripts** (`.claude/scripts/`): `do-auto.sh` (*internal* — the context-isolated loop `/do` drives for multi-cycle plans) · `do-tier.sh` (tier + pruned spine + classifier + ceiling) · `do-folder.sh` (folder-aware verify/build) · `do-survey.sh` (reuse verdict) · `do-reconcile.sh` (substrate dim/verb/dead-name gate) · `do-analyze.sh` (deliverable↔cycle coverage gate) · `do-prove.sh` (surface-detect proof) · `do-smoke.sh` (deterministic outcome) · `w1-recon.ts` (prompt-cached recon) · `w4-rubric.ts` (cached parallel rubric).
|
|
271
|
+
**Scripts** (`.claude/scripts/`): `do-auto.sh` (*internal* — the context-isolated, worktree-isolated loop `/do` drives for multi-cycle plans; builds on branch `do/<slug>`, merges to trunk only on a human's say-so) · `do-tier.sh` (tier + pruned spine + classifier + ceiling) · `do-folder.sh` (folder-aware verify/build) · `do-survey.sh` (reuse verdict) · `do-reconcile.sh` (substrate dim/verb/dead-name gate) · `do-analyze.sh` (deliverable↔cycle coverage gate) · `do-prove.sh` (surface-detect proof) · `do-smoke.sh` (deterministic outcome) · `w1-recon.ts` (prompt-cached recon) · `w4-rubric.ts` (cached parallel rubric).
|
|
270
272
|
|
|
271
273
|
**Templates**: `text/template-frame.md` (promise) · `plans/template-spec.md` (design + pre-mortem + decisions) · `plans/template-todo.md` (plan + parallel budget + testing policy) · `plans/agent-template.md` (agent definition).
|
|
272
274
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# /skill-create — crystallise a cycle's learnings into a reusable skill
|
|
2
|
+
|
|
3
|
+
Run after a `/do` cycle closes (or any meaningful session). Reads the recent git
|
|
4
|
+
diff and the last `learnings.md` entry, then drafts a `SKILL.md` under
|
|
5
|
+
`.claude/skills/<slug>/` — so the pattern compounds instead of evaporating.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## When to invoke
|
|
10
|
+
|
|
11
|
+
- After `/do` closes a cycle and `learnings.md` has a new entry
|
|
12
|
+
- When you notice a pattern being repeated across sessions
|
|
13
|
+
- Explicitly: `/skill-create [slug]`
|
|
14
|
+
|
|
15
|
+
If `[slug]` is omitted, derive it from the most recent `learnings.md` entry
|
|
16
|
+
(the slug of the last `/do` cycle).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
**Step 1 — Gather raw material (zero LLM)**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Last learnings entry
|
|
26
|
+
tail -5 plans/learnings.md
|
|
27
|
+
|
|
28
|
+
# Recent cycle diff (what actually changed)
|
|
29
|
+
git diff HEAD~1 HEAD --stat
|
|
30
|
+
git diff HEAD~1 HEAD -- $(git diff HEAD~1 HEAD --name-only | grep -v 'plans/\|text/' | head -20)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Step 2 — Extract the pattern (Haiku · low)**
|
|
34
|
+
|
|
35
|
+
From the diff + learnings entry, identify:
|
|
36
|
+
- What recurring problem this cycle solved
|
|
37
|
+
- The specific shape of the solution (code pattern, file layout, command sequence)
|
|
38
|
+
- When it applies (trigger condition)
|
|
39
|
+
- When it doesn't (anti-patterns / exclusions)
|
|
40
|
+
|
|
41
|
+
Ignore one-off specifics (feature names, slugs). Keep the reusable skeleton.
|
|
42
|
+
|
|
43
|
+
**Step 3 — Write `.claude/skills/<slug>/SKILL.md`**
|
|
44
|
+
|
|
45
|
+
Use this template:
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
# <title> skill
|
|
49
|
+
|
|
50
|
+
<one-sentence description — what this skill does for the model>
|
|
51
|
+
|
|
52
|
+
## When to use
|
|
53
|
+
<trigger phrase or condition that should invoke this skill>
|
|
54
|
+
|
|
55
|
+
## Pattern
|
|
56
|
+
<the reusable shape — code snippet, command, or process>
|
|
57
|
+
|
|
58
|
+
## Don't
|
|
59
|
+
- <anti-pattern 1>
|
|
60
|
+
- <anti-pattern 2>
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
<minimal concrete example>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Step 4 — Wire it (if applicable)**
|
|
67
|
+
|
|
68
|
+
- If the skill has a natural trigger phrase, append a row to the skills table in
|
|
69
|
+
`.claude/CLAUDE.md` so it auto-loads.
|
|
70
|
+
- If it extends an existing skill, note the relation.
|
|
71
|
+
|
|
72
|
+
**Step 5 — Close**
|
|
73
|
+
|
|
74
|
+
Emit `signal("learning:harden", 1, "skill=<slug>")` and append one line to
|
|
75
|
+
`plans/learnings.md`:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
- YYYY-MM-DD · skill:<slug> · crystallised from <source-slug> · source=skill-create
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Rules
|
|
84
|
+
|
|
85
|
+
- **Extract, don't invent.** The pattern must be in the diff or learnings entry — no
|
|
86
|
+
generalising beyond what the cycle actually did.
|
|
87
|
+
- **One skill = one pattern.** If the cycle contains two independent patterns, create
|
|
88
|
+
two skills.
|
|
89
|
+
- **Lean.** A skill that fits in 30 lines is more likely to be read than one that
|
|
90
|
+
doesn't. Cut examples to the minimum that makes the pattern clear.
|
|
91
|
+
- **No cycle-specific names.** Slug and title describe the pattern, not the feature
|
|
92
|
+
that spawned it (`worktree-isolation`, not `do-auto-fix`).
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Close
|
|
97
|
+
|
|
98
|
+
After writing the skill file: `[ ] skill-create:<slug> done` appended to
|
|
99
|
+
`plans/learnings.md`, signal emitted, session stop-reflect will pick it up as
|
|
100
|
+
a new primitive on the next session start.
|
package/hooks/hooks.json
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
+
"PreToolUse": [
|
|
3
|
+
{
|
|
4
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"id": "hook:gate-guard",
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/gate-guard.sh\"",
|
|
10
|
+
"timeout": 5000,
|
|
11
|
+
"blocking": true
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
2
16
|
"PostToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "Read",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"id": "hook:read-tracker",
|
|
22
|
+
"type": "command",
|
|
23
|
+
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/read-tracker.sh\"",
|
|
24
|
+
"timeout": 3000
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
3
28
|
{
|
|
4
29
|
"matcher": "Write|Edit",
|
|
5
30
|
"hooks": [
|
|
6
31
|
{
|
|
32
|
+
"id": "hook:post-edit",
|
|
7
33
|
"type": "command",
|
|
8
34
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/post-edit-check.sh\"",
|
|
9
35
|
"timeout": 15000
|
|
@@ -14,6 +40,7 @@
|
|
|
14
40
|
"matcher": "Write|Edit|MultiEdit",
|
|
15
41
|
"hooks": [
|
|
16
42
|
{
|
|
43
|
+
"id": "hook:sync-todo-docs",
|
|
17
44
|
"type": "command",
|
|
18
45
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/sync-todo-docs.sh\"",
|
|
19
46
|
"timeout": 5000
|
|
@@ -24,6 +51,7 @@
|
|
|
24
51
|
"matcher": "Write|Edit|MultiEdit",
|
|
25
52
|
"hooks": [
|
|
26
53
|
{
|
|
54
|
+
"id": "hook:design-check",
|
|
27
55
|
"type": "command",
|
|
28
56
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/design-check.sh\"",
|
|
29
57
|
"timeout": 5000,
|
|
@@ -35,6 +63,7 @@
|
|
|
35
63
|
"matcher": "*",
|
|
36
64
|
"hooks": [
|
|
37
65
|
{
|
|
66
|
+
"id": "hook:tool-signal",
|
|
38
67
|
"type": "command",
|
|
39
68
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/tool-signal.sh\"",
|
|
40
69
|
"timeout": 3000
|
|
@@ -47,6 +76,7 @@
|
|
|
47
76
|
"matcher": "*",
|
|
48
77
|
"hooks": [
|
|
49
78
|
{
|
|
79
|
+
"id": "hook:task-complete",
|
|
50
80
|
"type": "command",
|
|
51
81
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/task-complete-verify.sh\"",
|
|
52
82
|
"timeout": 120000,
|
|
@@ -60,12 +90,14 @@
|
|
|
60
90
|
"matcher": "*",
|
|
61
91
|
"hooks": [
|
|
62
92
|
{
|
|
93
|
+
"id": "hook:session-end",
|
|
63
94
|
"type": "command",
|
|
64
95
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/session-end-verify.sh\"",
|
|
65
96
|
"timeout": 30000,
|
|
66
97
|
"blocking": false
|
|
67
98
|
},
|
|
68
99
|
{
|
|
100
|
+
"id": "hook:stop-reflect",
|
|
69
101
|
"type": "command",
|
|
70
102
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/stop-reflect.sh\"",
|
|
71
103
|
"timeout": 10000,
|
|
@@ -79,6 +111,7 @@
|
|
|
79
111
|
"matcher": "*",
|
|
80
112
|
"hooks": [
|
|
81
113
|
{
|
|
114
|
+
"id": "hook:session-start",
|
|
82
115
|
"type": "command",
|
|
83
116
|
"command": "bash \"$CLAUDE_PLUGIN_ROOT/hooks/scripts/session-start.sh\"",
|
|
84
117
|
"timeout": 5000,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared hook utilities. Source from any hook:
|
|
3
|
+
# source "$CLAUDE_PROJECT_DIR/.claude/hooks/lib/hook.sh"
|
|
4
|
+
#
|
|
5
|
+
# is_hook_disabled <id> — returns 0 (skip) or 1 (run)
|
|
6
|
+
# hook_session_key <json-arg> — stable per-session key for temp files
|
|
7
|
+
|
|
8
|
+
# ── Runtime toggles ──────────────────────────────────────────────────────────
|
|
9
|
+
#
|
|
10
|
+
# ECC_DISABLED_HOOKS=hook:gate-guard,hook:stop-reflect
|
|
11
|
+
# Comma-separated hook IDs to skip. Useful in CI or setup scripts.
|
|
12
|
+
#
|
|
13
|
+
# ECC_HOOK_PROFILE=minimal|standard (default: standard)
|
|
14
|
+
# minimal — skips signal emissions, reflect, session-end, post-edit lint
|
|
15
|
+
# standard — all hooks active
|
|
16
|
+
#
|
|
17
|
+
# Per-hook disable: ECC_GATEGUARD=off (alias for hook:gate-guard)
|
|
18
|
+
|
|
19
|
+
is_hook_disabled() {
|
|
20
|
+
local id="$1"
|
|
21
|
+
local profile="${ECC_HOOK_PROFILE:-standard}"
|
|
22
|
+
|
|
23
|
+
# Explicit disable list (comma-separated)
|
|
24
|
+
if [[ -n "${ECC_DISABLED_HOOKS:-}" ]]; then
|
|
25
|
+
local IFS=','
|
|
26
|
+
for entry in $ECC_DISABLED_HOOKS; do
|
|
27
|
+
# trim whitespace
|
|
28
|
+
entry="${entry#"${entry%%[![:space:]]*}"}"
|
|
29
|
+
entry="${entry%"${entry##*[![:space:]]}"}"
|
|
30
|
+
[[ "$entry" == "$id" ]] && return 0
|
|
31
|
+
done
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Profile-based skips
|
|
35
|
+
case "$profile" in
|
|
36
|
+
minimal)
|
|
37
|
+
case "$id" in
|
|
38
|
+
hook:tool-signal|hook:stop-reflect|hook:session-end|hook:post-edit) return 0 ;;
|
|
39
|
+
esac
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
# Per-hook env aliases
|
|
44
|
+
case "$id" in
|
|
45
|
+
hook:gate-guard)
|
|
46
|
+
local v="${ECC_GATEGUARD:-}"
|
|
47
|
+
[[ "$v" =~ ^(0|off|false|disabled|disable)$ ]] && return 0
|
|
48
|
+
;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
return 1 # not disabled — run
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Derive a stable per-session key from the hook JSON payload ($1).
|
|
55
|
+
# Uses session_id from JSON if present; falls back to a cksum of the
|
|
56
|
+
# project dir so the key is consistent across hook invocations in one
|
|
57
|
+
# session even when CLAUDE_SESSION_ID isn't set.
|
|
58
|
+
hook_session_key() {
|
|
59
|
+
local payload="${1:-}"
|
|
60
|
+
local sid
|
|
61
|
+
sid=$(printf '%s' "$payload" | jq -r '.session_id // empty' 2>/dev/null)
|
|
62
|
+
if [[ -n "$sid" ]]; then
|
|
63
|
+
# Sanitise to filesystem-safe chars
|
|
64
|
+
printf '%s' "$sid" | tr -dc 'a-zA-Z0-9_-' | cut -c1-48
|
|
65
|
+
return
|
|
66
|
+
fi
|
|
67
|
+
# Fallback: hash of project dir
|
|
68
|
+
printf '%s' "${CLAUDE_PROJECT_DIR:-$(pwd)}" | cksum | awk '{print $1}'
|
|
69
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# GATE-GUARD — PreToolUse(Edit|Write|MultiEdit): require Read before edit.
|
|
3
|
+
#
|
|
4
|
+
# Blocks the first attempt to Edit/Write an existing file that hasn't been
|
|
5
|
+
# Read in this session. Forces "understand before you change."
|
|
6
|
+
#
|
|
7
|
+
# Rules:
|
|
8
|
+
# - New files (Write creating a file that doesn't exist yet) → always allowed
|
|
9
|
+
# - Settings files (.claude/settings*.json) → always allowed
|
|
10
|
+
# - Subagent calls → allowed (parent session's reads cover the session)
|
|
11
|
+
# - File already in this session's read registry → allowed
|
|
12
|
+
# - Existing file NOT yet read → BLOCK with instructions
|
|
13
|
+
#
|
|
14
|
+
# Disable for the session: ECC_GATEGUARD=off
|
|
15
|
+
# Disable one-off: ECC_DISABLED_HOOKS=hook:gate-guard
|
|
16
|
+
# Skip globally in CI: add hook:gate-guard to ECC_DISABLED_HOOKS in env
|
|
17
|
+
|
|
18
|
+
# shellcheck source=lib/hook.sh
|
|
19
|
+
_HOOK_LIB="${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/lib"
|
|
20
|
+
source "$_HOOK_LIB/hook.sh"
|
|
21
|
+
is_hook_disabled "hook:gate-guard" && exit 0
|
|
22
|
+
|
|
23
|
+
PAYLOAD="${1:-}"
|
|
24
|
+
[[ -z "$PAYLOAD" ]] && exit 0
|
|
25
|
+
|
|
26
|
+
TOOL=$(printf '%s' "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
27
|
+
case "$TOOL" in Edit|Write|MultiEdit) ;; *) exit 0 ;; esac
|
|
28
|
+
|
|
29
|
+
# Subagents: parent session already passed the gate for these files
|
|
30
|
+
SUBAGENT=$(printf '%s' "$PAYLOAD" | jq -r '.parent_tool_use_id // .parentToolUseId // empty' 2>/dev/null)
|
|
31
|
+
[[ -n "$SUBAGENT" ]] && exit 0
|
|
32
|
+
|
|
33
|
+
SESSION_KEY=$(hook_session_key "$PAYLOAD")
|
|
34
|
+
READS_FILE="/tmp/oneie-gate-reads-${SESSION_KEY}"
|
|
35
|
+
|
|
36
|
+
_deny() {
|
|
37
|
+
# Claude Code PreToolUse block format
|
|
38
|
+
printf '%s' "$(jq -nc --arg msg "$1" \
|
|
39
|
+
'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$msg}}')"
|
|
40
|
+
exit 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_is_settings() {
|
|
44
|
+
[[ "$1" =~ \.claude/settings[^/]*\.json$ ]]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_was_read() {
|
|
48
|
+
local fp="$1"
|
|
49
|
+
[[ -f "$READS_FILE" ]] && grep -qF "$fp" "$READS_FILE" 2>/dev/null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_block_msg() {
|
|
53
|
+
local fp="$1" verb="$2"
|
|
54
|
+
printf '[Gate-Guard] Read %s before you %s it.\n\nUse the Read tool on this file first to prove you understand it, then retry.\n\nTo disable for this session: set ECC_GATEGUARD=off\nTo disable permanently: add hook:gate-guard to ECC_DISABLED_HOOKS in your environment.' \
|
|
55
|
+
"$fp" "$verb"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
check() {
|
|
59
|
+
local fp="$1" tool="$2"
|
|
60
|
+
[[ -z "$fp" ]] && return 0
|
|
61
|
+
_is_settings "$fp" && return 0
|
|
62
|
+
# New file being created — no prior read needed
|
|
63
|
+
[[ "$tool" == "Write" && ! -f "$fp" ]] && return 0
|
|
64
|
+
# Previously read this session — allowed
|
|
65
|
+
_was_read "$fp" && return 0
|
|
66
|
+
# Existing file not read → block
|
|
67
|
+
[[ -f "$fp" ]] && { _deny "$(_block_msg "$fp" "$(printf '%s' "$tool" | tr '[:upper:]' '[:lower:]')")"; }
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case "$TOOL" in
|
|
72
|
+
Edit|Write)
|
|
73
|
+
FILE=$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
74
|
+
check "$FILE" "$TOOL"
|
|
75
|
+
;;
|
|
76
|
+
MultiEdit)
|
|
77
|
+
while IFS= read -r fp; do
|
|
78
|
+
[[ -z "$fp" ]] && continue
|
|
79
|
+
check "$fp" "Edit"
|
|
80
|
+
done < <(printf '%s' "$PAYLOAD" | jq -r '.tool_input.edits[].file_path // empty' 2>/dev/null)
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
|
|
84
|
+
exit 0
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# READ-TRACKER — PostToolUse(Read): record file_path as read this session.
|
|
3
|
+
#
|
|
4
|
+
# Feeds gate-guard.sh: once a file appears in the reads registry,
|
|
5
|
+
# Edit/Write on that file is allowed. Disable both with ECC_GATEGUARD=off
|
|
6
|
+
# or ECC_DISABLED_HOOKS=hook:gate-guard.
|
|
7
|
+
#
|
|
8
|
+
# Non-blocking, always exits 0. Failure to write the registry is silent
|
|
9
|
+
# (gate-guard falls back to allow on missing registry).
|
|
10
|
+
|
|
11
|
+
# shellcheck source=lib/hook.sh
|
|
12
|
+
_HOOK_LIB="${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/lib"
|
|
13
|
+
source "$_HOOK_LIB/hook.sh"
|
|
14
|
+
is_hook_disabled "hook:gate-guard" && exit 0
|
|
15
|
+
|
|
16
|
+
PAYLOAD="${1:-}"
|
|
17
|
+
[[ -z "$PAYLOAD" ]] && exit 0
|
|
18
|
+
|
|
19
|
+
FILE=$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
20
|
+
[[ -z "$FILE" ]] && exit 0
|
|
21
|
+
|
|
22
|
+
SESSION_KEY=$(hook_session_key "$PAYLOAD")
|
|
23
|
+
READS_FILE="/tmp/oneie-gate-reads-${SESSION_KEY}"
|
|
24
|
+
|
|
25
|
+
# Append absolute path — idempotent (duplicates are fine, grep is fast)
|
|
26
|
+
echo "$FILE" >> "$READS_FILE" 2>/dev/null || true
|
|
27
|
+
exit 0
|
|
@@ -73,6 +73,18 @@ check_drift '^packages/sdk/src/' 'packages/sdk/CLAUDE.md'
|
|
|
73
73
|
check_drift '^one\.ie/web/src/' 'one.ie/web/CLAUDE.md'
|
|
74
74
|
check_drift '^api/src/' 'api/CLAUDE.md'
|
|
75
75
|
|
|
76
|
+
# (4) Completed /do cycle → suggest /skill-create to crystallise the pattern.
|
|
77
|
+
# Heuristic: learnings.md was touched AND W3 edited >=3 non-plan/non-text files.
|
|
78
|
+
if echo "$CHANGED" | grep -qF 'plans/learnings.md'; then
|
|
79
|
+
CYCLE_FILES=$(echo "$CHANGED" | grep -vE '^plans/|^text/|^\.claude/' | wc -l | awk '{print $1}')
|
|
80
|
+
if [ "$CYCLE_FILES" -ge 3 ]; then
|
|
81
|
+
# Extract the cycle slug from the last learnings entry
|
|
82
|
+
CYCLE_SLUG=$(grep -oE 'cycle [0-9]+|· [a-z][a-z0-9-]+' plans/learnings.md 2>/dev/null | tail -1 | sed 's/^· //')
|
|
83
|
+
BUF+="- **skill-create** cycle closed with ${CYCLE_FILES} file(s) changed — run \`/skill-create ${CYCLE_SLUG}\` to crystallise the pattern as a reusable skill.\n"
|
|
84
|
+
PROPOSALS=$((PROPOSALS + 1))
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
76
88
|
# Only write the file if we found something worth reviewing
|
|
77
89
|
if [ "$PROPOSALS" -gt 0 ]; then
|
|
78
90
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneie/claude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "ONE Claude Code plugin — /do lifecycle, W1-W4 BUILD engine, ONE substrate via MCP, signals, and rules",
|
|
5
5
|
"files": [
|
|
6
6
|
".claude-plugin",
|
|
@@ -19,11 +19,27 @@
|
|
|
19
19
|
"@oneie/cli": ">=0.2.0"
|
|
20
20
|
},
|
|
21
21
|
"peerDependenciesMeta": {
|
|
22
|
-
"@oneie/sdk": {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
"@oneie/sdk": {
|
|
23
|
+
"optional": true
|
|
24
|
+
},
|
|
25
|
+
"@oneie/mcp": {
|
|
26
|
+
"optional": true
|
|
27
|
+
},
|
|
28
|
+
"@oneie/cli": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
25
31
|
},
|
|
26
|
-
"keywords": [
|
|
32
|
+
"keywords": [
|
|
33
|
+
"claude-code",
|
|
34
|
+
"plugin",
|
|
35
|
+
"do",
|
|
36
|
+
"lifecycle",
|
|
37
|
+
"mcp",
|
|
38
|
+
"signals",
|
|
39
|
+
"typedb",
|
|
40
|
+
"agents",
|
|
41
|
+
"one"
|
|
42
|
+
],
|
|
27
43
|
"license": "SEE LICENSE IN https://one.ie/free-license",
|
|
28
44
|
"repository": {
|
|
29
45
|
"type": "git",
|
package/scripts/do-auto.sh
CHANGED
|
@@ -40,15 +40,73 @@ fi
|
|
|
40
40
|
# claude subprocess uses --dangerously-skip-permissions because it is non-interactive
|
|
41
41
|
# — it cannot prompt the human for tool approvals. The workspace is the isolation
|
|
42
42
|
# boundary. Do not expose this script to untrusted input or run it in shared environments.
|
|
43
|
-
|
|
44
|
-
TRUST=".do-trust.json"
|
|
43
|
+
[ -f "plans/${SLUG}-todo.md" ] || { echo "[do-auto] plans/${SLUG}-todo.md not found — run /do $SLUG first" >&2; exit 1; }
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
# ── Worktree isolation (one per plan) ────────────────────────────────────────
|
|
46
|
+
# The loop's existence IS the trigger: do-auto only runs for multi-cycle plans
|
|
47
|
+
# (>=2 incomplete cycles) = exactly the FEATURE/SCHEMA work that must not land
|
|
48
|
+
# half-built on trunk. So every loop builds in its own worktree on branch
|
|
49
|
+
# do/<slug>, leaving trunk green until a human merges. No tier plumbing, no flag.
|
|
50
|
+
#
|
|
51
|
+
# Why a worktree and not just a branch: the main session keeps observing trunk
|
|
52
|
+
# while the loop's subprocesses build in a separate checkout — they never fight
|
|
53
|
+
# over HEAD or the working tree. A halt leaves a clean, inspectable WIP branch.
|
|
54
|
+
#
|
|
55
|
+
# trunk (BASE) ── never moves during the loop
|
|
56
|
+
# └─ do/<slug> (worktree .do-worktrees/<slug>) ── every cycle commits here
|
|
57
|
+
# plan complete → report `git merge do/<slug>` (human lands it — never auto)
|
|
58
|
+
# halt → worktree persists → re-run /do <slug> resumes in place
|
|
59
|
+
BASE="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
|
|
60
|
+
BR="do/${SLUG}"
|
|
61
|
+
WT=".do-worktrees/${SLUG}"
|
|
62
|
+
|
|
63
|
+
_setup_worktree() {
|
|
64
|
+
# Idempotent: reuse an existing worktree (resume), else attach one to an
|
|
65
|
+
# existing branch (prior halt), else create branch+worktree fresh from HEAD.
|
|
66
|
+
if git -C "$WT" rev-parse --git-dir >/dev/null 2>&1; then
|
|
67
|
+
echo "[do-auto] resuming worktree $WT on $BR"
|
|
68
|
+
elif git show-ref --verify --quiet "refs/heads/$BR"; then
|
|
69
|
+
echo "[do-auto] re-attaching worktree $WT to existing branch $BR"
|
|
70
|
+
git worktree add "$WT" "$BR" >/dev/null
|
|
71
|
+
else
|
|
72
|
+
echo "[do-auto] creating worktree $WT on new branch $BR (from $BASE)"
|
|
73
|
+
git worktree add -b "$BR" "$WT" HEAD >/dev/null
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Carry the plan's own artifacts into the worktree. The spine walk may have
|
|
77
|
+
# just written promise/spec/todo uncommitted in the main tree; a worktree cut
|
|
78
|
+
# from HEAD wouldn't have them. Copy only this slug's files — never unrelated
|
|
79
|
+
# working-tree changes — then commit them as the branch baseline.
|
|
80
|
+
local f changed=0
|
|
81
|
+
for f in "plans/${SLUG}.md" "plans/${SLUG}-todo.md" "text/${SLUG}.md" ".w4-improvements.json"; do
|
|
82
|
+
if [ -f "$f" ]; then
|
|
83
|
+
mkdir -p "$WT/$(dirname "$f")"
|
|
84
|
+
if ! cmp -s "$f" "$WT/$f" 2>/dev/null; then cp "$f" "$WT/$f"; changed=1; fi
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
if [ "$changed" -eq 1 ]; then
|
|
88
|
+
git -C "$WT" add -A
|
|
89
|
+
git -C "$WT" commit -q -m "do(${SLUG}): sync spine artifacts" 2>/dev/null || true
|
|
90
|
+
fi
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if ! $DRY_RUN; then
|
|
94
|
+
_setup_worktree
|
|
95
|
+
# All loop state now lives in the worktree — read the boxes the subprocess ticks.
|
|
96
|
+
TODO="$WT/plans/${SLUG}-todo.md"
|
|
97
|
+
TRUST="$WT/.do-trust.json"
|
|
98
|
+
else
|
|
99
|
+
TODO="plans/${SLUG}-todo.md"
|
|
100
|
+
TRUST=".do-trust.json"
|
|
101
|
+
fi
|
|
47
102
|
|
|
48
103
|
_remaining() {
|
|
49
104
|
# Count cycles in the Status section that are open ([ ]) or in-flight ([~]) — i.e. not [x].
|
|
50
105
|
# Pattern starts with '\[' (not '-') so grep never mistakes it for an option flag.
|
|
51
|
-
grep -
|
|
106
|
+
# grep -c already prints a count (0 on no match) but exits 1 then — capture it so
|
|
107
|
+
# the `|| true` swallows the exit without appending a second "0" to the output.
|
|
108
|
+
local n; n=$(grep -cE '\[[ ~]\] C[0-9]+' "$TODO" 2>/dev/null) || true
|
|
109
|
+
echo "${n:-0}"
|
|
52
110
|
}
|
|
53
111
|
|
|
54
112
|
_trust() {
|
|
@@ -82,6 +140,7 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
82
140
|
if [ "$stall" -ge 2 ]; then
|
|
83
141
|
echo "[do-auto] no progress for 2 iterations (${remaining} cycle(s) stuck) — halting." >&2
|
|
84
142
|
echo "[do-auto] The last cycle ticked no checkbox. Inspect $TODO, fix the blocker, then re-run /do $SLUG." >&2
|
|
143
|
+
echo "[do-auto] WIP preserved on branch $BR (worktree $WT)." >&2
|
|
85
144
|
exit 1
|
|
86
145
|
fi
|
|
87
146
|
else
|
|
@@ -92,6 +151,7 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
92
151
|
trust=$(_trust)
|
|
93
152
|
if [ "$trust" = "cautious" ]; then
|
|
94
153
|
echo "[do-auto] trust=cautious — halting. Fix the issue, then re-run /do $SLUG."
|
|
154
|
+
echo "[do-auto] WIP preserved on branch $BR (worktree $WT)."
|
|
95
155
|
exit 1
|
|
96
156
|
fi
|
|
97
157
|
|
|
@@ -107,11 +167,31 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
107
167
|
break
|
|
108
168
|
fi
|
|
109
169
|
|
|
110
|
-
# Fresh context
|
|
111
|
-
|
|
170
|
+
# Fresh context, isolated tree: the subprocess runs INSIDE the worktree, so
|
|
171
|
+
# every edit/box-tick/state-write lands on branch $BR — trunk is never touched.
|
|
172
|
+
( cd "$WT" && claude --dangerously-skip-permissions -p "/do $SLUG --next-cycle" )
|
|
173
|
+
|
|
174
|
+
# Commit the cycle on its branch. A cycle closes with a passing rubric, so it
|
|
175
|
+
# is the natural commit unit — and committed progress survives a later halt.
|
|
176
|
+
if [ -n "$(git -C "$WT" status --porcelain)" ]; then
|
|
177
|
+
git -C "$WT" add -A
|
|
178
|
+
git -C "$WT" commit -q -m "do(${SLUG}): cycle ${i}" || true
|
|
179
|
+
fi
|
|
112
180
|
done
|
|
113
181
|
|
|
182
|
+
if $DRY_RUN; then exit 0; fi
|
|
183
|
+
|
|
114
184
|
if [ "$i" -ge "$MAX_CYCLES" ]; then
|
|
115
185
|
echo "[do-auto] hit --max-cycles $MAX_CYCLES — halting"
|
|
186
|
+
echo "[do-auto] WIP preserved on branch $BR (worktree $WT)."
|
|
116
187
|
exit 1
|
|
117
188
|
fi
|
|
189
|
+
|
|
190
|
+
# Plan complete. The branch holds every cycle, proven and committed; trunk is
|
|
191
|
+
# untouched. Landing it is a human decision (commit/push only when asked), so we
|
|
192
|
+
# report the merge instead of running it — the worktree stays for inspection.
|
|
193
|
+
echo ""
|
|
194
|
+
echo "[do-auto] ✓ plan complete on branch $BR — trunk ($BASE) untouched."
|
|
195
|
+
echo "[do-auto] land: git merge --no-ff $BR # from $BASE"
|
|
196
|
+
echo "[do-auto] inspect: git -C $WT log --oneline $BASE..$BR"
|
|
197
|
+
echo "[do-auto] discard: git worktree remove $WT && git branch -D $BR"
|