@neikyun/ciel 6.8.0 → 6.9.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/assets/.claude/hooks/memory-bootstrap.sh +287 -0
- package/assets/.claude/hooks/memory-engine.py +718 -0
- package/assets/.claude/hooks/pre-agent-gate.sh +55 -0
- package/assets/.claude/hooks/pre-tool-write.sh +83 -0
- package/assets/.claude/hooks/session-start.sh +132 -0
- package/assets/.claude/hooks/session-version-check.sh +14 -0
- package/assets/.claude/hooks/user-prompt-submit.sh +112 -0
- package/assets/.claude/settings.json +4 -4
- package/assets/commands/ciel-audit.md +78 -17
- package/assets/commands/ciel-memory-bootstrap.md +160 -0
- package/assets/commands/ciel-status.md +1 -1
- package/assets/platforms/opencode/.opencode/agents/ciel-explorer.md +1 -1
- package/assets/platforms/opencode/.opencode/agents/ciel-improver.md +2 -2
- package/assets/platforms/opencode/.opencode/agents/ciel-researcher.md +1 -1
- package/assets/platforms/opencode/.opencode/commands/ciel-audit.md +40 -22
- package/dist/cli/claude.d.ts.map +1 -1
- package/dist/cli/claude.js +28 -13
- package/dist/cli/claude.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.cjs +11 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Ciel — PreToolUse hook for Agent (subagent type gate)
|
|
3
|
+
# Trigger: PreToolUse on Agent
|
|
4
|
+
# Purpose: block Agent dispatches that don't use a ciel-* subagent_type.
|
|
5
|
+
# Generic agents bypass Haiku model, call caps, and researcher waterfall
|
|
6
|
+
# → cost 20K-160K tokens vs ~5K for a constrained Ciel agent.
|
|
7
|
+
#
|
|
8
|
+
# Escape hatch: include [CIEL_GATE_BYPASS] anywhere in the prompt to allow
|
|
9
|
+
# a non-ciel agent through (e.g. legitimate one-off native dispatch).
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
input_json=""
|
|
14
|
+
if [ ! -t 0 ]; then
|
|
15
|
+
input_json=$(cat)
|
|
16
|
+
fi
|
|
17
|
+
[ -z "$input_json" ] && exit 0
|
|
18
|
+
|
|
19
|
+
parsed=$(echo "$input_json" | python3 -c "
|
|
20
|
+
import json, sys
|
|
21
|
+
try:
|
|
22
|
+
d = json.load(sys.stdin)
|
|
23
|
+
tool_input = d.get('tool_input', {})
|
|
24
|
+
subagent_type = tool_input.get('subagent_type', '')
|
|
25
|
+
prompt = tool_input.get('prompt', '')
|
|
26
|
+
print(subagent_type + '\t' + prompt[:200])
|
|
27
|
+
except Exception:
|
|
28
|
+
print('\t')
|
|
29
|
+
" 2>/dev/null)
|
|
30
|
+
|
|
31
|
+
subagent_type="${parsed%%$'\t'*}"
|
|
32
|
+
prompt_head="${parsed#*$'\t'}"
|
|
33
|
+
|
|
34
|
+
# Allow explicit bypass
|
|
35
|
+
if echo "$prompt_head" | grep -q "\[CIEL_GATE_BYPASS\]"; then
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Allow ciel-* subagent types
|
|
40
|
+
if [[ "$subagent_type" == ciel-* ]]; then
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Block everything else — generic or non-ciel subagent_type
|
|
45
|
+
label="${subagent_type:-<missing>}"
|
|
46
|
+
python3 -c "
|
|
47
|
+
import json
|
|
48
|
+
print(json.dumps({
|
|
49
|
+
'hookSpecificOutput': {
|
|
50
|
+
'hookEventName': 'PreToolUse',
|
|
51
|
+
'permissionDecision': 'deny',
|
|
52
|
+
'permissionDecisionReason': '[CIEL AGENT GATE] Blocked: subagent_type=\"${label}\" is not a Ciel agent. Re-dispatch with subagent_type=\"ciel-researcher\" | \"ciel-explorer\" | \"ciel-critic\" | \"ciel-improver\". Generic agents bypass Haiku model, call caps, and researcher waterfall early-exit. Add [CIEL_GATE_BYPASS] to prompt to force-allow a non-Ciel dispatch.'
|
|
53
|
+
}
|
|
54
|
+
}))"
|
|
55
|
+
exit 0
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Ciel — PreToolUse hook for Write/Edit
|
|
3
|
+
# Trigger: PreToolUse on Write|Edit
|
|
4
|
+
# Purpose: inject faire-gatekeeper + dispatch gate + pipeline reminders before code write
|
|
5
|
+
# Critical files get additional stride-analyzer hint
|
|
6
|
+
# Always exits 0 (never blocks), outputs reminders via stderr (reliable channel)
|
|
7
|
+
# Dispatch counter: /tmp/ciel_dispatched set by SubagentStart hooks (ciel-researcher/explorer)
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
|
|
11
|
+
FILE_PATH=$(echo "$INPUT" | python3 -c "
|
|
12
|
+
import sys, json
|
|
13
|
+
try:
|
|
14
|
+
d = json.load(sys.stdin)
|
|
15
|
+
tip = d.get('tool_input', {})
|
|
16
|
+
print(tip.get('file_path', tip.get('path', '')))
|
|
17
|
+
except:
|
|
18
|
+
print('')
|
|
19
|
+
" 2>/dev/null || echo "")
|
|
20
|
+
|
|
21
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
22
|
+
|
|
23
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-}"
|
|
24
|
+
|
|
25
|
+
# === DISPATCH GATE CHECK ===
|
|
26
|
+
# /tmp/ciel_dispatched is created by SubagentStart hooks when ciel-researcher/explorer dispatch
|
|
27
|
+
DISPATCHED=0
|
|
28
|
+
DISPATCH_FLAG="/tmp/ciel_dispatched"
|
|
29
|
+
if [ -f "$DISPATCH_FLAG" ]; then
|
|
30
|
+
DISPATCHED=1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# === FILE TRACK COUNT (RELIRE GATE) ===
|
|
34
|
+
COUNT=0
|
|
35
|
+
if [ -n "$PROJECT_DIR" ] && [ -f "$PROJECT_DIR/.ciel/tracked-files.json" ]; then
|
|
36
|
+
COUNT=$(CIEL_PATH="$PROJECT_DIR/.ciel/tracked-files.json" python3 -c "
|
|
37
|
+
import json, os
|
|
38
|
+
try: print(len(json.load(open(os.environ['CIEL_PATH']))))
|
|
39
|
+
except: print(0)
|
|
40
|
+
" 2>/dev/null || echo "0")
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Build warnings
|
|
44
|
+
WARNINGS=""
|
|
45
|
+
|
|
46
|
+
# Dispatch gate warning (no dispatched agents on non-trivial write)
|
|
47
|
+
if [ "$DISPATCHED" -eq 0 ] && [ "$COUNT" -ge 1 ]; then
|
|
48
|
+
WARNINGS="${WARNINGS}[DISPATCH GATE] WARNING: Writing file ${FILE_PATH} without prior Task() dispatch (ciel-researcher + ciel-explorer). Was this classified as Trivial? If Standard+, dispatch subagents BEFORE writing code."
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# RELIRE gate warning
|
|
52
|
+
if [ "${COUNT:-0}" -ge 2 ] 2>/dev/null; then
|
|
53
|
+
PIPELINE_WARN=" | CIEL PIPELINE: ${COUNT} file(s) edited. Have researcher+explorer been dispatched? If 3+ files: ciel-critic MODE=RELIRE required before merge."
|
|
54
|
+
WARNINGS="${WARNINGS}${PIPELINE_WARN}"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# If only dispatch gate fires, prefix to std err
|
|
58
|
+
if [ -n "$WARNINGS" ]; then
|
|
59
|
+
echo "[CIEL PRE-WRITE]" >&2
|
|
60
|
+
echo "$WARNINGS" | while IFS= read -r line; do
|
|
61
|
+
echo " $line" >&2
|
|
62
|
+
done
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# === FAIRE GATE REMINDER ===
|
|
66
|
+
# Skip non-code files
|
|
67
|
+
if ! echo "$FILE_PATH" | grep -qE '\.(kt|java|ts|tsx|js|jsx|py|go|rs|rb|php|cs|cpp|c|swift|scala|vue|svelte|sql|sh|json|yaml|yml|toml)$'; then
|
|
68
|
+
exit 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Check if file is critical path
|
|
72
|
+
CRITICAL=false
|
|
73
|
+
if echo "$FILE_PATH" | grep -qiE '(auth|Auth|security|Security|Token|Session|Password|Secret)'; then
|
|
74
|
+
CRITICAL=true
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if $CRITICAL; then
|
|
78
|
+
echo " [CIEL] Critical path: invoke faire-gatekeeper, stride-analyzer must have run, test-first (RED)." >&2
|
|
79
|
+
else
|
|
80
|
+
echo " [CIEL] Standard path: invoke faire-gatekeeper (alternatives, idiomatic, quality, removal, test-first)." >&2
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
exit 0
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Ciel — SessionStart hook
|
|
3
|
+
# Trigger: session begins or resumes
|
|
4
|
+
# Purpose: print Ciel banner, load overlay context, set TRACE_ID for eval logging
|
|
5
|
+
# Never blocks (exit 0 always). Stdout is added to Claude's context.
|
|
6
|
+
|
|
7
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
8
|
+
CWD=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('cwd', ''))" 2>/dev/null || pwd)
|
|
9
|
+
# python3 succeeds with an empty string when stdin JSON lacks a 'cwd' key — fall through to pwd.
|
|
10
|
+
[ -z "$CWD" ] && CWD="$(pwd)"
|
|
11
|
+
|
|
12
|
+
# Detect overlay presence
|
|
13
|
+
OVERLAY=""
|
|
14
|
+
for candidate in "$CWD/ciel-overlay.md" "$CWD/.claude/ciel-overlay.md"; do
|
|
15
|
+
if [[ -f "$candidate" ]]; then
|
|
16
|
+
OVERLAY="$candidate"
|
|
17
|
+
break
|
|
18
|
+
fi
|
|
19
|
+
done
|
|
20
|
+
|
|
21
|
+
# Reset session-scoped edit tracker so META/RELIRE gates don't bleed across sessions
|
|
22
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -f "$CLAUDE_PROJECT_DIR/.ciel/tracked-files.json" ]; then
|
|
23
|
+
echo "[]" > "$CLAUDE_PROJECT_DIR/.ciel/tracked-files.json" 2>/dev/null || true
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Generate TRACE_ID for this session (used by eval logging)
|
|
27
|
+
TRACE_ID=$(date -u +%Y%m%dT%H%M%SZ)-$$
|
|
28
|
+
export CIEL_TRACE_ID="$TRACE_ID"
|
|
29
|
+
|
|
30
|
+
# Resolve Ciel version at runtime (single source of truth, no hardcoded drift).
|
|
31
|
+
# Fallback chain: project sentinel → user sentinel → npm package → marketplace plugin → repo VERSION → unknown.
|
|
32
|
+
# Note: $HOME/.ciel/version is "last writer wins" across npm installs from different projects —
|
|
33
|
+
# project sentinel ($CWD/.ciel/version) is authoritative when present.
|
|
34
|
+
_resolve_ciel_version() {
|
|
35
|
+
local v=""
|
|
36
|
+
for f in \
|
|
37
|
+
"$CWD/.ciel/version" \
|
|
38
|
+
"$HOME/.ciel/version" \
|
|
39
|
+
"$HOME/.claude/plugins/ciel/package.json" \
|
|
40
|
+
"$HOME/.claude/plugins/ciel/.claude-plugin/plugin.json" \
|
|
41
|
+
"$(dirname "$0")/../../VERSION" \
|
|
42
|
+
"$(dirname "$0")/../VERSION"; do
|
|
43
|
+
# Skip entries that resolved against an empty $CWD/$HOME (e.g., "/.ciel/version").
|
|
44
|
+
case "$f" in /.ciel/*|/.claude/*) continue ;; esac
|
|
45
|
+
[ -r "$f" ] || continue
|
|
46
|
+
case "$f" in
|
|
47
|
+
*.json)
|
|
48
|
+
# Anchor to line-start whitespace so we only match top-level "version",
|
|
49
|
+
# not nested keys like "schema_version" or `"version"` deeper in the doc.
|
|
50
|
+
v=$(sed -n 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$f" 2>/dev/null | head -1)
|
|
51
|
+
;;
|
|
52
|
+
*)
|
|
53
|
+
v=$(tr -d '[:space:]' <"$f" 2>/dev/null)
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
[ -n "$v" ] && { echo "$v"; return; }
|
|
57
|
+
done
|
|
58
|
+
echo "unknown"
|
|
59
|
+
}
|
|
60
|
+
CIEL_VERSION="$(_resolve_ciel_version)"
|
|
61
|
+
MSG="CIEL v${CIEL_VERSION} — Skills-first deep-reasoning active. "
|
|
62
|
+
if [[ -n "$OVERLAY" ]]; then
|
|
63
|
+
MSG+="Overlay loaded: $OVERLAY. "
|
|
64
|
+
else
|
|
65
|
+
MSG+="No overlay found at $CWD/ciel-overlay.md — create one for project-specific rules. "
|
|
66
|
+
fi
|
|
67
|
+
MSG+="Trace ID: $TRACE_ID. Principle: Understand before generating. Verify before claiming done."
|
|
68
|
+
|
|
69
|
+
# ─── Cued-recall: surface relevant memories ──────────────────────────────────
|
|
70
|
+
# If a memory corpus exists, list active (non-stale) memories so the model
|
|
71
|
+
# knows what cues are available. Full content read on-demand. See ADR-0001.
|
|
72
|
+
MEMORY_INDEX="$CWD/.ciel/memory/index.json"
|
|
73
|
+
if [[ -f "$MEMORY_INDEX" ]]; then
|
|
74
|
+
MEMORY_SUMMARY=$(MEMORY_INDEX="$MEMORY_INDEX" python3 -c "
|
|
75
|
+
import json, os
|
|
76
|
+
try:
|
|
77
|
+
with open(os.environ['MEMORY_INDEX']) as f:
|
|
78
|
+
idx = json.load(f)
|
|
79
|
+
mems = idx.get('memories', {})
|
|
80
|
+
active = [(mid, m) for mid, m in mems.items() if not m.get('stale')]
|
|
81
|
+
if not active:
|
|
82
|
+
print('')
|
|
83
|
+
else:
|
|
84
|
+
# Sort by trigger_count desc, then by last_triggered desc
|
|
85
|
+
active.sort(key=lambda x: (-(x[1].get('trigger_count') or 0), x[1].get('last_triggered') or ''), reverse=False)
|
|
86
|
+
active.sort(key=lambda x: -(x[1].get('trigger_count') or 0))
|
|
87
|
+
top = active[:10]
|
|
88
|
+
lines = [f\" [{mid}, {m.get('trigger_count', 0)}x] {m.get('title', '?')}\" for mid, m in top]
|
|
89
|
+
total = len(active)
|
|
90
|
+
more = f' (+{total - len(top)} more)' if total > len(top) else ''
|
|
91
|
+
print(f'Cued-recall memory active ({total} memories{more}):\\n' + '\\n'.join(lines))
|
|
92
|
+
except Exception:
|
|
93
|
+
print('')
|
|
94
|
+
" 2>/dev/null || echo "")
|
|
95
|
+
if [[ -n "$MEMORY_SUMMARY" ]]; then
|
|
96
|
+
MSG+=$'\n'"$MEMORY_SUMMARY"
|
|
97
|
+
MSG+=$'\n'"Memories auto-inject when path/symbol/intent cues match. Read full content from .ciel/memory/{episodes,concepts,guards}/ when relevant."
|
|
98
|
+
fi
|
|
99
|
+
elif [[ -d "$CWD/.ciel" ]] || [[ -f "$CWD/.claude/settings.json" ]] || [[ -f "$CWD/opencode.json" ]] || [[ -f "$CWD/ciel-overlay.md" ]]; then
|
|
100
|
+
# Only suggest bootstrap if Ciel is actually installed in this project (not
|
|
101
|
+
# any random repo with a CLAUDE.md). Markers checked: .ciel/ dir, .claude
|
|
102
|
+
# settings, opencode config, or an explicit Ciel overlay.
|
|
103
|
+
MSG+=$'\n'"No cued-recall memory yet. Run /ciel-memory-bootstrap to scan project for ingestable tribal docs (lessons.md, ciel-overlay.md, .claude/rules/, etc.)."
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# ─── Update check (throttled to once per 24h, never blocks) ──────────────────
|
|
107
|
+
# Fetches GitHub VERSION non-blocking (max 2s). Any failure → silent skip.
|
|
108
|
+
MANIFEST="$HOME/.ciel/manifest.json"
|
|
109
|
+
LAST_CHECK="$HOME/.ciel/.last-update-check"
|
|
110
|
+
if [[ -f "$MANIFEST" ]]; then
|
|
111
|
+
STALE=false
|
|
112
|
+
if [[ ! -f "$LAST_CHECK" ]]; then
|
|
113
|
+
STALE=true
|
|
114
|
+
elif find "$LAST_CHECK" -mmin +1440 2>/dev/null | grep -q .; then
|
|
115
|
+
STALE=true
|
|
116
|
+
fi
|
|
117
|
+
if $STALE; then
|
|
118
|
+
LOCAL_VER=$(grep -oE '"version":[[:space:]]*"[^"]+"' "$MANIFEST" 2>/dev/null \
|
|
119
|
+
| head -1 | sed 's/.*"\([^"]*\)".*/\1/')
|
|
120
|
+
REMOTE_VER=$(curl -fsSL --max-time 2 \
|
|
121
|
+
https://raw.githubusercontent.com/KaosKyun/Ciel/main/VERSION 2>/dev/null \
|
|
122
|
+
| tr -d '[:space:]')
|
|
123
|
+
if [[ -n "$LOCAL_VER" && -n "$REMOTE_VER" && "$LOCAL_VER" != "$REMOTE_VER" ]]; then
|
|
124
|
+
MSG+=" [UPDATE] Ciel v$LOCAL_VER → v$REMOTE_VER available. Run /ciel-update."
|
|
125
|
+
fi
|
|
126
|
+
mkdir -p "$HOME/.ciel" 2>/dev/null || true
|
|
127
|
+
touch "$LAST_CHECK" 2>/dev/null || true
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
echo "$MSG"
|
|
132
|
+
exit 0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Ciel — Session-start version check
|
|
3
|
+
# Outputs to stderr so the user sees the notification.
|
|
4
|
+
# Non-blocking: silent on fetch failure (offline/proxy).
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
8
|
+
LOCAL_VERSION=$(cat "$PROJECT_DIR/VERSION" 2>/dev/null || echo "0.0.0")
|
|
9
|
+
|
|
10
|
+
REMOTE_VERSION=$(curl -fsSL --connect-timeout 3 "https://raw.githubusercontent.com/KaosKyun/Ciel/main/VERSION" 2>/dev/null || true)
|
|
11
|
+
|
|
12
|
+
if [ -n "$REMOTE_VERSION" ] && [ "$REMOTE_VERSION" != "$LOCAL_VERSION" ]; then
|
|
13
|
+
echo "[CIEL] Update available: v${LOCAL_VERSION} → v${REMOTE_VERSION}. Run /ciel-update to upgrade." >&2
|
|
14
|
+
fi
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Ciel — UserPromptSubmit hook
|
|
3
|
+
# Trigger: user submits a prompt (before Claude processes)
|
|
4
|
+
# Purpose: light depth pre-classification hint injected into context
|
|
5
|
+
# Invokes: depth-classifier skill (lightweight mode)
|
|
6
|
+
# Never blocks (exit 0 always)
|
|
7
|
+
|
|
8
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
9
|
+
PROMPT=$(echo "$INPUT" | python3 -c "
|
|
10
|
+
import sys, json
|
|
11
|
+
try:
|
|
12
|
+
d = json.load(sys.stdin)
|
|
13
|
+
print(d.get('prompt', ''))
|
|
14
|
+
except:
|
|
15
|
+
print('')
|
|
16
|
+
" 2>/dev/null || echo "")
|
|
17
|
+
|
|
18
|
+
[ -z "$PROMPT" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Mechanical depth signals
|
|
21
|
+
DEPTH="Standard" # default
|
|
22
|
+
REASON=""
|
|
23
|
+
|
|
24
|
+
# Check for Critical signals
|
|
25
|
+
if echo "$PROMPT" | grep -qiE '\b(auth|authenti|author|jwt|oauth|password|secret|token|session|payment|credit.card|migration.*schema|2fa|mfa|encryption|credential|cookie.*security)\b'; then
|
|
26
|
+
DEPTH="Critical"
|
|
27
|
+
REASON="auth/security/payment keyword detected"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Check for Trivial signals (only if not Critical)
|
|
31
|
+
if [[ "$DEPTH" != "Critical" ]] && echo "$PROMPT" | grep -qiE '\b(rename|typo|copyright|comment|readme|1-line|one.line|fix.typo|spelling)\b'; then
|
|
32
|
+
DEPTH="Trivial"
|
|
33
|
+
REASON="rename/typo/docs keyword detected"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
DISPATCH_GATE=""
|
|
37
|
+
if [[ "$DEPTH" == "Standard" || "$DEPTH" == "Critical" ]]; then
|
|
38
|
+
DISPATCH_GATE=" | DISPATCH GATE: dispatch ciel-researcher + ciel-explorer in parallel BEFORE first Bash/Read/Edit."
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
META_GATE=""
|
|
42
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-}"
|
|
43
|
+
if [ -n "$PROJECT_DIR" ] && [ -f "$PROJECT_DIR/.ciel/tracked-files.json" ]; then
|
|
44
|
+
EDIT_COUNT=$(CIEL_PATH="$PROJECT_DIR/.ciel/tracked-files.json" python3 -c "
|
|
45
|
+
import json, os
|
|
46
|
+
try: print(len(json.load(open(os.environ['CIEL_PATH']))))
|
|
47
|
+
except: print(0)
|
|
48
|
+
" 2>/dev/null || echo "0")
|
|
49
|
+
if [ "${EDIT_COUNT:-0}" -gt 0 ] 2>/dev/null; then
|
|
50
|
+
META_GATE=" | META GATE: ${EDIT_COUNT} file(s) edited this session — complete 10-item META if previous task ended at PROUVER."
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# ─── Cued-recall: intervention pattern detection ─────────────────────────────
|
|
55
|
+
# When the user message contains a clear correction/intervention pattern,
|
|
56
|
+
# suggest capturing it as a memory. Patterns are intentionally narrow to keep
|
|
57
|
+
# false-positive rate low — generic words like "wait" / "stop" / "actually"
|
|
58
|
+
# are NOT triggers on their own; they must combine with a negation/correction
|
|
59
|
+
# adjacent. Never auto-silent — the model surfaces a question to the user.
|
|
60
|
+
# See ADR-0001 and skill `memoire`.
|
|
61
|
+
INTERVENTION_GATE=""
|
|
62
|
+
# POSIX-ERE only (no PCRE lookahead). Patterns are intentionally high-precision
|
|
63
|
+
# to avoid false positives on generic words (wait/stop/actually). Each pattern
|
|
64
|
+
# is a clear signal of correction or "you missed something".
|
|
65
|
+
if echo "$PROMPT" | grep -qiE "(tu as oublié|t'as oublié|n'oublie pas (que|de)|non en fait|non,? en fait|attention que|rappelle-toi (que|de)|ici on (fait|utilise) plutôt|non on (fait|utilise) plutôt|en fait c'est pas|c'est pas comme ça|mauvaise approche|tu te trompes|you forgot (to|that)|don't forget (to|that)|that's not (right|correct|how)|that's wrong|no[,]? actually|actually,? no|wait[,—-] (no|don't|you forgot)|stop[,—-] (no|you forgot|don't))"; then
|
|
66
|
+
INTERVENTION_GATE=" | CAPTURE GATE: intervention pattern detected — propose AskUserQuestion to capture as memory under .ciel/memory/episodes/ (skill: memoire). Never silent-write."
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# ─── Cued-recall: query memory engine for matching memories ──────────────────
|
|
70
|
+
# Calls hooks/memory-engine.py if installed and a memory corpus exists. The
|
|
71
|
+
# engine handles cue extraction (paths, symbols, intents, language), scoring,
|
|
72
|
+
# token cap, decay, and trigger updates. See docs/adrs/0001-cued-recall-memory.md.
|
|
73
|
+
MEMORY_OUTPUT=""
|
|
74
|
+
ENGINE_PATH=""
|
|
75
|
+
# Resolution order: same dir as this script (most reliable, found via BASH_SOURCE)
|
|
76
|
+
# → project-relative paths in priority order → $HOME fallbacks. Covers local-mode
|
|
77
|
+
# install (top-level hooks/), curl-mode install (.claude/hooks/ or ~/.claude/plugins/ciel/),
|
|
78
|
+
# and OpenCode plugin layout (~/.config/opencode/...).
|
|
79
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null || echo "")"
|
|
80
|
+
for candidate in \
|
|
81
|
+
"$SCRIPT_DIR/memory-engine.py" \
|
|
82
|
+
"$PROJECT_DIR/.claude/hooks/memory-engine.py" \
|
|
83
|
+
"$PROJECT_DIR/hooks/memory-engine.py" \
|
|
84
|
+
"$HOME/.claude/plugins/ciel/memory-engine.py" \
|
|
85
|
+
"$HOME/.ciel/hooks/memory-engine.py"; do
|
|
86
|
+
if [[ -n "$candidate" ]] && [[ -f "$candidate" ]]; then
|
|
87
|
+
ENGINE_PATH="$candidate"
|
|
88
|
+
break
|
|
89
|
+
fi
|
|
90
|
+
done
|
|
91
|
+
|
|
92
|
+
if [[ -n "$ENGINE_PATH" ]] && [[ -n "$PROJECT_DIR" ]] && [[ -f "$PROJECT_DIR/.ciel/memory/index.json" ]]; then
|
|
93
|
+
DEPTH_LOWER=$(echo "$DEPTH" | tr '[:upper:]' '[:lower:]')
|
|
94
|
+
MEMORY_OUTPUT=$(python3 "$ENGINE_PATH" query --prompt "$PROMPT" --cwd "$PROJECT_DIR" --depth "$DEPTH_LOWER" 2>/dev/null || echo "")
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
MSG_BASE="CIEL depth hint: $DEPTH ($REASON).$DISPATCH_GATE$META_GATE$INTERVENTION_GATE Invoke depth-classifier if ambiguous before routing pipeline."
|
|
98
|
+
|
|
99
|
+
# Emit JSON via python to handle newlines and quoting safely
|
|
100
|
+
MSG_BASE="$MSG_BASE" MEMORY_OUTPUT="$MEMORY_OUTPUT" python3 -c "
|
|
101
|
+
import os, json
|
|
102
|
+
base = os.environ.get('MSG_BASE', '')
|
|
103
|
+
mem = os.environ.get('MEMORY_OUTPUT', '').strip()
|
|
104
|
+
combined = base + ('\n\n' + mem if mem else '')
|
|
105
|
+
print(json.dumps({
|
|
106
|
+
'hookSpecificOutput': {
|
|
107
|
+
'hookEventName': 'UserPromptSubmit',
|
|
108
|
+
'additionalContext': combined,
|
|
109
|
+
}
|
|
110
|
+
}))
|
|
111
|
+
"
|
|
112
|
+
exit 0
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"hooks": [
|
|
21
21
|
{
|
|
22
22
|
"type": "command",
|
|
23
|
-
"command": "echo \"[CIEL v6] Session started — Pipeline: DOCS → QUOI → ASK → AVEC QUOI → DIVERGE → RECHERCHE → CODEBASE → EVALUER → ASK2 → FAIRE → RELIRE → PROUVER → MEMOIRE → META\" && \"$CLAUDE_PROJECT_DIR\"/hooks/session-version-check.sh"
|
|
23
|
+
"command": "echo \"[CIEL v6] Session started — Pipeline: DOCS → QUOI → ASK → AVEC QUOI → DIVERGE → RECHERCHE → CODEBASE → EVALUER → ASK2 → FAIRE → RELIRE → PROUVER → MEMOIRE → META\" && \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-version-check.sh"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"type": "command",
|
|
38
|
-
"command": "\"$CLAUDE_PROJECT_DIR\"/hooks/pre-tool-write.sh"
|
|
38
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-tool-write.sh"
|
|
39
39
|
}
|
|
40
40
|
]
|
|
41
41
|
},
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"hooks": [
|
|
55
55
|
{
|
|
56
56
|
"type": "command",
|
|
57
|
-
"command": "\"$CLAUDE_PROJECT_DIR\"/hooks/pre-agent-gate.sh"
|
|
57
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-agent-gate.sh"
|
|
58
58
|
}
|
|
59
59
|
]
|
|
60
60
|
}
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"type": "command",
|
|
72
|
-
"command": "COUNT=$(cat \"$CLAUDE_PROJECT_DIR/.ciel/tracked-files.json\" 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(len(d))\" 2>/dev/null || echo \"?\"); echo \"[CIEL] Tracked files: $COUNT — RELIRE recommended at 5+ files or on Critical paths\" >&2"
|
|
72
|
+
"command": "COUNT=$(cat \"${CLAUDE_PROJECT_DIR:-$PWD}/.ciel/tracked-files.json\" 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(len(d))\" 2>/dev/null || echo \"?\"); echo \"[CIEL] Tracked files: $COUNT — RELIRE recommended at 5+ files or on Critical paths\" >&2"
|
|
73
73
|
}
|
|
74
74
|
]
|
|
75
75
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Audits the current Claude Code session for Ciel paradigm violations (missed Task dispatches, inline gathering, hook inactivity, skill overlaps, intent routing misses). Produces a structured report with a Ciel Health Score (0-100). If score <
|
|
2
|
+
description: Audits the current Claude Code session for Ciel paradigm violations (missed Task dispatches, inline gathering, hook inactivity, skill overlaps, intent routing misses). Produces a structured report with a Ciel Health Score (0-100). If score < 90, creates a GitHub Issue on the Ciel repository with the findings and session timeline. Hook-independent — works even when Ciel hooks are broken.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# /ciel-audit — Session post-mortem
|
|
6
6
|
|
|
7
|
-
*Generates a structured report of Ciel behavior violations observed in the current session. Calculates a Ciel Health Score (0-100). If the score is below
|
|
7
|
+
*Generates a structured report of Ciel behavior violations observed in the current session. Calculates a Ciel Health Score (0-100). If the score is below 90, creates a GitHub Issue on the Ciel repository (github.com/KaosKyun/Ciel) with the full timeline and findings — otherwise produces the report only without creating an issue.*
|
|
8
8
|
|
|
9
9
|
Usage: `/ciel-audit`
|
|
10
10
|
|
|
@@ -85,17 +85,79 @@ If npm version > local version, include an **Update notification** in the report
|
|
|
85
85
|
|
|
86
86
|
#### Dimension 8: Platform health — penalty up to -5
|
|
87
87
|
|
|
88
|
-
Check
|
|
88
|
+
Check that Ciel platform installations exist and are valid. Ciel currently supports two platforms: **claude** and **opencode**.
|
|
89
89
|
|
|
90
|
+
**Claude Code** — check for expected agent and hook files:
|
|
90
91
|
```bash
|
|
91
|
-
ls /
|
|
92
|
+
CLAUDE_AGENTS=$(ls .claude/agents/ciel-*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
93
|
+
CLAUDE_HOOK=$(test -f .claude/hooks/session-start.sh && echo "1" || echo "0")
|
|
94
|
+
CLAUDE_SETTINGS=$(test -f .claude/settings.json && echo "1" || echo "0")
|
|
95
|
+
echo "Claude: agents=$CLAUDE_AGENTS hook=$CLAUDE_HOOK settings=$CLAUDE_SETTINGS"
|
|
96
|
+
if [ "$CLAUDE_AGENTS" -ge 3 ] && [ "$CLAUDE_HOOK" = "1" ] && [ "$CLAUDE_SETTINGS" = "1" ]; then
|
|
97
|
+
echo "Claude platform: OK"
|
|
98
|
+
else
|
|
99
|
+
echo "Claude platform: INCOMPLETE"
|
|
100
|
+
fi
|
|
92
101
|
```
|
|
102
|
+
Expected: at least 3 agent files + session-start.sh + settings.json
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
**OpenCode** — check for expected plugin, agent, and command files:
|
|
105
|
+
```bash
|
|
106
|
+
# Plugin file may be ciel.ts (legacy) or ciel.js (v6+ runtime); accept either.
|
|
107
|
+
OPENCODE_PLUGIN=$( ( test -f .opencode/plugins/ciel.ts || test -f .opencode/plugins/ciel.js ) && echo "1" || echo "0")
|
|
108
|
+
OPENCODE_AGENTS=$(ls .opencode/agents/ciel-*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
109
|
+
OPENCODE_COMMANDS=$(ls .opencode/commands/ciel*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
110
|
+
echo "OpenCode: plugin=$OPENCODE_PLUGIN agents=$OPENCODE_AGENTS commands=$OPENCODE_COMMANDS"
|
|
111
|
+
if [ "$OPENCODE_PLUGIN" = "1" ] && [ "$OPENCODE_AGENTS" -ge 3 ] && [ "$OPENCODE_COMMANDS" -ge 5 ]; then
|
|
112
|
+
echo "OpenCode platform: OK"
|
|
113
|
+
else
|
|
114
|
+
echo "OpenCode platform: INCOMPLETE"
|
|
115
|
+
fi
|
|
116
|
+
```
|
|
117
|
+
Expected: ciel plugin (ciel.ts or ciel.js) + at least 3 agent files + at least 5 command files
|
|
118
|
+
|
|
119
|
+
Scoring:
|
|
120
|
+
- Both platforms fully present and valid: **0**
|
|
121
|
+
- One platform missing or incomplete: **-3**
|
|
122
|
+
- Both platforms missing or critically incomplete: **-5**
|
|
123
|
+
|
|
124
|
+
**Important**: Do NOT check for `.claude/plugins/ciel/platforms/` or `.opencode/platforms/` directories — these are not part of the v6 architecture. Platform files are installed directly into `.claude/` and `.opencode/` respectively. Do NOT check for codex, cursor, kilocode, lmstudio, ollama, or windsurf — these platforms are not yet implemented.
|
|
125
|
+
|
|
126
|
+
#### Dimension 9: Memory health — penalty up to -10
|
|
95
127
|
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
-
|
|
128
|
+
Check the cued-recall memory system (see `docs/adrs/0001-cued-recall-memory.md`):
|
|
129
|
+
|
|
130
|
+
- **index.json missing**: `.ciel/memory/index.json` does not exist. The memory system was never bootstrapped. **-10**
|
|
131
|
+
- **index.json exists but episodes/ empty**: `.ciel/memory/episodes/` has no files. Bootstrap ran but no memories were ingested, or the directory structure is incomplete. **-5**
|
|
132
|
+
- **Low trigger ratio**: Count memories with `trigger_count > 0` vs total. If < 30% of memories have ever been triggered, the cue-matching system may be misconfigured or the memories are not relevant to actual usage. **-3**
|
|
133
|
+
- **Stale memories**: Any memory with `stale: true` or with `last_triggered` older than `stale_after_days` (default 90). Stale memories waste index space and should be cleaned up by `memory-engine.py rebuild-index`. **-2**
|
|
134
|
+
|
|
135
|
+
Scoring:
|
|
136
|
+
- index.json missing: **-10** (blocks all other checks)
|
|
137
|
+
- index.json present but no episode files: **-5**
|
|
138
|
+
- All checks pass: **0**
|
|
139
|
+
|
|
140
|
+
Run these checks:
|
|
141
|
+
```bash
|
|
142
|
+
# Check index.json exists
|
|
143
|
+
test -f .ciel/memory/index.json && echo "index: OK" || echo "index: MISSING"
|
|
144
|
+
|
|
145
|
+
# Count episodes
|
|
146
|
+
EPISODES=$(ls .ciel/memory/episodes/*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
147
|
+
echo "episodes: $EPISODES"
|
|
148
|
+
|
|
149
|
+
# Count triggered vs total (requires python3)
|
|
150
|
+
python3 -c "
|
|
151
|
+
import json
|
|
152
|
+
with open('.ciel/memory/index.json') as f:
|
|
153
|
+
idx = json.load(f)
|
|
154
|
+
mems = idx.get('memories', {})
|
|
155
|
+
total = len(mems)
|
|
156
|
+
triggered = sum(1 for m in mems.values() if m.get('trigger_count', 0) > 0)
|
|
157
|
+
stale = sum(1 for m in mems.values() if m.get('stale'))
|
|
158
|
+
print(f'total: {total}, triggered: {triggered} ({0 if total==0 else triggered*100//total}%), stale: {stale}')
|
|
159
|
+
" 2>/dev/null || echo "memory check failed (no python3?)"
|
|
160
|
+
```
|
|
99
161
|
|
|
100
162
|
---
|
|
101
163
|
|
|
@@ -106,9 +168,7 @@ Expected platforms: codex, cursor, kilocode, lmstudio, ollama, opencode, windsur
|
|
|
106
168
|
| Score range | Status | Issue created? |
|
|
107
169
|
|-------------|--------|----------------|
|
|
108
170
|
| 90-100 | Excellent | No |
|
|
109
|
-
|
|
|
110
|
-
| 50-74 | Needs improvement | **Yes** — creates issue with timeline |
|
|
111
|
-
| 0-49 | Critical | **Yes** — creates issue with timeline |
|
|
171
|
+
| 0-89 | Needs improvement | **Yes** — creates issue with timeline |
|
|
112
172
|
|
|
113
173
|
The score is calculated automatically from the detected violations.
|
|
114
174
|
|
|
@@ -124,7 +184,7 @@ Begin the output with the literal line `# Ciel Session Audit Report`. End with t
|
|
|
124
184
|
**Date**: <today's date>
|
|
125
185
|
**Ciel Health Score**: <N>/100 — <Excellent|Good|Needs improvement|Critical>
|
|
126
186
|
**npm**: local v<X.Y.Z> | npm v<X.Y.Z> | <up-to-date|update available>
|
|
127
|
-
**Platforms**:
|
|
187
|
+
**Platforms**: claude ✓ opencode ✓ (or ✗ if missing)
|
|
128
188
|
**Session summary**: <N> /ciel invocation(s), <N> total tool calls, <N> Task() dispatches, <N> inline Bash/Read/Grep/WebSearch calls in main session.
|
|
129
189
|
|
|
130
190
|
**Verdict**: <PASS | VIOLATIONS FOUND>
|
|
@@ -173,6 +233,7 @@ Begin the output with the literal line `# Ciel Session Audit Report`. End with t
|
|
|
173
233
|
| D6 — Intent routing | -<N> |
|
|
174
234
|
| D7 — npm version | -<N> |
|
|
175
235
|
| D8 — Platform health | -<N> |
|
|
236
|
+
| D9 — Memory health | -<N> |
|
|
176
237
|
| **Total** | **-<N>** |
|
|
177
238
|
| **Health Score** | **<N>/100** |
|
|
178
239
|
|
|
@@ -202,7 +263,7 @@ Output a single short section — **no issue is created** for PASS verdicts:
|
|
|
202
263
|
**Date**: <today's date>
|
|
203
264
|
**Ciel Health Score**: 100/100 — Excellent
|
|
204
265
|
**npm**: local v<X.Y.Z> | npm v<X.Y.Z> | up-to-date
|
|
205
|
-
**Platforms**:
|
|
266
|
+
**Platforms**: claude ✓ opencode ✓
|
|
206
267
|
**Session summary**: <N> /ciel invocation(s), <N> tool calls, <N> Task() dispatches.
|
|
207
268
|
**Verdict**: PASS
|
|
208
269
|
|
|
@@ -213,11 +274,11 @@ Output a single short section — **no issue is created** for PASS verdicts:
|
|
|
213
274
|
|
|
214
275
|
---
|
|
215
276
|
|
|
216
|
-
### GitHub Issue creation (only if score <
|
|
277
|
+
### GitHub Issue creation (only if score < 90)
|
|
217
278
|
|
|
218
|
-
If the Ciel Health Score is **below
|
|
279
|
+
If the Ciel Health Score is **below 90**, create a GitHub Issue with the report AND the session timeline.
|
|
219
280
|
|
|
220
|
-
**Important**: Do NOT create an issue if score >=
|
|
281
|
+
**Important**: Do NOT create an issue if score >= 90. Only create for scores < 90.
|
|
221
282
|
|
|
222
283
|
1. **Check for duplicate issues first**:
|
|
223
284
|
```bash
|
|
@@ -299,6 +360,6 @@ If the Ciel Health Score is **below 75**, create a GitHub Issue with the report
|
|
|
299
360
|
- Do NOT invoke other Ciel skills. This command is fully self-contained.
|
|
300
361
|
- Do NOT dispatch `Task()` agents. Audit happens inline.
|
|
301
362
|
- Do NOT ask clarifying questions. Produce the report with the information you have.
|
|
302
|
-
- Do NOT create an issue if score >=
|
|
363
|
+
- Do NOT create an issue if score >= 90. Only create for score < 90.
|
|
303
364
|
- Do NOT create duplicate issues — run the `gh issue list` check before creating.
|
|
304
365
|
- Do NOT restart, rerun, or attempt to fix the session in-flight. The audit report is the deliverable.
|