@oneie/claude 0.3.0 → 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.
@@ -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.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": { "optional": true },
23
- "@oneie/mcp": { "optional": true },
24
- "@oneie/cli": { "optional": true }
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": ["claude-code", "plugin", "do", "lifecycle", "mcp", "signals", "typedb", "agents", "one"],
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",