@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.
- package/README.md +38 -0
- package/dist/content-depth.d.ts +13 -0
- package/dist/content-depth.d.ts.map +1 -0
- package/dist/content-depth.js +394 -0
- package/dist/content-depth.js.map +1 -0
- package/dist/generators.d.ts +25 -0
- package/dist/generators.d.ts.map +1 -0
- package/dist/generators.js +46 -0
- package/dist/generators.js.map +1 -0
- package/dist/hook-generators.d.ts +35 -0
- package/dist/hook-generators.d.ts.map +1 -0
- package/dist/hook-generators.js +890 -0
- package/dist/hook-generators.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/installer.d.ts +21 -0
- package/dist/installer.d.ts.map +1 -0
- package/dist/installer.js +311 -0
- package/dist/installer.js.map +1 -0
- package/dist/templates.d.ts +11 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +51 -0
- package/dist/templates.js.map +1 -0
- package/package.json +53 -0
|
@@ -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
|