@ekkos/cli 0.2.9 → 0.2.10
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/dist/cache/LocalSessionStore.d.ts +34 -21
- package/dist/cache/LocalSessionStore.js +169 -53
- package/dist/cache/capture.d.ts +19 -11
- package/dist/cache/capture.js +243 -76
- package/dist/cache/types.d.ts +14 -1
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +148 -73
- package/dist/commands/hooks.d.ts +109 -0
- package/dist/commands/hooks.js +668 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +69 -21
- package/dist/index.js +42 -1
- package/dist/restore/RestoreOrchestrator.d.ts +17 -3
- package/dist/restore/RestoreOrchestrator.js +64 -22
- package/dist/utils/paths.d.ts +125 -0
- package/dist/utils/paths.js +283 -0
- package/package.json +1 -1
- package/templates/ekkos-manifest.json +223 -0
- package/templates/helpers/json-parse.cjs +101 -0
- package/templates/hooks/assistant-response.ps1 +256 -0
- package/templates/hooks/assistant-response.sh +124 -64
- package/templates/hooks/session-start.ps1 +107 -2
- package/templates/hooks/session-start.sh +201 -166
- package/templates/hooks/stop.ps1 +124 -3
- package/templates/hooks/stop.sh +470 -843
- package/templates/hooks/user-prompt-submit.ps1 +107 -22
- package/templates/hooks/user-prompt-submit.sh +403 -393
- package/templates/project-stubs/session-start.ps1 +63 -0
- package/templates/project-stubs/session-start.sh +55 -0
- package/templates/project-stubs/stop.ps1 +63 -0
- package/templates/project-stubs/stop.sh +55 -0
- package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
- package/templates/project-stubs/user-prompt-submit.sh +55 -0
- package/templates/shared/hooks-enabled.json +22 -0
- package/templates/shared/session-words.json +45 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
3
|
# ekkOS_ Hook: UserPromptSubmit - SEAMLESS CONTEXT CONTINUITY
|
|
4
|
+
# MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
|
|
5
|
+
# EKKOS_MANAGED=1
|
|
4
6
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
5
7
|
# ZERO USER ACTION NEEDED:
|
|
6
8
|
# 1. Tracks turn number and context size
|
|
@@ -11,89 +13,152 @@
|
|
|
11
13
|
set +e
|
|
12
14
|
|
|
13
15
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
14
|
-
#
|
|
16
|
+
# CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
|
|
17
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
EKKOS_CONFIG_DIR="${EKKOS_CONFIG_DIR:-$HOME/.ekkos}"
|
|
19
|
+
SESSION_WORDS_JSON="$EKKOS_CONFIG_DIR/session-words.json"
|
|
20
|
+
SESSION_WORDS_DEFAULT="$EKKOS_CONFIG_DIR/.defaults/session-words.json"
|
|
21
|
+
JSON_PARSE_HELPER="$EKKOS_CONFIG_DIR/.helpers/json-parse.cjs"
|
|
22
|
+
|
|
23
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
# SESSION NAME GENERATION - Uses external session-words.json (NO jq)
|
|
15
25
|
# Format: adj-noun-verb (e.g., "cosmic-penguin-runs")
|
|
16
|
-
# 100 × 100 × 100 = 1,000,000 combinations
|
|
17
26
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
27
|
+
|
|
28
|
+
# Arrays loaded from JSON - NOT hardcoded
|
|
29
|
+
declare -a ADJECTIVES
|
|
30
|
+
declare -a NOUNS
|
|
31
|
+
declare -a VERBS
|
|
32
|
+
SESSION_WORDS_LOADED=false
|
|
33
|
+
|
|
34
|
+
load_session_words() {
|
|
35
|
+
if [ "$SESSION_WORDS_LOADED" = "true" ]; then
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
local words_file="$SESSION_WORDS_JSON"
|
|
40
|
+
|
|
41
|
+
# Fallback to managed defaults if user file missing/invalid
|
|
42
|
+
if [ ! -f "$words_file" ]; then
|
|
43
|
+
words_file="$SESSION_WORDS_DEFAULT"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if [ ! -f "$words_file" ]; then
|
|
47
|
+
echo "ERROR: No session-words.json found. Run 'ekkos hooks install --global' to fix." >&2
|
|
48
|
+
return 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Check for json-parse helper
|
|
52
|
+
if [ ! -f "$JSON_PARSE_HELPER" ]; then
|
|
53
|
+
echo "ERROR: json-parse.cjs helper not found. Run 'ekkos hooks install --global' to fix." >&2
|
|
54
|
+
return 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Parse using Node helper (no jq dependency)
|
|
58
|
+
if command -v node &>/dev/null; then
|
|
59
|
+
# Use readarray if available (bash 4+), otherwise use while loop
|
|
60
|
+
if [ "${BASH_VERSINFO[0]}" -ge 4 ]; then
|
|
61
|
+
readarray -t ADJECTIVES < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
|
|
62
|
+
readarray -t NOUNS < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
|
|
63
|
+
readarray -t VERBS < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
|
|
64
|
+
else
|
|
65
|
+
# Bash 3 fallback (macOS default)
|
|
66
|
+
local i=0
|
|
67
|
+
while IFS= read -r line; do
|
|
68
|
+
ADJECTIVES[i]="$line"
|
|
69
|
+
((i++))
|
|
70
|
+
done < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
|
|
71
|
+
i=0
|
|
72
|
+
while IFS= read -r line; do
|
|
73
|
+
NOUNS[i]="$line"
|
|
74
|
+
((i++))
|
|
75
|
+
done < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
|
|
76
|
+
i=0
|
|
77
|
+
while IFS= read -r line; do
|
|
78
|
+
VERBS[i]="$line"
|
|
79
|
+
((i++))
|
|
80
|
+
done < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ ${#ADJECTIVES[@]} -eq 0 ] || [ ${#NOUNS[@]} -eq 0 ] || [ ${#VERBS[@]} -eq 0 ]; then
|
|
84
|
+
echo "ERROR: Failed to parse $words_file. Check JSON syntax." >&2
|
|
85
|
+
return 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
SESSION_WORDS_LOADED=true
|
|
89
|
+
return 0
|
|
90
|
+
else
|
|
91
|
+
echo "ERROR: Node.js not found. Install Node.js 18+ to use ekkOS hooks." >&2
|
|
92
|
+
return 1
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
63
95
|
|
|
64
96
|
uuid_to_words() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
local uuid="$1"
|
|
98
|
+
|
|
99
|
+
# Load session words if not already loaded
|
|
100
|
+
if [ "$SESSION_WORDS_LOADED" != "true" ]; then
|
|
101
|
+
load_session_words || {
|
|
102
|
+
echo "unknown-session-starts"
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
local hex="${uuid//-/}"
|
|
108
|
+
hex="${hex:0:12}"
|
|
109
|
+
if [[ ! "$hex" =~ ^[0-9a-fA-F]+$ ]]; then
|
|
110
|
+
echo "unknown-session-starts"
|
|
111
|
+
return
|
|
112
|
+
fi
|
|
113
|
+
local adj_seed=$((16#${hex:0:4}))
|
|
114
|
+
local noun_seed=$((16#${hex:4:4}))
|
|
115
|
+
local verb_seed=$((16#${hex:8:4}))
|
|
116
|
+
local adj_idx=$((adj_seed % ${#ADJECTIVES[@]}))
|
|
117
|
+
local noun_idx=$((noun_seed % ${#NOUNS[@]}))
|
|
118
|
+
local verb_idx=$((verb_seed % ${#VERBS[@]}))
|
|
119
|
+
echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}-${VERBS[$verb_idx]}"
|
|
79
120
|
}
|
|
80
121
|
|
|
81
122
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
82
123
|
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
83
124
|
|
|
84
125
|
INPUT=$(cat)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
126
|
+
|
|
127
|
+
# Parse input using json-parse helper (no jq)
|
|
128
|
+
parse_json_value() {
|
|
129
|
+
local json="$1"
|
|
130
|
+
local path="$2"
|
|
131
|
+
echo "$json" | node -e "
|
|
132
|
+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'));
|
|
133
|
+
const path = '$path'.replace(/^\./,'').split('.').filter(Boolean);
|
|
134
|
+
let result = data;
|
|
135
|
+
for (const p of path) {
|
|
136
|
+
if (result === undefined || result === null) { result = undefined; break; }
|
|
137
|
+
result = result[p];
|
|
138
|
+
}
|
|
139
|
+
if (result !== undefined && result !== null) console.log(result);
|
|
140
|
+
" 2>/dev/null || echo ""
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
USER_QUERY=$(parse_json_value "$INPUT" '.query')
|
|
144
|
+
if [ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ]; then
|
|
145
|
+
USER_QUERY=$(parse_json_value "$INPUT" '.message')
|
|
146
|
+
fi
|
|
147
|
+
if [ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ]; then
|
|
148
|
+
USER_QUERY=$(parse_json_value "$INPUT" '.prompt')
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
RAW_SESSION_ID=$(parse_json_value "$INPUT" '.session_id')
|
|
152
|
+
TRANSCRIPT_PATH=$(parse_json_value "$INPUT" '.transcript_path')
|
|
88
153
|
|
|
89
154
|
[ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ] && exit 0
|
|
90
155
|
|
|
91
156
|
# Fallback: read session_id from saved state if not in INPUT
|
|
92
|
-
if [ "$RAW_SESSION_ID"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
157
|
+
if [ -z "$RAW_SESSION_ID" ] || [ "$RAW_SESSION_ID" = "unknown" ] || [ "$RAW_SESSION_ID" = "null" ]; then
|
|
158
|
+
STATE_FILE="$HOME/.claude/state/current-session.json"
|
|
159
|
+
if [ -f "$STATE_FILE" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
160
|
+
RAW_SESSION_ID=$(node "$JSON_PARSE_HELPER" "$STATE_FILE" '.session_id' 2>/dev/null || echo "unknown")
|
|
161
|
+
fi
|
|
97
162
|
fi
|
|
98
163
|
|
|
99
164
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -103,92 +168,74 @@ fi
|
|
|
103
168
|
SKILL_REMINDERS=()
|
|
104
169
|
QUERY_LOWER=$(echo "$USER_QUERY" | tr '[:upper:]' '[:lower:]')
|
|
105
170
|
|
|
106
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
107
|
-
# MANDATORY TRIGGERS (Always check ekkOS first)
|
|
108
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
109
|
-
|
|
110
171
|
# Memory First - Debug/Error/Problem solving
|
|
111
172
|
if echo "$QUERY_LOWER" | grep -qE '(how do i|debug|error|bug|fix|not working|broken|fails|issue|problem|wrong|crash)'; then
|
|
112
|
-
|
|
173
|
+
SKILL_REMINDERS+=("SKILL REQUIRED: Call Skill(skill: \"ekkOS_Memory_First\") FIRST before debugging")
|
|
113
174
|
fi
|
|
114
175
|
|
|
115
|
-
#
|
|
116
|
-
# RECALL TRIGGERS (Time-based memory)
|
|
117
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
176
|
+
# Recall Triggers - Time-based memory
|
|
118
177
|
if echo "$QUERY_LOWER" | grep -qE '(yesterday|last week|last month|remember when|what did we|where did we leave|before|earlier|previous|ago)'; then
|
|
119
|
-
|
|
178
|
+
SKILL_REMINDERS+=("SKILL REQUIRED: Call Skill(skill: \"ekkOS_Deep_Recall\") for time-based memory")
|
|
120
179
|
fi
|
|
121
180
|
|
|
122
|
-
#
|
|
123
|
-
# DIRECTIVE TRIGGERS (User preferences)
|
|
124
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
181
|
+
# Directive Triggers - User preferences
|
|
125
182
|
if echo "$QUERY_LOWER" | grep -qE '(always |never |i prefer|i like |dont |don.t |avoid |remember that |from now on)'; then
|
|
126
|
-
|
|
183
|
+
SKILL_REMINDERS+=("SKILL REQUIRED: Call Skill(skill: \"ekkOS_Preferences\") to capture directive")
|
|
127
184
|
fi
|
|
128
185
|
|
|
129
|
-
#
|
|
130
|
-
# SAFETY TRIGGERS (Destructive actions)
|
|
131
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
186
|
+
# Safety Triggers - Destructive actions
|
|
132
187
|
if echo "$QUERY_LOWER" | grep -qE '(delete|drop |rm -rf|deploy|push.*main|push.*master|production|migrate|rollback)'; then
|
|
133
|
-
|
|
188
|
+
SKILL_REMINDERS+=("SAFETY REQUIRED: Call ekkOS_Conflict before this destructive action")
|
|
134
189
|
fi
|
|
135
190
|
|
|
136
|
-
#
|
|
137
|
-
# SCHEMA TRIGGERS (Database operations)
|
|
138
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
# Schema Triggers - Database operations
|
|
139
192
|
if echo "$QUERY_LOWER" | grep -qE '(sql|query|supabase|prisma|database|table|column|select |insert |update |where )'; then
|
|
140
|
-
|
|
193
|
+
SKILL_REMINDERS+=("SCHEMA REQUIRED: Call ekkOS_GetSchema for correct field names")
|
|
141
194
|
fi
|
|
142
195
|
|
|
143
|
-
#
|
|
144
|
-
# SECRET TRIGGERS (API keys, credentials)
|
|
145
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
196
|
+
# Secret Triggers - API keys, credentials
|
|
146
197
|
if echo "$QUERY_LOWER" | grep -qE '(api key|token|password|credential|secret|my.*key|store.*key)'; then
|
|
147
|
-
|
|
198
|
+
SKILL_REMINDERS+=("SECRETS: Use ekkOS_StoreSecret to securely save credentials")
|
|
148
199
|
fi
|
|
149
200
|
|
|
150
|
-
#
|
|
151
|
-
# PLAN TRIGGERS (Complex multi-step tasks)
|
|
152
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
201
|
+
# Plan Triggers - Complex multi-step tasks
|
|
153
202
|
if echo "$QUERY_LOWER" | grep -qE '(implement|build|create.*feature|refactor|migrate|set up|architecture)'; then
|
|
154
|
-
|
|
203
|
+
SKILL_REMINDERS+=("PLAN REQUIRED: Call ekkOS_Plan for complex multi-step tasks")
|
|
155
204
|
fi
|
|
156
205
|
|
|
157
|
-
#
|
|
158
|
-
# LEARN TRIGGERS (User expressing success/failure)
|
|
159
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
206
|
+
# Learn Triggers - User expressing success/failure
|
|
160
207
|
if echo "$QUERY_LOWER" | grep -qE '(that worked|thanks|perfect|great|awesome|nailed it|solved|fixed it)'; then
|
|
161
|
-
|
|
208
|
+
SKILL_REMINDERS+=("LEARN: Consider calling ekkOS_Forge to capture this solution as a pattern")
|
|
162
209
|
fi
|
|
163
210
|
|
|
164
|
-
#
|
|
165
|
-
# CODEBASE TRIGGERS (Project-specific code search)
|
|
166
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
211
|
+
# Codebase Triggers - Project-specific code search
|
|
167
212
|
if echo "$QUERY_LOWER" | grep -qE '(where is|find.*file|search.*code|in this project|in the codebase)'; then
|
|
168
|
-
|
|
213
|
+
SKILL_REMINDERS+=("CODEBASE: Use ekkOS_Codebase for project-specific code search")
|
|
169
214
|
fi
|
|
170
215
|
|
|
171
216
|
# Combine skill reminders (only take first 3 to avoid noise)
|
|
172
217
|
SKILL_REMINDER=""
|
|
173
218
|
REMINDER_COUNT=${#SKILL_REMINDERS[@]}
|
|
174
219
|
if [ "$REMINDER_COUNT" -gt 0 ]; then
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
[ -n "$SKILL_REMINDER" ] && SKILL_REMINDER="$SKILL_REMINDER
|
|
220
|
+
MAX_REMINDERS=3
|
|
221
|
+
[ "$REMINDER_COUNT" -lt "$MAX_REMINDERS" ] && MAX_REMINDERS="$REMINDER_COUNT"
|
|
222
|
+
for i in $(seq 0 $((MAX_REMINDERS - 1))); do
|
|
223
|
+
[ -n "$SKILL_REMINDER" ] && SKILL_REMINDER="$SKILL_REMINDER
|
|
180
224
|
"
|
|
181
|
-
|
|
182
|
-
|
|
225
|
+
SKILL_REMINDER="$SKILL_REMINDER${SKILL_REMINDERS[$i]}"
|
|
226
|
+
done
|
|
183
227
|
fi
|
|
184
228
|
|
|
185
229
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
186
|
-
# Load auth
|
|
230
|
+
# Load auth using json-parse helper (no jq)
|
|
187
231
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
188
232
|
EKKOS_CONFIG="$HOME/.ekkos/config.json"
|
|
189
233
|
AUTH_TOKEN=""
|
|
190
|
-
if [ -f "$EKKOS_CONFIG" ]; then
|
|
191
|
-
|
|
234
|
+
if [ -f "$EKKOS_CONFIG" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
235
|
+
AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.hookApiKey' 2>/dev/null || echo "")
|
|
236
|
+
if [ -z "$AUTH_TOKEN" ]; then
|
|
237
|
+
AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.apiKey' 2>/dev/null || echo "")
|
|
238
|
+
fi
|
|
192
239
|
fi
|
|
193
240
|
[ -z "$AUTH_TOKEN" ] && AUTH_TOKEN=$(grep -E "^SUPABASE_SECRET_KEY=" "$PROJECT_ROOT/.env.local" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
|
|
194
241
|
|
|
@@ -196,21 +243,17 @@ MEMORY_API_URL="https://mcp.ekkos.dev"
|
|
|
196
243
|
|
|
197
244
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
198
245
|
# Session ID - NEW ID per conversation (not persisted 24h anymore)
|
|
199
|
-
# Each Claude Code session gets unique ID for proper Time Machine grouping
|
|
200
246
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
201
247
|
STATE_DIR="$PROJECT_ROOT/.claude/state"
|
|
202
248
|
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
203
249
|
SESSION_FILE="$STATE_DIR/current-session.json"
|
|
204
250
|
|
|
205
|
-
# Use Claude's RAW_SESSION_ID exclusively
|
|
206
251
|
SESSION_ID="$RAW_SESSION_ID"
|
|
207
252
|
|
|
208
|
-
# Skip if no valid session ID from Claude
|
|
209
253
|
if [ -z "$SESSION_ID" ] || [ "$SESSION_ID" = "unknown" ] || [ "$SESSION_ID" = "null" ]; then
|
|
210
|
-
|
|
254
|
+
exit 0
|
|
211
255
|
fi
|
|
212
256
|
|
|
213
|
-
# Save for other hooks to reference (but don't reuse across conversations)
|
|
214
257
|
echo "{\"session_id\": \"$SESSION_ID\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$SESSION_FILE"
|
|
215
258
|
|
|
216
259
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -220,87 +263,98 @@ PROJECT_SESSION_DIR="$STATE_DIR/sessions"
|
|
|
220
263
|
mkdir -p "$PROJECT_SESSION_DIR" 2>/dev/null || true
|
|
221
264
|
TURN_COUNTER_FILE="$PROJECT_SESSION_DIR/${SESSION_ID}.turn"
|
|
222
265
|
|
|
223
|
-
# Count actual user messages in transcript for accurate turn number
|
|
224
266
|
TURN_NUMBER=1
|
|
225
267
|
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
[ "$TURN_NUMBER" -eq 0 ] && TURN_NUMBER=1
|
|
268
|
+
TURN_NUMBER=$(grep -c '"type":"user"' "$TRANSCRIPT_PATH" 2>/dev/null || echo "1")
|
|
269
|
+
[ "$TURN_NUMBER" -eq 0 ] && TURN_NUMBER=1
|
|
229
270
|
fi
|
|
230
271
|
|
|
231
|
-
# PRESERVE HISTORY: Don't overwrite if saved count is higher (after /clear)
|
|
232
272
|
SAVED_TURN_COUNT=0
|
|
233
273
|
[ -f "$TURN_COUNTER_FILE" ] && SAVED_TURN_COUNT=$(cat "$TURN_COUNTER_FILE" 2>/dev/null || echo "0")
|
|
234
|
-
TRANSCRIPT_TURN_COUNT=$TURN_NUMBER
|
|
274
|
+
TRANSCRIPT_TURN_COUNT=$TURN_NUMBER
|
|
235
275
|
POST_CLEAR_DETECTED=false
|
|
236
276
|
if [ "$SAVED_TURN_COUNT" -gt "$TURN_NUMBER" ]; then
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
POST_CLEAR_DETECTED=true
|
|
277
|
+
TURN_NUMBER=$((SAVED_TURN_COUNT + 1))
|
|
278
|
+
POST_CLEAR_DETECTED=true
|
|
240
279
|
fi
|
|
241
280
|
echo "$TURN_NUMBER" > "$TURN_COUNTER_FILE"
|
|
242
281
|
|
|
243
282
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
244
|
-
#
|
|
283
|
+
# WORKING MEMORY: Fast capture each turn (async, non-blocking)
|
|
245
284
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
246
285
|
MEMORY_API_URL="https://mcp.ekkos.dev"
|
|
247
|
-
if [ -f "$HOME/.ekkos/config.json" ]; then
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
286
|
+
if [ -f "$HOME/.ekkos/config.json" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
287
|
+
CAPTURE_TOKEN=$(node "$JSON_PARSE_HELPER" "$HOME/.ekkos/config.json" '.hookApiKey' 2>/dev/null || echo "")
|
|
288
|
+
if [ -z "$CAPTURE_TOKEN" ]; then
|
|
289
|
+
CAPTURE_TOKEN=$(node "$JSON_PARSE_HELPER" "$HOME/.ekkos/config.json" '.apiKey' 2>/dev/null || echo "")
|
|
290
|
+
fi
|
|
291
|
+
if [ -n "$CAPTURE_TOKEN" ] && [ "$CAPTURE_TOKEN" != "null" ]; then
|
|
292
|
+
# Async capture to Redis/Supabase - doesn't block hook execution
|
|
293
|
+
# Build JSON safely using node
|
|
294
|
+
(node -e "
|
|
295
|
+
const body = JSON.stringify({
|
|
296
|
+
session_id: process.argv[1],
|
|
297
|
+
turn: parseInt(process.argv[2]),
|
|
298
|
+
query: process.argv[3]
|
|
299
|
+
});
|
|
300
|
+
const https = require('https');
|
|
301
|
+
const req = https.request({
|
|
302
|
+
hostname: 'mcp.ekkos.dev',
|
|
303
|
+
path: '/api/v1/working/fast-capture',
|
|
304
|
+
method: 'POST',
|
|
305
|
+
headers: {
|
|
306
|
+
'Authorization': 'Bearer ' + process.argv[4],
|
|
307
|
+
'Content-Type': 'application/json'
|
|
308
|
+
}
|
|
309
|
+
}, () => {});
|
|
310
|
+
req.on('error', () => {});
|
|
311
|
+
req.write(body);
|
|
312
|
+
req.end();
|
|
313
|
+
" "$RAW_SESSION_ID" "$TURN_NUMBER" "$USER_QUERY" "$CAPTURE_TOKEN" >/dev/null 2>&1) &
|
|
314
|
+
fi
|
|
257
315
|
fi
|
|
258
316
|
|
|
259
317
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
260
|
-
#
|
|
318
|
+
# LOCAL CACHE: Tier 0 capture for instant /continue (async, non-blocking)
|
|
319
|
+
# Per ekkOS Onboarding Spec v1.2 ADDENDUM: Pass instanceId for namespacing
|
|
261
320
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
262
321
|
if command -v ekkos-capture &>/dev/null; then
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
322
|
+
if [ -z "$SESSION_NAME" ] || [ "$SESSION_NAME" = "unknown-session-starts" ]; then
|
|
323
|
+
SESSION_NAME=$(uuid_to_words "$RAW_SESSION_ID" 2>/dev/null || echo "unknown-session")
|
|
324
|
+
fi
|
|
325
|
+
# NEW format: ekkos-capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
|
|
326
|
+
INSTANCE_ID="${EKKOS_INSTANCE_ID:-default}"
|
|
327
|
+
(ekkos-capture user "$INSTANCE_ID" "$RAW_SESSION_ID" "$SESSION_NAME" "$TURN_NUMBER" "$USER_QUERY" "$PROJECT_ROOT" \
|
|
328
|
+
>/dev/null 2>&1) &
|
|
270
329
|
fi
|
|
271
330
|
|
|
272
331
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
273
|
-
#
|
|
332
|
+
# GOLDEN LOOP: CAPTURE PHASE - Track turn start
|
|
274
333
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
275
334
|
GOLDEN_LOOP_FILE="$PROJECT_ROOT/.ekkos/golden-loop-current.json"
|
|
276
335
|
mkdir -p "$PROJECT_ROOT/.ekkos" 2>/dev/null || true
|
|
277
336
|
|
|
278
|
-
# Write current phase to file
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
retrieved: 0,
|
|
291
|
-
applied: 0,
|
|
292
|
-
forged: 0
|
|
293
|
-
}
|
|
294
|
-
}' > "$GOLDEN_LOOP_FILE" 2>/dev/null || true
|
|
337
|
+
# Write current phase to file using node (no jq)
|
|
338
|
+
node -e "
|
|
339
|
+
const fs = require('fs');
|
|
340
|
+
const data = {
|
|
341
|
+
phase: 'capture',
|
|
342
|
+
turn: $TURN_NUMBER,
|
|
343
|
+
session: '$SESSION_ID',
|
|
344
|
+
timestamp: new Date().toISOString(),
|
|
345
|
+
stats: { retrieved: 0, applied: 0, forged: 0 }
|
|
346
|
+
};
|
|
347
|
+
fs.writeFileSync('$GOLDEN_LOOP_FILE', JSON.stringify(data, null, 2));
|
|
348
|
+
" 2>/dev/null || true
|
|
295
349
|
|
|
296
350
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
297
|
-
#
|
|
351
|
+
# GOLDEN LOOP: RETRIEVE PHASE - Auto-retrieve patterns from ekkOS
|
|
298
352
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
299
353
|
EKKOS_API_KEY=""
|
|
300
354
|
if [ -f "$HOME/.ekkos/.hookApiKey" ]; then
|
|
301
|
-
|
|
302
|
-
elif [ -f "$HOME/.ekkos/config.json" ]; then
|
|
303
|
-
|
|
355
|
+
EKKOS_API_KEY=$(cat "$HOME/.ekkos/.hookApiKey" 2>/dev/null || echo "")
|
|
356
|
+
elif [ -f "$HOME/.ekkos/config.json" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
357
|
+
EKKOS_API_KEY=$(node "$JSON_PARSE_HELPER" "$HOME/.ekkos/config.json" '.hookApiKey' 2>/dev/null || echo "")
|
|
304
358
|
fi
|
|
305
359
|
|
|
306
360
|
RETRIEVED_PATTERNS=""
|
|
@@ -309,166 +363,161 @@ RETRIEVED_DIRECTIVES=""
|
|
|
309
363
|
DIRECTIVE_COUNT=0
|
|
310
364
|
|
|
311
365
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
312
|
-
#
|
|
313
|
-
# Only fetch from API if:
|
|
314
|
-
# 1. Cache doesn't exist
|
|
315
|
-
# 2. Cache is >1 hour old
|
|
316
|
-
# 3. Directive-related trigger detected in query
|
|
366
|
+
# DIRECTIVE CACHE: Local cache to avoid API calls every turn
|
|
317
367
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
318
368
|
DIRECTIVE_CACHE_DIR="$HOME/.ekkos/cache"
|
|
319
369
|
DIRECTIVE_CACHE_FILE="$DIRECTIVE_CACHE_DIR/directives.json"
|
|
320
|
-
DIRECTIVE_CACHE_TTL=3600
|
|
370
|
+
DIRECTIVE_CACHE_TTL=3600
|
|
321
371
|
mkdir -p "$DIRECTIVE_CACHE_DIR" 2>/dev/null || true
|
|
322
372
|
|
|
323
|
-
# Check if we need to refresh directive cache
|
|
324
373
|
DIRECTIVE_CACHE_VALID=false
|
|
325
374
|
DIRECTIVE_TRIGGER_DETECTED=false
|
|
326
375
|
|
|
327
|
-
# Smart detection: Check if query mentions directive-related keywords
|
|
328
376
|
if echo "$QUERY_LOWER" | grep -qE '(always |never |i prefer|i like |dont |don.t |avoid |remember that |from now on|directive|preference)'; then
|
|
329
|
-
|
|
377
|
+
DIRECTIVE_TRIGGER_DETECTED=true
|
|
330
378
|
fi
|
|
331
379
|
|
|
332
|
-
if [ -f "$DIRECTIVE_CACHE_FILE" ]; then
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
380
|
+
if [ -f "$DIRECTIVE_CACHE_FILE" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
381
|
+
CACHE_TIMESTAMP=$(node "$JSON_PARSE_HELPER" "$DIRECTIVE_CACHE_FILE" '.cached_at' 2>/dev/null || echo "0")
|
|
382
|
+
CURRENT_TIMESTAMP=$(date +%s)
|
|
383
|
+
CACHE_AGE=$((CURRENT_TIMESTAMP - CACHE_TIMESTAMP))
|
|
336
384
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
fi
|
|
385
|
+
if [ "$CACHE_AGE" -lt "$DIRECTIVE_CACHE_TTL" ] && [ "$DIRECTIVE_TRIGGER_DETECTED" = "false" ]; then
|
|
386
|
+
DIRECTIVE_CACHE_VALID=true
|
|
387
|
+
fi
|
|
341
388
|
fi
|
|
342
389
|
|
|
343
|
-
# Decide whether to inject directives this turn
|
|
344
|
-
# SMART INJECTION: Only on Turn 1, post-clear, or directive trigger
|
|
345
390
|
SHOULD_INJECT_DIRECTIVES=false
|
|
346
391
|
if [ "$TURN_NUMBER" -eq 1 ] || [ "$POST_CLEAR_DETECTED" = "true" ] || [ "$DIRECTIVE_TRIGGER_DETECTED" = "true" ]; then
|
|
347
|
-
|
|
392
|
+
SHOULD_INJECT_DIRECTIVES=true
|
|
348
393
|
fi
|
|
349
394
|
|
|
350
395
|
if [ -n "$EKKOS_API_KEY" ] && [ -n "$USER_QUERY" ]; then
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
'
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
# Build directive section
|
|
437
|
-
if [ -n "$MUST_DIRECTIVES" ] || [ -n "$NEVER_DIRECTIVES" ] || [ -n "$PREFER_DIRECTIVES" ] || [ -n "$AVOID_DIRECTIVES" ]; then
|
|
438
|
-
RETRIEVED_DIRECTIVES="🔴 USER DIRECTIVES (FOLLOW THESE):"
|
|
439
|
-
[ -n "$MUST_DIRECTIVES" ] && RETRIEVED_DIRECTIVES="$RETRIEVED_DIRECTIVES
|
|
440
|
-
|
|
441
|
-
MUST:
|
|
442
|
-
$MUST_DIRECTIVES"
|
|
443
|
-
[ -n "$NEVER_DIRECTIVES" ] && RETRIEVED_DIRECTIVES="$RETRIEVED_DIRECTIVES
|
|
444
|
-
|
|
445
|
-
NEVER:
|
|
446
|
-
$NEVER_DIRECTIVES"
|
|
447
|
-
[ -n "$PREFER_DIRECTIVES" ] && RETRIEVED_DIRECTIVES="$RETRIEVED_DIRECTIVES
|
|
448
|
-
|
|
449
|
-
PREFER:
|
|
450
|
-
$PREFER_DIRECTIVES"
|
|
451
|
-
[ -n "$AVOID_DIRECTIVES" ] && RETRIEVED_DIRECTIVES="$RETRIEVED_DIRECTIVES
|
|
452
|
-
|
|
453
|
-
AVOID:
|
|
454
|
-
$AVOID_DIRECTIVES"
|
|
455
|
-
fi
|
|
456
|
-
|
|
457
|
-
# Save to cache for future turns
|
|
458
|
-
jq -n \
|
|
459
|
-
--argjson count "$DIRECTIVE_COUNT" \
|
|
460
|
-
--arg formatted "$RETRIEVED_DIRECTIVES" \
|
|
461
|
-
--argjson cached_at "$(date +%s)" \
|
|
462
|
-
'{
|
|
463
|
-
count: $count,
|
|
464
|
-
formatted: $formatted,
|
|
465
|
-
cached_at: $cached_at
|
|
466
|
-
}' > "$DIRECTIVE_CACHE_FILE" 2>/dev/null || true
|
|
396
|
+
# Update phase to RETRIEVE
|
|
397
|
+
node -e "
|
|
398
|
+
const fs = require('fs');
|
|
399
|
+
const data = {
|
|
400
|
+
phase: 'retrieve',
|
|
401
|
+
turn: $TURN_NUMBER,
|
|
402
|
+
session: '$SESSION_ID',
|
|
403
|
+
timestamp: new Date().toISOString(),
|
|
404
|
+
stats: { retrieved: 0, applied: 0, forged: 0 }
|
|
405
|
+
};
|
|
406
|
+
fs.writeFileSync('$GOLDEN_LOOP_FILE', JSON.stringify(data, null, 2));
|
|
407
|
+
" 2>/dev/null || true
|
|
408
|
+
|
|
409
|
+
# Build sources array
|
|
410
|
+
if [ "$DIRECTIVE_CACHE_VALID" = "true" ]; then
|
|
411
|
+
SEARCH_SOURCES='["patterns"]'
|
|
412
|
+
else
|
|
413
|
+
SEARCH_SOURCES='["patterns", "directives"]'
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
# Call ekkOS MCP gateway using node (no curl dependency for JSON handling)
|
|
417
|
+
SEARCH_RESPONSE=$(node -e "
|
|
418
|
+
const https = require('https');
|
|
419
|
+
const body = JSON.stringify({
|
|
420
|
+
tool: 'ekkOS_Search',
|
|
421
|
+
arguments: {
|
|
422
|
+
query: process.argv[1],
|
|
423
|
+
limit: 5,
|
|
424
|
+
sources: $SEARCH_SOURCES
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
const req = https.request({
|
|
428
|
+
hostname: 'api.ekkos.dev',
|
|
429
|
+
path: '/api/v1/mcp/call',
|
|
430
|
+
method: 'POST',
|
|
431
|
+
headers: {
|
|
432
|
+
'Authorization': 'Bearer ' + process.argv[2],
|
|
433
|
+
'Content-Type': 'application/json',
|
|
434
|
+
'Content-Length': Buffer.byteLength(body)
|
|
435
|
+
},
|
|
436
|
+
timeout: 2000
|
|
437
|
+
}, (res) => {
|
|
438
|
+
let data = '';
|
|
439
|
+
res.on('data', chunk => data += chunk);
|
|
440
|
+
res.on('end', () => console.log(data));
|
|
441
|
+
});
|
|
442
|
+
req.on('error', () => console.log('{}'));
|
|
443
|
+
req.on('timeout', () => { req.destroy(); console.log('{}'); });
|
|
444
|
+
req.write(body);
|
|
445
|
+
req.end();
|
|
446
|
+
" "$USER_QUERY" "$EKKOS_API_KEY" 2>/dev/null || echo '{}')
|
|
447
|
+
|
|
448
|
+
# Parse pattern count using node
|
|
449
|
+
PATTERN_COUNT=$(echo "$SEARCH_RESPONSE" | node -e "
|
|
450
|
+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
451
|
+
console.log((data?.result?.results?.patterns?.length || 0));
|
|
452
|
+
" 2>/dev/null || echo "0")
|
|
453
|
+
|
|
454
|
+
if [ "$PATTERN_COUNT" -gt 0 ]; then
|
|
455
|
+
# Update golden loop
|
|
456
|
+
node -e "
|
|
457
|
+
const fs = require('fs');
|
|
458
|
+
const data = {
|
|
459
|
+
phase: 'inject',
|
|
460
|
+
turn: $TURN_NUMBER,
|
|
461
|
+
session: '$SESSION_ID',
|
|
462
|
+
timestamp: new Date().toISOString(),
|
|
463
|
+
stats: { retrieved: $PATTERN_COUNT, applied: 0, forged: 0 }
|
|
464
|
+
};
|
|
465
|
+
fs.writeFileSync('$GOLDEN_LOOP_FILE', JSON.stringify(data, null, 2));
|
|
466
|
+
" 2>/dev/null || true
|
|
467
|
+
|
|
468
|
+
# Format patterns for injection
|
|
469
|
+
RETRIEVED_PATTERNS=$(echo "$SEARCH_RESPONSE" | node -e "
|
|
470
|
+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
471
|
+
const patterns = data?.result?.results?.patterns || [];
|
|
472
|
+
patterns.forEach(p => {
|
|
473
|
+
console.log('**' + (p.title || 'Untitled') + '**');
|
|
474
|
+
console.log((p.problem || p.guidance || '') + '\\n');
|
|
475
|
+
console.log('## Solution');
|
|
476
|
+
console.log((p.solution || p.content || '') + '\\n');
|
|
477
|
+
console.log('Success Rate: ' + Math.round((p.success_rate || 0) * 100) + '%');
|
|
478
|
+
console.log('Applied: ' + (p.applied_count || 0) + ' times\\n');
|
|
479
|
+
});
|
|
480
|
+
" 2>/dev/null || echo "")
|
|
467
481
|
fi
|
|
468
|
-
fi
|
|
469
|
-
fi
|
|
470
482
|
|
|
471
|
-
#
|
|
483
|
+
# Handle directives
|
|
484
|
+
if [ "$DIRECTIVE_CACHE_VALID" = "true" ] && [ -f "$DIRECTIVE_CACHE_FILE" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
485
|
+
DIRECTIVE_COUNT=$(node "$JSON_PARSE_HELPER" "$DIRECTIVE_CACHE_FILE" '.count' 2>/dev/null || echo "0")
|
|
486
|
+
RETRIEVED_DIRECTIVES=$(node "$JSON_PARSE_HELPER" "$DIRECTIVE_CACHE_FILE" '.formatted' 2>/dev/null || echo "")
|
|
487
|
+
else
|
|
488
|
+
DIRECTIVE_COUNT=$(echo "$SEARCH_RESPONSE" | node -e "
|
|
489
|
+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
490
|
+
console.log((data?.result?.results?.directives?.length || 0));
|
|
491
|
+
" 2>/dev/null || echo "0")
|
|
492
|
+
|
|
493
|
+
if [ "$DIRECTIVE_COUNT" -gt 0 ]; then
|
|
494
|
+
RETRIEVED_DIRECTIVES=$(echo "$SEARCH_RESPONSE" | node -e "
|
|
495
|
+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
496
|
+
const directives = data?.result?.results?.directives || [];
|
|
497
|
+
const byType = { MUST: [], NEVER: [], PREFER: [], AVOID: [] };
|
|
498
|
+
directives.forEach(d => {
|
|
499
|
+
if (byType[d.type]) byType[d.type].push(' - ' + d.rule);
|
|
500
|
+
});
|
|
501
|
+
let output = 'USER DIRECTIVES (FOLLOW THESE):';
|
|
502
|
+
if (byType.MUST.length) output += '\\n\\nMUST:\\n' + byType.MUST.join('\\n');
|
|
503
|
+
if (byType.NEVER.length) output += '\\n\\nNEVER:\\n' + byType.NEVER.join('\\n');
|
|
504
|
+
if (byType.PREFER.length) output += '\\n\\nPREFER:\\n' + byType.PREFER.join('\\n');
|
|
505
|
+
if (byType.AVOID.length) output += '\\n\\nAVOID:\\n' + byType.AVOID.join('\\n');
|
|
506
|
+
console.log(output);
|
|
507
|
+
" 2>/dev/null || echo "")
|
|
508
|
+
|
|
509
|
+
# Save to cache
|
|
510
|
+
node -e "
|
|
511
|
+
const fs = require('fs');
|
|
512
|
+
fs.writeFileSync('$DIRECTIVE_CACHE_FILE', JSON.stringify({
|
|
513
|
+
count: $DIRECTIVE_COUNT,
|
|
514
|
+
formatted: process.argv[1],
|
|
515
|
+
cached_at: Math.floor(Date.now() / 1000)
|
|
516
|
+
}, null, 2));
|
|
517
|
+
" "$RETRIEVED_DIRECTIVES" 2>/dev/null || true
|
|
518
|
+
fi
|
|
519
|
+
fi
|
|
520
|
+
fi
|
|
472
521
|
|
|
473
522
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
474
523
|
# COLORS
|
|
@@ -483,105 +532,66 @@ RESET='\033[0m'
|
|
|
483
532
|
|
|
484
533
|
CURRENT_TIME=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
|
|
485
534
|
|
|
486
|
-
# Generate session name
|
|
535
|
+
# Generate session name
|
|
487
536
|
SESSION_NAME=""
|
|
488
537
|
if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ] && [ "$SESSION_ID" != "null" ]; then
|
|
489
|
-
|
|
538
|
+
SESSION_NAME=$(uuid_to_words "$SESSION_ID")
|
|
490
539
|
fi
|
|
491
540
|
|
|
492
|
-
#
|
|
493
|
-
|
|
494
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
495
|
-
# REMOVED: Hook used to intercept /continue and do simple restoration
|
|
496
|
-
# NOW: Let /continue Skill handle it - supports session names + intelligent narrative
|
|
497
|
-
#
|
|
498
|
-
# Why this changed:
|
|
499
|
-
# - Hook was ignoring session name argument (always used "current")
|
|
500
|
-
# - Hook couldn't provide intelligent narrative briefing
|
|
501
|
-
# - Skill system now has proper name→UUID resolution in API
|
|
502
|
-
#
|
|
503
|
-
# OLD BEHAVIOR (removed):
|
|
504
|
-
# - Hook caught /continue → API call with "current" → exit
|
|
505
|
-
# NEW BEHAVIOR:
|
|
506
|
-
# - /continue groovy-cactus → Skill system → API with session name → intelligent briefing
|
|
507
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
508
|
-
|
|
509
|
-
# Note: Session name is shown in status line for easy reference:
|
|
510
|
-
# Example: "🧠 ekkOS Memory | Turn 42 | groovy-cactus | 2026-01-12 09:40 AM EST"
|
|
511
|
-
|
|
512
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
513
|
-
# AUTO-RESTORE REMOVED: Manual /continue only (saves 79% token burn!)
|
|
514
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
515
|
-
# WHY REMOVED:
|
|
516
|
-
# - Auto-restore burns 5,000 tokens per turn (250K tokens over 50 turns)
|
|
517
|
-
# - Manual /continue: 2,000 tokens once + clean slate (52K total = 79% savings!)
|
|
518
|
-
# - Manual /continue is 10x more powerful (Bash + multi-source + narrative)
|
|
519
|
-
# - User has control (can choose session, can skip if starting fresh)
|
|
520
|
-
# - Explicit > implicit (user knows exactly what's happening)
|
|
521
|
-
#
|
|
522
|
-
# OLD BEHAVIOR (removed):
|
|
523
|
-
# - Compaction detection → auto-inject 10 turns
|
|
524
|
-
# - Post-clear detection → auto-inject 10 turns
|
|
525
|
-
# NEW BEHAVIOR:
|
|
526
|
-
# - User types: /continue groovy-cactus
|
|
527
|
-
# - Skill runs with full Bash power + intelligent narrative
|
|
528
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
529
|
-
|
|
530
|
-
# Simple status line - no context warnings, Claude handles its own context
|
|
531
|
-
echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| Turn ${TURN_NUMBER} | ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
541
|
+
# Simple status line
|
|
542
|
+
echo -e "${CYAN}${BOLD}ekkOS Memory${RESET} ${DIM}| Turn ${TURN_NUMBER} | ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
532
543
|
|
|
533
544
|
# Output skill reminder if detected
|
|
534
545
|
if [ -n "$SKILL_REMINDER" ]; then
|
|
535
|
-
|
|
536
|
-
|
|
546
|
+
echo ""
|
|
547
|
+
echo -e "${MAGENTA}${BOLD}$SKILL_REMINDER${RESET}"
|
|
537
548
|
fi
|
|
538
549
|
|
|
539
|
-
#
|
|
540
|
-
# SMART INJECTION: Only on Turn 1, post-clear, or directive trigger
|
|
550
|
+
# Inject directives FIRST (highest priority)
|
|
541
551
|
if [ "$SHOULD_INJECT_DIRECTIVES" = "true" ] && [ -n "$RETRIEVED_DIRECTIVES" ] && [ "$DIRECTIVE_COUNT" -gt 0 ]; then
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
552
|
+
echo ""
|
|
553
|
+
echo "<system-reminder>"
|
|
554
|
+
echo -e "$RETRIEVED_DIRECTIVES"
|
|
555
|
+
echo "</system-reminder>"
|
|
546
556
|
fi
|
|
547
557
|
|
|
548
|
-
#
|
|
558
|
+
# Inject retrieved patterns into context
|
|
549
559
|
if [ -n "$RETRIEVED_PATTERNS" ] && [ "$PATTERN_COUNT" -gt 0 ]; then
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
560
|
+
echo ""
|
|
561
|
+
echo "<system-reminder>"
|
|
562
|
+
echo "RETRIEVED PATTERNS FROM ekkOS MEMORY ($PATTERN_COUNT patterns found)"
|
|
563
|
+
echo ""
|
|
564
|
+
echo -e "$RETRIEVED_PATTERNS"
|
|
565
|
+
echo ""
|
|
566
|
+
echo "These patterns were automatically retrieved based on your query. Review and apply relevant solutions."
|
|
567
|
+
echo "</system-reminder>"
|
|
568
|
+
echo ""
|
|
569
|
+
echo "MANDATORY PATTERN ACKNOWLEDGMENT (PatternGuard):"
|
|
570
|
+
echo "You MUST acknowledge ALL $PATTERN_COUNT patterns above - either SELECT or SKIP each one."
|
|
571
|
+
echo ""
|
|
572
|
+
echo "For patterns you USE:"
|
|
573
|
+
echo "[ekkOS_SELECT]"
|
|
574
|
+
echo "- id: <pattern_id>"
|
|
575
|
+
echo " reason: <1-line why using>"
|
|
576
|
+
echo " confidence: <0.0-1.0>"
|
|
577
|
+
echo "[/ekkOS_SELECT]"
|
|
578
|
+
echo ""
|
|
579
|
+
echo "For patterns NOT relevant:"
|
|
580
|
+
echo "[ekkOS_SKIP]"
|
|
581
|
+
echo "- id: <pattern_id>"
|
|
582
|
+
echo " reason: <1-line why not relevant>"
|
|
583
|
+
echo "[/ekkOS_SKIP]"
|
|
584
|
+
echo ""
|
|
585
|
+
echo "AUTOMATIC APPLICATION: When pattern matches your task, APPLY IT immediately without asking."
|
|
586
|
+
echo "Track outcome after: ekkOS_Outcome({success: true/false})"
|
|
587
|
+
echo ""
|
|
588
|
+
echo "100% coverage required. This is how the system learns what works."
|
|
579
589
|
fi
|
|
580
590
|
|
|
581
|
-
# Inject footer format reminder
|
|
591
|
+
# Inject footer format reminder
|
|
582
592
|
if [ -n "$SESSION_NAME" ] && [ "$SESSION_NAME" != "unknown-session" ]; then
|
|
583
|
-
|
|
584
|
-
|
|
593
|
+
echo ""
|
|
594
|
+
echo "<footer-format>End responses with: Claude Code ({Model}) - ekkOS - Turn ${TURN_NUMBER} - ${SESSION_NAME} - ${CURRENT_TIME}</footer-format>"
|
|
585
595
|
fi
|
|
586
596
|
|
|
587
597
|
exit 0
|