@arcanea/overlay-claude 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,890 @@
1
+ /**
2
+ * Hook generators for Claude Code overlay.
3
+ *
4
+ * Generates the 8 Arcanea Intelligence OS hook scripts and
5
+ * the settings.local.json hook registration for any target project.
6
+ *
7
+ * All hooks use relative paths from the project root so they
8
+ * work in ANY project directory — not just the Arcanea monorepo.
9
+ *
10
+ * Shared content (routing patterns, banned phrases, model keywords,
11
+ * tool costs, Guardian verbs) is imported from @arcanea/os and
12
+ * embedded into the generated bash scripts.
13
+ */
14
+ import { GUARDIAN_ROUTING_PATTERNS, BANNED_PHRASES, CONTEXT_SENSITIVE_PHRASES, MODEL_KEYWORD_TIERS, TOOL_COST_ESTIMATES, GUARDIAN_VERBS, GUARDIANS, } from '@arcanea/os';
15
+ // ─── Session Start Hook ──────────────────────────────────────────────────────
16
+ export function generateSessionStartHook() {
17
+ return `#!/usr/bin/env bash
18
+ # Arcanea Intelligence OS — Session Start Hook
19
+ # Initializes session state, Guardian defaults, realm context, and AgentDB.
20
+ set +e
21
+
22
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
23
+ SESSION_DIR="\$ARCANEA_HOME/sessions/current"
24
+ DB_PATH="\${ARCANEA_DB:-\$ARCANEA_HOME/agentdb.sqlite3}"
25
+ HOOK_DIR="\$(cd "\$(dirname "\$0")" && pwd)"
26
+ AGENTDB_DIR="\$(cd "\$HOOK_DIR/../agentdb" 2>/dev/null && pwd)"
27
+
28
+ # Create directories
29
+ mkdir -p "\$ARCANEA_HOME/sessions"
30
+ mkdir -p "\$SESSION_DIR"
31
+
32
+ # Initialize AgentDB if needed
33
+ if [ ! -f "\$DB_PATH" ] && [ -f "\$AGENTDB_DIR/init.sh" ]; then
34
+ bash "\$AGENTDB_DIR/init.sh"
35
+ fi
36
+
37
+ # ── State Files (read by statusline.mjs) ──────────────────────────────────
38
+ echo '{"input": 0, "output": 0, "total": 0}' > "\$SESSION_DIR/tokens.json"
39
+ echo "Shinkami" > "\$SESSION_DIR/guardian"
40
+ echo "Source" > "\$SESSION_DIR/gate"
41
+ echo "Void" > "\$SESSION_DIR/element"
42
+ echo "Intelligence Sanctum" > "\$SESSION_DIR/realm"
43
+ echo "Source Council" > "\$SESSION_DIR/team"
44
+ echo "" > "\$SESSION_DIR/focus"
45
+
46
+ # ── Session Logs ──────────────────────────────────────────────────────────
47
+ echo "[\$(date -u '+%Y-%m-%dT%H:%M:%SZ')] Session started" > "\$SESSION_DIR/start.log"
48
+ echo "0" > "\$SESSION_DIR/tool-count"
49
+ touch "\$SESSION_DIR/routing.log"
50
+ touch "\$SESSION_DIR/tools.log"
51
+ touch "\$SESSION_DIR/voice-violations.log"
52
+
53
+ # ── AgentDB: Reset agent states ───────────────────────────────────────────
54
+ if [ -f "\$DB_PATH" ]; then
55
+ python3 << PYEOF 2>/dev/null
56
+ import sqlite3
57
+ db = sqlite3.connect("\$DB_PATH")
58
+ c = db.cursor()
59
+ c.execute("UPDATE agents SET status='idle', last_active=CURRENT_TIMESTAMP WHERE status='active'")
60
+ c.execute("UPDATE agents SET status='active', last_active=CURRENT_TIMESTAMP WHERE id='shinkami'")
61
+ db.commit()
62
+ db.close()
63
+ PYEOF
64
+ fi
65
+
66
+ echo "Arcanea Intelligence OS initialized. Guardian: Shinkami. Gate: Source."
67
+ `;
68
+ }
69
+ // ─── Prompt Submit Hook ──────────────────────────────────────────────────────
70
+ export function generatePromptSubmitHook() {
71
+ return `#!/usr/bin/env bash
72
+ # Arcanea Intelligence OS — Prompt Submit Hook
73
+ # Routes prompts to Guardian, detects realm/team/focus for statusline.
74
+ set +e
75
+
76
+ PROMPT="\${1:-}"
77
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
78
+ SESSION_DIR="\$ARCANEA_HOME/sessions/current"
79
+ DB_PATH="\${ARCANEA_DB:-\$ARCANEA_HOME/agentdb.sqlite3}"
80
+ TIMESTAMP="\$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
81
+
82
+ mkdir -p "\$SESSION_DIR"
83
+
84
+ # Convert prompt to lowercase for matching
85
+ PROMPT_LOWER="\$(echo "\$PROMPT" | tr '[:upper:]' '[:lower:]')"
86
+
87
+ # ── Guardian Routing ──────────────────────────────────────────────────────
88
+ GUARDIAN="Shinkami"
89
+ GATE="Source"
90
+ KEYWORDS=""
91
+
92
+ # More specific patterns first, broader patterns last
93
+ ${GUARDIAN_ROUTING_PATTERNS.map((p, i) => {
94
+ const keyword = p.pattern.split('|').slice(0, 3).join('/');
95
+ const prefix = i === 0 ? 'if' : 'elif';
96
+ return `${prefix} echo "\\$PROMPT_LOWER" | grep -qE '${p.pattern}'; then\n GUARDIAN="${p.guardian}"; GATE="${p.gate}"; KEYWORDS="${keyword}"`;
97
+ }).join('\n')}
98
+ fi
99
+
100
+ # ── Write State Files ─────────────────────────────────────────────────────
101
+ echo "\$GUARDIAN" > "\$SESSION_DIR/guardian"
102
+ echo "\$GATE" > "\$SESSION_DIR/gate"
103
+
104
+ # ── Session Logging ───────────────────────────────────────────────────────
105
+ echo "[\$TIMESTAMP] Guardian: \$GUARDIAN | Gate: \$GATE | Prompt: \${PROMPT:0:80}..." >> "\$SESSION_DIR/routing.log"
106
+
107
+ # ── AgentDB Routing Log ──────────────────────────────────────────────────
108
+ if [ -f "\$DB_PATH" ]; then
109
+ PROMPT_HASH="\$(echo -n "\$PROMPT" | md5sum 2>/dev/null | cut -d' ' -f1 || echo 'unknown')"
110
+ python3 << PYEOF 2>/dev/null
111
+ import sqlite3
112
+ db = sqlite3.connect("\$DB_PATH")
113
+ c = db.cursor()
114
+ c.execute(
115
+ "INSERT INTO routing_log (prompt_hash, detected_guardian, confidence, keywords_matched) VALUES (?,?,?,?)",
116
+ ("\$PROMPT_HASH", "\$GUARDIAN", 1.0, "\$KEYWORDS")
117
+ )
118
+ c.execute("UPDATE agents SET status='idle', last_active=CURRENT_TIMESTAMP WHERE status='active'")
119
+ c.execute("UPDATE agents SET status='active', last_active=CURRENT_TIMESTAMP WHERE guardian=?", ("\$GUARDIAN",))
120
+ db.commit()
121
+ db.close()
122
+ PYEOF
123
+ fi
124
+ `;
125
+ }
126
+ // ─── Model Route Hook ────────────────────────────────────────────────────────
127
+ export function generateModelRouteHook() {
128
+ return `#!/usr/bin/env bash
129
+ # Arcanea Model Routing — Guardian-Based Recommendation Engine
130
+ # Routes tasks to the optimal model tier based on complexity analysis.
131
+ set +e
132
+
133
+ TASK="\${1:-}"
134
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
135
+ SESSION_DIR="\$ARCANEA_HOME/sessions/current"
136
+ RECOMMENDATION_FILE="\$SESSION_DIR/model-recommendation"
137
+ mkdir -p "\$SESSION_DIR"
138
+
139
+ if [[ -z "\$TASK" ]]; then
140
+ echo "[MODEL_RECOMMENDATION] sonnet | Guardian: Lyssandria | Complexity: 5 | Reason: no task provided"
141
+ echo "sonnet" > "\$RECOMMENDATION_FILE"
142
+ exit 0
143
+ fi
144
+
145
+ TASK_LOWER="\$(echo "\$TASK" | tr '[:upper:]' '[:lower:]')"
146
+
147
+ # Complexity scoring
148
+ COMPLEXITY=0
149
+ REASONS=()
150
+
151
+ ${MODEL_KEYWORD_TIERS.map(tier => {
152
+ const label = tier.tier.toUpperCase().replace('-', ' ');
153
+ const keywords = tier.keywords.map(k => `"${k}"`).join(' ');
154
+ const useWordBound = tier.tier !== 'opus' && tier.tier !== 'haiku';
155
+ const grepPattern = useWordBound ? '"\\\\b\\${keyword}\\\\b"' : '"\\$keyword"';
156
+ return `# ${label} keywords (+${tier.weight})
157
+ for keyword in ${keywords}; do
158
+ if echo "\\$TASK_LOWER" | grep -qE ${grepPattern}; then
159
+ COMPLEXITY=\\$((COMPLEXITY + ${tier.weight}))
160
+ REASONS+=("\\$keyword")
161
+ fi
162
+ done`;
163
+ }).join('\n\n')}
164
+
165
+ # Scope multipliers
166
+ echo "\$TASK_LOWER" | grep -qE "\\b(entire|all|every|whole|complete)\\b" && COMPLEXITY=\$((COMPLEXITY + 2))
167
+ echo "\$TASK_LOWER" | grep -qE "\\bnew\\b" && COMPLEXITY=\$((COMPLEXITY + 1))
168
+ echo "\$TASK_LOWER" | grep -qE "\\bwith\\b" && COMPLEXITY=\$((COMPLEXITY + 1))
169
+
170
+ WORD_COUNT=\$(echo "\$TASK" | wc -w)
171
+ [[ "\$WORD_COUNT" -gt 20 ]] && COMPLEXITY=\$((COMPLEXITY + 1))
172
+ [[ "\$WORD_COUNT" -gt 40 ]] && COMPLEXITY=\$((COMPLEXITY + 1))
173
+
174
+ # Clamp 1-10
175
+ [[ "\$COMPLEXITY" -lt 1 ]] && COMPLEXITY=1
176
+ [[ "\$COMPLEXITY" -gt 10 ]] && COMPLEXITY=10
177
+
178
+ # Build reason (max 4 unique)
179
+ REASON_STRING=""
180
+ SEEN=()
181
+ COUNT=0
182
+ for r in "\${REASONS[@]}"; do
183
+ SKIP=0
184
+ for s in "\${SEEN[@]}"; do [[ "\$s" == "\$r" ]] && SKIP=1 && break; done
185
+ [[ "\$SKIP" -eq 1 ]] && continue
186
+ SEEN+=("\$r")
187
+ [[ "\$COUNT" -gt 0 ]] && REASON_STRING="\${REASON_STRING} + \${r}" || REASON_STRING="\$r"
188
+ COUNT=\$((COUNT + 1))
189
+ [[ "\$COUNT" -ge 4 ]] && break
190
+ done
191
+ [[ -z "\$REASON_STRING" ]] && REASON_STRING="general task"
192
+
193
+ # Map to model tier
194
+ if [[ "\$COMPLEXITY" -ge 9 ]]; then
195
+ MODEL="opus"
196
+ GUARDIAN="Draconia"
197
+ elif [[ "\$COMPLEXITY" -ge 4 ]]; then
198
+ MODEL="sonnet"
199
+ if echo "\$TASK_LOWER" | grep -qE "(architect|infrastructure|foundation|schema|database)"; then
200
+ GUARDIAN="Lyssandria"
201
+ elif echo "\$TASK_LOWER" | grep -qE "(design|creative|art|visual|style)"; then
202
+ GUARDIAN="Leyla"
203
+ elif echo "\$TASK_LOWER" | grep -qE "(review|audit|analyze|quality)"; then
204
+ GUARDIAN="Alera"
205
+ elif echo "\$TASK_LOWER" | grep -qE "(vision|insight|intuition)"; then
206
+ GUARDIAN="Lyria"
207
+ else
208
+ GUARDIAN="Lyssandria"
209
+ fi
210
+ else
211
+ MODEL="haiku"
212
+ GUARDIAN="Maylinn"
213
+ fi
214
+
215
+ echo "[MODEL_RECOMMENDATION] \${MODEL} | Guardian: \${GUARDIAN} | Complexity: \${COMPLEXITY} | Reason: \${REASON_STRING}"
216
+ echo "\$MODEL" > "\$RECOMMENDATION_FILE"
217
+ exit 0
218
+ `;
219
+ }
220
+ // ─── Pre-Tool Hook ───────────────────────────────────────────────────────────
221
+ export function generatePreToolHook() {
222
+ return `#!/usr/bin/env bash
223
+ # Arcanea Intelligence OS — Pre-Tool Use Hook
224
+ # Logs tool invocations and tracks usage count.
225
+ set +e
226
+
227
+ TOOL_NAME="\${1:-unknown}"
228
+ TOOL_INPUT="\${2:-}"
229
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
230
+ SESSION_DIR="\$ARCANEA_HOME/sessions/current"
231
+ TIMESTAMP="\$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
232
+
233
+ mkdir -p "\$SESSION_DIR"
234
+
235
+ # Increment tool count
236
+ COUNT_FILE="\$SESSION_DIR/tool-count"
237
+ if [ -f "\$COUNT_FILE" ]; then
238
+ COUNT=\$(cat "\$COUNT_FILE" 2>/dev/null || echo "0")
239
+ else
240
+ COUNT=0
241
+ fi
242
+ COUNT=\$((COUNT + 1))
243
+ echo "\$COUNT" > "\$COUNT_FILE"
244
+
245
+ # Log tool invocation
246
+ INPUT_PREVIEW="\${TOOL_INPUT:0:120}"
247
+ echo "[\$TIMESTAMP] [#\$COUNT] START \$TOOL_NAME | Input: \${INPUT_PREVIEW}..." >> "\$SESSION_DIR/tools.log"
248
+ `;
249
+ }
250
+ // ─── Voice Check Hook ────────────────────────────────────────────────────────
251
+ export function generateVoiceCheckHook() {
252
+ return `#!/usr/bin/env bash
253
+ # Arcanea Voice Enforcement Hook
254
+ # Scans content for banned phrases and suggests Voice Bible v2.0 replacements.
255
+ # Warns but never blocks — the flow must not be interrupted.
256
+ set -euo pipefail
257
+
258
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
259
+ LOG_DIR="\$ARCANEA_HOME/sessions/current"
260
+ LOG_FILE="\${LOG_DIR}/voice-violations.log"
261
+ TIMESTAMP="\$(date '+%Y-%m-%d %H:%M:%S')"
262
+
263
+ mkdir -p "\${LOG_DIR}"
264
+
265
+ # Banned phrases: "banned_phrase|replacement"
266
+ BANNED_PHRASES=(
267
+ ${BANNED_PHRASES.map(p => ` "${p.banned}|${p.replacement || '[FORBIDDEN -- remove entirely]'}"`).join('\n')}
268
+ )
269
+
270
+ # Context-sensitive phrases
271
+ CONTEXT_SENSITIVE=(${CONTEXT_SENSITIVE_PHRASES.map(p => `"${p}"`).join(' ')})
272
+
273
+ # Read input
274
+ CONTENT=""
275
+ if [[ \$# -ge 1 && -n "\$1" ]]; then
276
+ CONTENT="\$1"
277
+ else
278
+ if [[ ! -t 0 ]]; then
279
+ CONTENT="\$(cat)"
280
+ fi
281
+ fi
282
+
283
+ if [[ -z "\${CONTENT}" ]]; then
284
+ exit 0
285
+ fi
286
+
287
+ # Scan for violations
288
+ VIOLATION_COUNT=0
289
+ VIOLATIONS_OUTPUT=""
290
+
291
+ mapfile -t LINES <<< "\${CONTENT}"
292
+
293
+ for entry in "\${BANNED_PHRASES[@]}"; do
294
+ BANNED="\${entry%%|*}"
295
+ REPLACEMENT="\${entry##*|}"
296
+
297
+ IS_CONTEXT_SENSITIVE=false
298
+ for cs in "\${CONTEXT_SENSITIVE[@]}"; do
299
+ if [[ "\${BANNED}" == "\${cs}" ]]; then
300
+ IS_CONTEXT_SENSITIVE=true
301
+ break
302
+ fi
303
+ done
304
+
305
+ for i in "\${!LINES[@]}"; do
306
+ LINE="\${LINES[\$i]}"
307
+ LINE_NUM=\$((i + 1))
308
+ LINE_LOWER="\${LINE,,}"
309
+ BANNED_LOWER="\${BANNED,,}"
310
+
311
+ if [[ "\${LINE_LOWER}" == *"\${BANNED_LOWER}"* ]]; then
312
+ VIOLATION_COUNT=\$((VIOLATION_COUNT + 1))
313
+ if [[ "\${IS_CONTEXT_SENSITIVE}" == true ]]; then
314
+ MSG="[VOICE] \\"\${BANNED}\\" -> \\"\${REPLACEMENT}\\" (line ~\${LINE_NUM}) [context-sensitive: only when referring to Arcanea]"
315
+ else
316
+ MSG="[VOICE] \\"\${BANNED}\\" -> \\"\${REPLACEMENT}\\" (line ~\${LINE_NUM})"
317
+ fi
318
+ VIOLATIONS_OUTPUT+="\${MSG}"\$'\\n'
319
+ fi
320
+ done
321
+ done
322
+
323
+ if [[ \${VIOLATION_COUNT} -gt 0 ]]; then
324
+ {
325
+ echo ""
326
+ echo "================================================================"
327
+ echo " ARCANEA VOICE CHECK -- \${VIOLATION_COUNT} violation(s) detected"
328
+ echo "================================================================"
329
+ echo ""
330
+ echo "\${VIOLATIONS_OUTPUT}"
331
+ echo "These are warnings only. The Arcanea voice favors precision over hype."
332
+ echo ""
333
+ } >&2
334
+
335
+ {
336
+ echo "--- \${TIMESTAMP} ---"
337
+ echo "\${VIOLATIONS_OUTPUT}"
338
+ } >> "\${LOG_FILE}"
339
+ fi
340
+
341
+ # Always exit 0 -- warn, never block
342
+ exit 0
343
+ `;
344
+ }
345
+ // ─── Post-Tool Hook ──────────────────────────────────────────────────────────
346
+ export function generatePostToolHook() {
347
+ return `#!/usr/bin/env bash
348
+ # Arcanea Intelligence OS — Post-Tool Use Hook
349
+ # Logs tool completion and writes significant operations to AgentDB memories.
350
+ set +e
351
+
352
+ TOOL_NAME="\${1:-unknown}"
353
+ TOOL_OUTPUT="\${2:-}"
354
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
355
+ SESSION_DIR="\$ARCANEA_HOME/sessions/current"
356
+ DB_PATH="\${ARCANEA_DB:-\$ARCANEA_HOME/agentdb.sqlite3}"
357
+ TIMESTAMP="\$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
358
+
359
+ mkdir -p "\$SESSION_DIR"
360
+
361
+ COUNT=\$(cat "\$SESSION_DIR/tool-count" 2>/dev/null || echo "0")
362
+ OUTPUT_LEN=\${#TOOL_OUTPUT}
363
+
364
+ echo "[\$TIMESTAMP] [#\$COUNT] DONE \$TOOL_NAME | Output: \${OUTPUT_LEN} chars" >> "\$SESSION_DIR/tools.log"
365
+
366
+ # Write significant operations to AgentDB
367
+ if [ -f "\$DB_PATH" ] && [ "\$OUTPUT_LEN" -gt 50 ]; then
368
+ case "\$TOOL_NAME" in
369
+ Write|Edit|Bash)
370
+ GUARDIAN=\$(cat "\$SESSION_DIR/guardian" 2>/dev/null || echo "shinkami")
371
+ GUARDIAN_LOWER="\$(echo "\$GUARDIAN" | tr '[:upper:]' '[:lower:]')"
372
+ OUTPUT_PREVIEW="\${TOOL_OUTPUT:0:200}"
373
+ python3 << PYEOF 2>/dev/null
374
+ import sqlite3
375
+ db = sqlite3.connect("\$DB_PATH")
376
+ c = db.cursor()
377
+ c.execute(
378
+ "INSERT INTO memories (agent_id, namespace, key, value) VALUES (?,?,?,?)",
379
+ ("\$GUARDIAN_LOWER", "tool-log", "\$TOOL_NAME-\$TIMESTAMP", "\${OUTPUT_PREVIEW//\\"/\\\\\\"}")
380
+ )
381
+ db.commit()
382
+ db.close()
383
+ PYEOF
384
+ ;;
385
+ esac
386
+ fi
387
+ `;
388
+ }
389
+ // ─── Context Tracker Hook ────────────────────────────────────────────────────
390
+ export function generateContextTrackerHook() {
391
+ return `#!/usr/bin/env bash
392
+ # Arcanea Context Budget Tracker
393
+ # Monitors token usage and warns when output quality may degrade.
394
+ # 0-30% context: PEAK — optimal reasoning depth
395
+ # 30-50% context: GOOD — high quality
396
+ # 50-70% context: DEGRADING — suggest checkpoint
397
+ # 70%+ context: REFRESH — recommend session refresh
398
+ set -euo pipefail
399
+
400
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
401
+ SESSION_DIR="\$ARCANEA_HOME/sessions/current"
402
+ TOKENS_FILE="\$SESSION_DIR/tokens.json"
403
+ STATUS_FILE="\$SESSION_DIR/context-status"
404
+ mkdir -p "\$SESSION_DIR"
405
+ MAX_TOKENS="\${ARCANEA_MAX_TOKENS:-200000}"
406
+
407
+ # Tool token cost estimates
408
+ declare -A TOOL_COSTS=(
409
+ ${Object.entries(TOOL_COST_ESTIMATES).map(([tool, cost]) => `[${tool}]=${cost}`).join(' ')}
410
+ )
411
+
412
+ init_tokens_file() {
413
+ if [[ ! -f "\$TOKENS_FILE" ]]; then
414
+ printf '{"input": 0, "output": 0, "total": 0}\\n' > "\$TOKENS_FILE"
415
+ fi
416
+ if ! python3 -c "import json; json.load(open('\$TOKENS_FILE'))" 2>/dev/null; then
417
+ printf '{"input": 0, "output": 0, "total": 0}\\n' > "\$TOKENS_FILE"
418
+ fi
419
+ }
420
+
421
+ read_total() {
422
+ python3 -c "
423
+ import json
424
+ with open('\$TOKENS_FILE') as f:
425
+ data = json.load(f)
426
+ print(int(data.get('total', 0)))
427
+ "
428
+ }
429
+
430
+ increment_tokens() {
431
+ local tool_name="\$1"
432
+ local cost="\${TOOL_COSTS[\$tool_name]:-0}"
433
+ [[ "\$cost" -eq 0 ]] && return 0
434
+
435
+ python3 -c "
436
+ import json
437
+ with open('\$TOKENS_FILE') as f:
438
+ data = json.load(f)
439
+ cost = \$cost
440
+ data['input'] = int(data.get('input', 0)) + int(cost * 0.6)
441
+ data['output'] = int(data.get('output', 0)) + cost - int(cost * 0.6)
442
+ data['total'] = int(data.get('total', 0)) + cost
443
+ with open('\$TOKENS_FILE', 'w') as f:
444
+ json.dump(data, f)
445
+ "
446
+ }
447
+
448
+ get_zone() {
449
+ local pct="\$1"
450
+ if (( pct < 30 )); then echo "PEAK"
451
+ elif (( pct < 50 )); then echo "GOOD"
452
+ elif (( pct < 70 )); then echo "DEGRADING"
453
+ else echo "REFRESH"; fi
454
+ }
455
+
456
+ format_tokens() {
457
+ local n="\$1"
458
+ if (( n >= 1000 )); then echo "\$((n / 1000))k"
459
+ else echo "\$n"; fi
460
+ }
461
+
462
+ main() {
463
+ local tool_name="\${1:-}"
464
+ init_tokens_file
465
+ [[ -n "\$tool_name" ]] && increment_tokens "\$tool_name"
466
+
467
+ local total
468
+ total=\$(read_total)
469
+ local pct=0
470
+ (( MAX_TOKENS > 0 )) && pct=\$(( (total * 100) / MAX_TOKENS ))
471
+
472
+ local zone
473
+ zone=\$(get_zone "\$pct")
474
+ printf '%s|%d|%d|%d\\n' "\$zone" "\$pct" "\$total" "\$MAX_TOKENS" > "\$STATUS_FILE"
475
+
476
+ # Log zone transitions to AgentDB
477
+ local db_path="\${ARCANEA_DB:-\$ARCANEA_HOME/agentdb.sqlite3}"
478
+ local prev_zone=""
479
+ [[ -f "\$SESSION_DIR/prev-zone" ]] && prev_zone=\$(cat "\$SESSION_DIR/prev-zone" 2>/dev/null)
480
+ if [[ "\$zone" != "\$prev_zone" ]] && [[ -f "\$db_path" ]]; then
481
+ echo "\$zone" > "\$SESSION_DIR/prev-zone"
482
+ python3 -c "
483
+ import sqlite3
484
+ db = sqlite3.connect('\$db_path')
485
+ c = db.cursor()
486
+ c.execute('INSERT INTO memories (agent_id, namespace, key, value) VALUES (?,?,?,?)',
487
+ ('shinkami', 'context-budget', 'zone-\$zone-\$(date +%s)', '\$zone at \${pct}% (\${total}/\${MAX_TOKENS})'))
488
+ db.commit()
489
+ db.close()
490
+ " 2>/dev/null || true
491
+ fi
492
+
493
+ local total_fmt max_fmt
494
+ total_fmt=\$(format_tokens "\$total")
495
+ max_fmt=\$(format_tokens "\$MAX_TOKENS")
496
+
497
+ case "\$zone" in
498
+ PEAK) echo "[PEAK] Context: \${pct}% (\${total_fmt}/\${max_fmt}) — Optimal reasoning depth" ;;
499
+ GOOD) echo "[GOOD] Context: \${pct}% (\${total_fmt}/\${max_fmt}) — High quality" ;;
500
+ DEGRADING)
501
+ echo ""
502
+ echo "=== CONTEXT WARNING ==="
503
+ echo "[DEGRADING] Context: \${pct}% (\${total_fmt}/\${max_fmt})"
504
+ echo "Output quality declining. Consider checkpointing and refreshing."
505
+ echo "========================"
506
+ echo "" ;;
507
+ REFRESH)
508
+ echo ""
509
+ echo "!!! CONTEXT CRITICAL !!!"
510
+ echo "[REFRESH] Context: \${pct}% (\${total_fmt}/\${max_fmt})"
511
+ echo "Commit work, document remaining tasks, start a NEW session."
512
+ echo "!!!!!!!!!!!!!!!!!!!!!!!!"
513
+ echo "" ;;
514
+ esac
515
+ return 0
516
+ }
517
+
518
+ main "\$@" || true
519
+ `;
520
+ }
521
+ // ─── Session End Hook ────────────────────────────────────────────────────────
522
+ export function generateSessionEndHook() {
523
+ return `#!/usr/bin/env bash
524
+ # Arcanea Intelligence OS — Session End Hook
525
+ # Summarizes session and writes stats to AgentDB.
526
+ set +e
527
+
528
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
529
+ SESSION_DIR="\$ARCANEA_HOME/sessions/current"
530
+ DB_PATH="\${ARCANEA_DB:-\$ARCANEA_HOME/agentdb.sqlite3}"
531
+ TIMESTAMP="\$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
532
+
533
+ # Read session metrics
534
+ TOOL_COUNT=\$(cat "\$SESSION_DIR/tool-count" 2>/dev/null || echo "0")
535
+ ROUTING_COUNT=\$(wc -l < "\$SESSION_DIR/routing.log" 2>/dev/null || echo "0")
536
+
537
+ # Read final context state
538
+ CONTEXT_STATUS=\$(cat "\$SESSION_DIR/context-status" 2>/dev/null || echo "UNKNOWN|0|0|200000")
539
+ ZONE=\$(echo "\$CONTEXT_STATUS" | cut -d'|' -f1)
540
+ PCT=\$(echo "\$CONTEXT_STATUS" | cut -d'|' -f2)
541
+
542
+ # Read final Guardian
543
+ GUARDIAN=\$(cat "\$SESSION_DIR/guardian" 2>/dev/null || echo "Shinkami")
544
+
545
+ SUMMARY="Session ended at \$TIMESTAMP. Tools: \$TOOL_COUNT. Routes: \$ROUTING_COUNT. Guardian: \$GUARDIAN. Context: \${ZONE} (\${PCT}%)."
546
+
547
+ echo "[\$TIMESTAMP] \$SUMMARY" >> "\$SESSION_DIR/start.log"
548
+
549
+ # Write to AgentDB
550
+ if [ -f "\$DB_PATH" ]; then
551
+ SESSION_ID="session-\$(date +%Y%m%d-%H%M%S)"
552
+ python3 << PYEOF 2>/dev/null
553
+ import sqlite3
554
+ db = sqlite3.connect("\$DB_PATH")
555
+ c = db.cursor()
556
+ c.execute(
557
+ "INSERT OR REPLACE INTO vault_entries (id, layer, category, key, value, source) VALUES (?,?,?,?,?,?)",
558
+ ("\$SESSION_ID", "INTELLECT", "session", "summary", "\$SUMMARY", "session-end-hook")
559
+ )
560
+ c.execute("UPDATE agents SET status='idle', last_active=CURRENT_TIMESTAMP")
561
+ db.commit()
562
+ db.close()
563
+ PYEOF
564
+ fi
565
+
566
+ # Archive session
567
+ ARCHIVE_DIR="\$ARCANEA_HOME/sessions/archive/\$(date +%Y%m%d-%H%M%S)"
568
+ if [ -d "\$SESSION_DIR" ]; then
569
+ mkdir -p "\$ARCANEA_HOME/sessions/archive"
570
+ cp -r "\$SESSION_DIR" "\$ARCHIVE_DIR" 2>/dev/null
571
+ fi
572
+
573
+ echo "Session archived. \$SUMMARY"
574
+ `;
575
+ }
576
+ // ─── Statusline Generator ────────────────────────────────────────────────────
577
+ export function generateStatusline() {
578
+ return `#!/usr/bin/env node
579
+ // Arcanea Intelligence OS — Context-Adaptive Statusline
580
+ // Reads session state and renders statusline for Claude Code.
581
+
582
+ import { readFileSync, existsSync } from 'node:fs';
583
+ import { join } from 'node:path';
584
+ import { execSync } from 'node:child_process';
585
+
586
+ const ARCANEA_HOME = process.env.ARCANEA_HOME || join(process.env.HOME || '', '.arcanea');
587
+ const SESSION_DIR = join(ARCANEA_HOME, 'sessions', 'current');
588
+
589
+ function safeRead(filePath) {
590
+ try {
591
+ if (existsSync(filePath)) {
592
+ return readFileSync(filePath, 'utf-8').trim();
593
+ }
594
+ } catch { /* ignore */ }
595
+ return '';
596
+ }
597
+
598
+ // ── Read state ──────────────────────────────────────────────────────────
599
+ const guardian = safeRead(join(SESSION_DIR, 'guardian')) || 'Shinkami';
600
+ const gate = safeRead(join(SESSION_DIR, 'gate')) || 'Source';
601
+ const realm = safeRead(join(SESSION_DIR, 'realm')) || '';
602
+ const focus = safeRead(join(SESSION_DIR, 'focus')) || '';
603
+
604
+ // ── Context zone ────────────────────────────────────────────────────────
605
+ const contextStatus = safeRead(join(SESSION_DIR, 'context-status'));
606
+ let zone = '';
607
+ if (contextStatus) {
608
+ const parts = contextStatus.split('|');
609
+ zone = parts[0] || '';
610
+ }
611
+
612
+ // ── Guardian verb ───────────────────────────────────────────────────────
613
+ const GUARDIAN_VERBS = {
614
+ ${Object.entries(GUARDIAN_VERBS).map(([g, v]) => `${g}: '${v}'`).join(', ')},
615
+ };
616
+ const verb = GUARDIAN_VERBS[guardian] || 'guides';
617
+
618
+ // ── Git branch ──────────────────────────────────────────────────────────
619
+ let branch = '';
620
+ try {
621
+ branch = execSync('git rev-parse --abbrev-ref HEAD 2>/dev/null', { encoding: 'utf-8' }).trim();
622
+ } catch { /* not a git repo */ }
623
+
624
+ // ── Compose statusline ──────────────────────────────────────────────────
625
+ const parts = ['Arcanea'];
626
+ parts.push(\`\${guardian} \${verb}\`);
627
+ if (focus) parts.push(focus);
628
+ if (branch) parts.push(branch);
629
+ if (zone && zone !== 'PEAK') parts.push(\`[\${zone}]\`);
630
+
631
+ process.stdout.write(parts.join(' | '));
632
+ `;
633
+ }
634
+ // ─── Settings (Hook Registration) ────────────────────────────────────────────
635
+ export function generateHookSettings(projectDir) {
636
+ const hooksDir = `${projectDir}/.claude/hooks`;
637
+ return {
638
+ hooks: {
639
+ SessionStart: [
640
+ {
641
+ hooks: [
642
+ {
643
+ type: 'command',
644
+ command: `bash ${hooksDir}/session-start.sh`,
645
+ },
646
+ ],
647
+ },
648
+ ],
649
+ UserPromptSubmit: [
650
+ {
651
+ hooks: [
652
+ {
653
+ type: 'command',
654
+ command: `bash ${hooksDir}/prompt-submit.sh "$USER_PROMPT"`,
655
+ },
656
+ {
657
+ type: 'command',
658
+ command: `bash ${hooksDir}/model-route.sh "$USER_PROMPT"`,
659
+ },
660
+ ],
661
+ },
662
+ ],
663
+ PreToolUse: [
664
+ {
665
+ matcher: 'Task|Bash|Write|Edit',
666
+ hooks: [
667
+ {
668
+ type: 'command',
669
+ timeout: 3000,
670
+ command: `bash ${hooksDir}/pre-tool.sh "$TOOL_NAME"`,
671
+ },
672
+ ],
673
+ },
674
+ {
675
+ matcher: 'Write|Edit',
676
+ hooks: [
677
+ {
678
+ type: 'command',
679
+ timeout: 3000,
680
+ command: `bash ${hooksDir}/voice-check.sh "$TOOL_INPUT"`,
681
+ },
682
+ ],
683
+ },
684
+ ],
685
+ PostToolUse: [
686
+ {
687
+ matcher: 'Task|Bash|Write|Edit|Read|Grep|Glob|WebFetch|WebSearch',
688
+ hooks: [
689
+ {
690
+ type: 'command',
691
+ timeout: 3000,
692
+ command: `bash ${hooksDir}/post-tool.sh "$TOOL_NAME"`,
693
+ },
694
+ {
695
+ type: 'command',
696
+ timeout: 3000,
697
+ command: `bash ${hooksDir}/context-tracker.sh "$TOOL_NAME"`,
698
+ },
699
+ ],
700
+ },
701
+ ],
702
+ Stop: [
703
+ {
704
+ hooks: [
705
+ {
706
+ type: 'command',
707
+ timeout: 5000,
708
+ command: `bash ${hooksDir}/session-end.sh`,
709
+ },
710
+ ],
711
+ },
712
+ ],
713
+ },
714
+ };
715
+ }
716
+ // ─── AgentDB Schema ──────────────────────────────────────────────────────────
717
+ export function generateAgentDBSchema() {
718
+ return `-- Arcanea AgentDB Schema
719
+ -- SQLite database for agent state, memories, and session tracking.
720
+
721
+ CREATE TABLE IF NOT EXISTS agents (
722
+ id TEXT PRIMARY KEY,
723
+ guardian TEXT,
724
+ role TEXT,
725
+ element TEXT,
726
+ status TEXT DEFAULT 'idle',
727
+ last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP
728
+ );
729
+
730
+ CREATE TABLE IF NOT EXISTS memories (
731
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
732
+ agent_id TEXT NOT NULL,
733
+ namespace TEXT NOT NULL,
734
+ key TEXT NOT NULL,
735
+ value TEXT,
736
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
737
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
738
+ );
739
+
740
+ CREATE TABLE IF NOT EXISTS tasks (
741
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
742
+ agent_id TEXT,
743
+ description TEXT NOT NULL,
744
+ status TEXT DEFAULT 'pending',
745
+ priority INTEGER DEFAULT 5,
746
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
747
+ completed_at TIMESTAMP,
748
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
749
+ );
750
+
751
+ CREATE TABLE IF NOT EXISTS swarm_sessions (
752
+ id TEXT PRIMARY KEY,
753
+ pattern TEXT NOT NULL,
754
+ status TEXT DEFAULT 'active',
755
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
756
+ completed_at TIMESTAMP
757
+ );
758
+
759
+ CREATE TABLE IF NOT EXISTS swarm_agents (
760
+ session_id TEXT NOT NULL,
761
+ agent_id TEXT NOT NULL,
762
+ role TEXT,
763
+ status TEXT DEFAULT 'pending',
764
+ PRIMARY KEY (session_id, agent_id),
765
+ FOREIGN KEY (session_id) REFERENCES swarm_sessions(id),
766
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
767
+ );
768
+
769
+ CREATE TABLE IF NOT EXISTS routing_log (
770
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
771
+ prompt_hash TEXT,
772
+ detected_guardian TEXT,
773
+ confidence REAL,
774
+ keywords_matched TEXT,
775
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
776
+ );
777
+
778
+ CREATE TABLE IF NOT EXISTS vault_entries (
779
+ id TEXT PRIMARY KEY,
780
+ layer TEXT NOT NULL,
781
+ category TEXT NOT NULL,
782
+ key TEXT NOT NULL,
783
+ value TEXT,
784
+ source TEXT,
785
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
786
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
787
+ );
788
+
789
+ -- Seed the 10 Guardians
790
+ INSERT OR IGNORE INTO agents (id, guardian, role, element, status) VALUES
791
+ ${GUARDIANS.map((g, i) => {
792
+ const status = g.name === 'shinkami' ? 'active' : 'idle';
793
+ const comma = i < GUARDIANS.length - 1 ? ',' : ';';
794
+ return ` ('${g.name}', '${g.displayName}', '${g.role}', '${g.element || 'void'}', '${status}')${comma}`;
795
+ }).join('\n')}
796
+ `;
797
+ }
798
+ // ─── AgentDB Init Script ─────────────────────────────────────────────────────
799
+ export function generateAgentDBInit() {
800
+ return `#!/usr/bin/env bash
801
+ # Initialize Arcanea AgentDB
802
+ set +e
803
+
804
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
805
+ DB_PATH="\${ARCANEA_DB:-\$ARCANEA_HOME/agentdb.sqlite3}"
806
+ SCRIPT_DIR="\$(cd "\$(dirname "\$0")" && pwd)"
807
+ SCHEMA_FILE="\$SCRIPT_DIR/schema.sql"
808
+
809
+ mkdir -p "\$ARCANEA_HOME"
810
+
811
+ if [ ! -f "\$DB_PATH" ] && [ -f "\$SCHEMA_FILE" ]; then
812
+ python3 << PYEOF
813
+ import sqlite3
814
+ db = sqlite3.connect("\$DB_PATH")
815
+ with open("\$SCHEMA_FILE") as f:
816
+ db.executescript(f.read())
817
+ db.close()
818
+ print("AgentDB initialized at \$DB_PATH")
819
+ PYEOF
820
+ else
821
+ echo "AgentDB already exists at \$DB_PATH"
822
+ fi
823
+ `;
824
+ }
825
+ // ─── Helper Scripts ──────────────────────────────────────────────────────────
826
+ export function generateQuickStatusScript() {
827
+ return `#!/usr/bin/env bash
828
+ # Arcanea Quick Status — one-liner overview
829
+ set +e
830
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
831
+ SD="\$ARCANEA_HOME/sessions/current"
832
+ G=\$(cat "\$SD/guardian" 2>/dev/null || echo "Shinkami")
833
+ GT=\$(cat "\$SD/gate" 2>/dev/null || echo "Source")
834
+ TC=\$(cat "\$SD/tool-count" 2>/dev/null || echo "0")
835
+ CS=\$(cat "\$SD/context-status" 2>/dev/null || echo "PEAK|0|0|200000")
836
+ Z=\$(echo "\$CS" | cut -d'|' -f1)
837
+ P=\$(echo "\$CS" | cut -d'|' -f2)
838
+ echo "Arcanea | Guardian: \$G | Gate: \$GT | Tools: \$TC | Context: \$Z (\${P}%)"
839
+ `;
840
+ }
841
+ export function generateHealthCheckScript() {
842
+ return `#!/usr/bin/env bash
843
+ # Arcanea Health Check — verify all subsystems
844
+ set +e
845
+ ARCANEA_HOME="\${ARCANEA_HOME:-\$HOME/.arcanea}"
846
+ DB_PATH="\${ARCANEA_DB:-\$ARCANEA_HOME/agentdb.sqlite3}"
847
+ SD="\$ARCANEA_HOME/sessions/current"
848
+ PASS=0; FAIL=0
849
+
850
+ check() {
851
+ if eval "\$2" >/dev/null 2>&1; then
852
+ echo " [OK] \$1"; PASS=\$((PASS + 1))
853
+ else
854
+ echo " [!!] \$1"; FAIL=\$((FAIL + 1))
855
+ fi
856
+ }
857
+
858
+ echo "=== Arcanea Health Check ==="
859
+ check "Session directory" "[ -d '\$SD' ]"
860
+ check "Guardian state" "[ -f '\$SD/guardian' ]"
861
+ check "Gate state" "[ -f '\$SD/gate' ]"
862
+ check "Tool counter" "[ -f '\$SD/tool-count' ]"
863
+ check "Tokens tracker" "[ -f '\$SD/tokens.json' ]"
864
+ check "AgentDB exists" "[ -f '\$DB_PATH' ]"
865
+ check "AgentDB readable" "python3 -c \\"import sqlite3; sqlite3.connect('\$DB_PATH').execute('SELECT 1')\\""
866
+ check "Python3 available" "which python3"
867
+ check "Routing log" "[ -f '\$SD/routing.log' ]"
868
+ echo ""
869
+ echo "Result: \$PASS passed, \$FAIL failed"
870
+ `;
871
+ }
872
+ export function getAllHookFiles() {
873
+ return [
874
+ { filename: 'session-start.sh', content: generateSessionStartHook(), executable: true },
875
+ { filename: 'prompt-submit.sh', content: generatePromptSubmitHook(), executable: true },
876
+ { filename: 'model-route.sh', content: generateModelRouteHook(), executable: true },
877
+ { filename: 'pre-tool.sh', content: generatePreToolHook(), executable: true },
878
+ { filename: 'voice-check.sh', content: generateVoiceCheckHook(), executable: true },
879
+ { filename: 'post-tool.sh', content: generatePostToolHook(), executable: true },
880
+ { filename: 'context-tracker.sh', content: generateContextTrackerHook(), executable: true },
881
+ { filename: 'session-end.sh', content: generateSessionEndHook(), executable: true },
882
+ ];
883
+ }
884
+ export function getAllHelperFiles() {
885
+ return [
886
+ { filename: 'arcanea-quick-status.sh', content: generateQuickStatusScript(), executable: true },
887
+ { filename: 'arcanea-health.sh', content: generateHealthCheckScript(), executable: true },
888
+ ];
889
+ }
890
+ //# sourceMappingURL=hook-generators.js.map