@ekkos/cli 0.2.8 → 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.
Files changed (36) hide show
  1. package/dist/cache/LocalSessionStore.d.ts +34 -21
  2. package/dist/cache/LocalSessionStore.js +169 -53
  3. package/dist/cache/capture.d.ts +19 -11
  4. package/dist/cache/capture.js +243 -76
  5. package/dist/cache/types.d.ts +14 -1
  6. package/dist/commands/doctor.d.ts +10 -0
  7. package/dist/commands/doctor.js +148 -73
  8. package/dist/commands/hooks.d.ts +109 -0
  9. package/dist/commands/hooks.js +668 -0
  10. package/dist/commands/run.d.ts +1 -0
  11. package/dist/commands/run.js +69 -21
  12. package/dist/index.js +42 -1
  13. package/dist/restore/RestoreOrchestrator.d.ts +17 -3
  14. package/dist/restore/RestoreOrchestrator.js +64 -22
  15. package/dist/utils/paths.d.ts +125 -0
  16. package/dist/utils/paths.js +283 -0
  17. package/dist/utils/session-words.json +30 -111
  18. package/package.json +1 -1
  19. package/templates/ekkos-manifest.json +223 -0
  20. package/templates/helpers/json-parse.cjs +101 -0
  21. package/templates/hooks/assistant-response.ps1 +256 -0
  22. package/templates/hooks/assistant-response.sh +124 -64
  23. package/templates/hooks/session-start.ps1 +107 -2
  24. package/templates/hooks/session-start.sh +201 -166
  25. package/templates/hooks/stop.ps1 +124 -3
  26. package/templates/hooks/stop.sh +470 -843
  27. package/templates/hooks/user-prompt-submit.ps1 +107 -22
  28. package/templates/hooks/user-prompt-submit.sh +403 -393
  29. package/templates/project-stubs/session-start.ps1 +63 -0
  30. package/templates/project-stubs/session-start.sh +55 -0
  31. package/templates/project-stubs/stop.ps1 +63 -0
  32. package/templates/project-stubs/stop.sh +55 -0
  33. package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
  34. package/templates/project-stubs/user-prompt-submit.sh +55 -0
  35. package/templates/shared/hooks-enabled.json +22 -0
  36. 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
- # SESSION NAME GENERATION (must be defined early - used by ekkos-capture)
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
- ADJECTIVES=(
19
- "cosmic" "turbo" "mega" "hyper" "quantum" "atomic" "stellar" "epic"
20
- "mighty" "groovy" "zippy" "snappy" "jazzy" "funky" "zesty" "peppy"
21
- "spicy" "crispy" "fluffy" "sparkly" "chunky" "bouncy" "bubbly" "sassy"
22
- "slick" "sleek" "bold" "nifty" "perky" "plucky" "witty" "nimble"
23
- "dapper" "fancy" "quirky" "punchy" "swift" "brave" "clever" "dandy"
24
- "eager" "fiery" "golden" "hasty" "icy" "jolly" "keen" "lively"
25
- "merry" "noble" "odd" "plush" "quick" "royal" "silly" "tidy"
26
- "ultra" "vivid" "wacky" "zany" "alpha" "beta" "cyber" "delta"
27
- "electric" "foggy" "giga" "hazy" "ionic" "jumpy" "kinky" "lunar"
28
- "magic" "nerdy" "omega" "pixel" "quaint" "retro" "solar" "techno"
29
- "unified" "viral" "wonky" "xerox" "yappy" "zen" "agile" "binary"
30
- "chrome" "disco" "elastic" "fizzy" "glossy" "humble" "itchy" "jiffy"
31
- "kooky" "loopy" "moody" "noisy"
32
- )
33
- NOUNS=(
34
- "penguin" "panda" "otter" "narwhal" "alpaca" "llama" "badger" "walrus"
35
- "waffle" "pickle" "noodle" "pretzel" "muffin" "taco" "nugget" "biscuit"
36
- "rocket" "comet" "nebula" "quasar" "meteor" "photon" "pulsar" "nova"
37
- "ninja" "pirate" "wizard" "robot" "yeti" "phoenix" "sphinx" "kraken"
38
- "thunder" "blizzard" "tornado" "avalanche" "mango" "kiwi" "banana" "coconut"
39
- "donut" "espresso" "falafel" "gyro" "hummus" "icecream" "jambon" "kebab"
40
- "latte" "mocha" "nachos" "olive" "pasta" "quinoa" "ramen" "sushi"
41
- "tamale" "udon" "velvet" "wasabi" "xmas" "yogurt" "ziti" "anchor"
42
- "beacon" "canyon" "drifter" "echo" "falcon" "glacier" "harbor" "island"
43
- "jetpack" "kayak" "lagoon" "meadow" "nebula" "orbit" "parrot" "quest"
44
- "rapids" "summit" "tunnel" "umbrella" "volcano" "whisper" "xylophone" "yacht"
45
- "zephyr" "acorn" "bobcat" "cactus" "dolphin" "eagle" "ferret" "gopher"
46
- "hedgehog" "iguana" "jackal" "koala"
47
- )
48
- VERBS=(
49
- "runs" "jumps" "flies" "swims" "dives" "soars" "glides" "dashes"
50
- "zooms" "zips" "spins" "twirls" "bounces" "floats" "drifts" "sails"
51
- "climbs" "leaps" "hops" "skips" "rolls" "slides" "surfs" "rides"
52
- "builds" "creates" "forges" "shapes" "crafts" "designs" "codes" "types"
53
- "thinks" "dreams" "learns" "grows" "blooms" "shines" "glows" "sparks"
54
- "sings" "hums" "calls" "beeps" "clicks" "taps" "pings" "chimes"
55
- "wins" "leads" "helps" "saves" "guards" "shields" "heals" "fixes"
56
- "starts" "begins" "launches" "ignites" "blazes" "flares" "bursts" "pops"
57
- "waves" "nods" "winks" "grins" "smiles" "laughs" "cheers" "claps"
58
- "seeks" "finds" "spots" "tracks" "hunts" "chases" "catches" "grabs"
59
- "pushes" "pulls" "lifts" "throws" "kicks" "punts" "bats" "swings"
60
- "reads" "writes" "draws" "paints" "sculpts" "carves" "molds" "weaves"
61
- "cooks" "bakes" "grills" "fries"
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
- local uuid="$1"
66
- local hex="${uuid//-/}"
67
- hex="${hex:0:12}"
68
- if [[ ! "$hex" =~ ^[0-9a-fA-F]+$ ]]; then
69
- echo "unknown-session-starts"
70
- return
71
- fi
72
- local adj_seed=$((16#${hex:0:4}))
73
- local noun_seed=$((16#${hex:4:4}))
74
- local verb_seed=$((16#${hex:8:4}))
75
- local adj_idx=$((adj_seed % ${#ADJECTIVES[@]}))
76
- local noun_idx=$((noun_seed % ${#NOUNS[@]}))
77
- local verb_idx=$((verb_seed % ${#VERBS[@]}))
78
- echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}-${VERBS[$verb_idx]}"
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
- USER_QUERY=$(echo "$INPUT" | jq -r '.query // .message // .prompt // ""')
86
- RAW_SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
87
- TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""')
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" = "unknown" ] || [ "$RAW_SESSION_ID" = "null" ] || [ -z "$RAW_SESSION_ID" ]; then
93
- STATE_FILE="$HOME/.claude/state/current-session.json"
94
- if [ -f "$STATE_FILE" ]; then
95
- RAW_SESSION_ID=$(jq -r '.session_id // "unknown"' "$STATE_FILE" 2>/dev/null || echo "unknown")
96
- fi
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
- SKILL_REMINDERS+=("🔧 SKILL REQUIRED: Call Skill(skill: \"ekkOS_Memory_First\") FIRST before debugging")
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
- SKILL_REMINDERS+=("📅 SKILL REQUIRED: Call Skill(skill: \"ekkOS_Deep_Recall\") for time-based memory")
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
- SKILL_REMINDERS+=("⚙️ SKILL REQUIRED: Call Skill(skill: \"ekkOS_Preferences\") to capture directive")
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
- SKILL_REMINDERS+=("⚠️ SAFETY REQUIRED: Call ekkOS_Conflict before this destructive action")
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
- SKILL_REMINDERS+=("🗄️ SCHEMA REQUIRED: Call ekkOS_GetSchema for correct field names")
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
- SKILL_REMINDERS+=("🔐 SECRETS: Use ekkOS_StoreSecret to securely save credentials")
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
- SKILL_REMINDERS+=("📋 PLAN REQUIRED: Call ekkOS_Plan for complex multi-step tasks")
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
- SKILL_REMINDERS+=("🎯 LEARN: Consider calling ekkOS_Forge to capture this solution as a pattern")
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
- SKILL_REMINDERS+=("🔍 CODEBASE: Use ekkOS_Codebase for project-specific code search")
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
- # Take up to 3 most relevant reminders
176
- MAX_REMINDERS=3
177
- [ "$REMINDER_COUNT" -lt "$MAX_REMINDERS" ] && MAX_REMINDERS="$REMINDER_COUNT"
178
- for i in $(seq 0 $((MAX_REMINDERS - 1))); do
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
- SKILL_REMINDER="$SKILL_REMINDER${SKILL_REMINDERS[$i]}"
182
- done
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
- AUTH_TOKEN=$(jq -r '.hookApiKey // .apiKey // ""' "$EKKOS_CONFIG" 2>/dev/null || echo "")
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
- exit 0
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
- # Count user message entries in JSONL transcript
227
- TURN_NUMBER=$(grep -c '"type":"user"' "$TRANSCRIPT_PATH" 2>/dev/null || echo "1")
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 # Save for post-clear detection
274
+ TRANSCRIPT_TURN_COUNT=$TURN_NUMBER
235
275
  POST_CLEAR_DETECTED=false
236
276
  if [ "$SAVED_TURN_COUNT" -gt "$TURN_NUMBER" ]; then
237
- # Post-clear: INCREMENT from saved count (not just copy it)
238
- TURN_NUMBER=$((SAVED_TURN_COUNT + 1))
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
- # 🧠 WORKING MEMORY: Fast capture each turn (async, non-blocking)
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
- CAPTURE_TOKEN=$(jq -r '.hookApiKey // .apiKey // ""' "$HOME/.ekkos/config.json" 2>/dev/null || echo "")
249
- if [ -n "$CAPTURE_TOKEN" ] && [ "$CAPTURE_TOKEN" != "null" ]; then
250
- # Async capture to Redis/Supabase - doesn't block hook execution
251
- (curl -s -X POST "$MEMORY_API_URL/api/v1/working/fast-capture" \
252
- -H "Authorization: Bearer $CAPTURE_TOKEN" \
253
- -H "Content-Type: application/json" \
254
- -d "{\"session_id\":\"$RAW_SESSION_ID\",\"turn\":$TURN_NUMBER,\"query\":$(echo "$USER_QUERY" | jq -Rs .)}" \
255
- >/dev/null 2>&1) &
256
- fi
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
- # 💾 LOCAL CACHE: Tier 0 capture for instant /continue (async, non-blocking)
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
- # Generate session name if not already set
264
- if [ -z "$SESSION_NAME" ] || [ "$SESSION_NAME" = "unknown-session-starts" ]; then
265
- SESSION_NAME=$(uuid_to_words "$RAW_SESSION_ID" 2>/dev/null || echo "unknown-session")
266
- fi
267
- # Async local capture - writes to ~/.ekkos/cache/sessions/<uuid>.jsonl
268
- (ekkos-capture user "$RAW_SESSION_ID" "$SESSION_NAME" "$TURN_NUMBER" "$USER_QUERY" "$PROJECT_ROOT" \
269
- >/dev/null 2>&1) &
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
- # 📥 GOLDEN LOOP: CAPTURE PHASE - Track turn start
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 (extension watches this for real-time updates)
279
- jq -n \
280
- --arg phase "capture" \
281
- --argjson turn "$TURN_NUMBER" \
282
- --arg session "$SESSION_ID" \
283
- --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
284
- '{
285
- phase: $phase,
286
- turn: $turn,
287
- session: $session,
288
- timestamp: $timestamp,
289
- stats: {
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
- # 🔍 GOLDEN LOOP: RETRIEVE PHASE - Auto-retrieve patterns from ekkOS
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
- EKKOS_API_KEY=$(cat "$HOME/.ekkos/.hookApiKey" 2>/dev/null || echo "")
302
- elif [ -f "$HOME/.ekkos/config.json" ]; then
303
- EKKOS_API_KEY=$(jq -r '.hookApiKey // ""' "$HOME/.ekkos/config.json" 2>/dev/null || echo "")
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
- # 📦 DIRECTIVE CACHE: Local cache to avoid API calls every turn
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 # 1 hour in seconds
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
- DIRECTIVE_TRIGGER_DETECTED=true
377
+ DIRECTIVE_TRIGGER_DETECTED=true
330
378
  fi
331
379
 
332
- if [ -f "$DIRECTIVE_CACHE_FILE" ]; then
333
- CACHE_TIMESTAMP=$(jq -r '.cached_at // 0' "$DIRECTIVE_CACHE_FILE" 2>/dev/null || echo "0")
334
- CURRENT_TIMESTAMP=$(date +%s)
335
- CACHE_AGE=$((CURRENT_TIMESTAMP - CACHE_TIMESTAMP))
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
- # Cache is valid if <1 hour old AND no directive trigger detected
338
- if [ "$CACHE_AGE" -lt "$DIRECTIVE_CACHE_TTL" ] && [ "$DIRECTIVE_TRIGGER_DETECTED" = "false" ]; then
339
- DIRECTIVE_CACHE_VALID=true
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
- SHOULD_INJECT_DIRECTIVES=true
392
+ SHOULD_INJECT_DIRECTIVES=true
348
393
  fi
349
394
 
350
395
  if [ -n "$EKKOS_API_KEY" ] && [ -n "$USER_QUERY" ]; then
351
- # Update phase to RETRIEVE
352
- jq -n \
353
- --arg phase "retrieve" \
354
- --argjson turn "$TURN_NUMBER" \
355
- --arg session "$SESSION_ID" \
356
- --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
357
- '{
358
- phase: $phase,
359
- turn: $turn,
360
- session: $session,
361
- timestamp: $timestamp,
362
- stats: {
363
- retrieved: 0,
364
- applied: 0,
365
- forged: 0
366
- }
367
- }' > "$GOLDEN_LOOP_FILE" 2>/dev/null || true
368
-
369
- # ═══════════════════════════════════════════════════════════════════════════
370
- # PATTERN RETRIEVAL: Always search patterns (they're query-specific)
371
- # DIRECTIVE RETRIEVAL: Only if cache is invalid or trigger detected
372
- # ═══════════════════════════════════════════════════════════════════════════
373
-
374
- # Build sources array - always include patterns, conditionally include directives
375
- if [ "$DIRECTIVE_CACHE_VALID" = "true" ]; then
376
- SEARCH_SOURCES='["patterns"]'
377
- else
378
- SEARCH_SOURCES='["patterns", "directives"]'
379
- fi
380
-
381
- # Call ekkOS MCP gateway
382
- SEARCH_RESPONSE=$(curl -s -X POST "https://api.ekkos.dev/api/v1/mcp/call" \
383
- -H "Authorization: Bearer $EKKOS_API_KEY" \
384
- -H "Content-Type: application/json" \
385
- --max-time 2 \
386
- -d "{\"tool\": \"ekkOS_Search\", \"arguments\": {\"query\": $(echo "$USER_QUERY" | jq -Rs .), \"limit\": 5, \"sources\": $SEARCH_SOURCES}}" 2>/dev/null || echo '{"result": {"results": {"patterns": [], "directives": []}}}')
387
-
388
- # Count patterns retrieved (MCP response: .result.results.patterns)
389
- PATTERN_COUNT=$(echo "$SEARCH_RESPONSE" | jq -r '.result.results.patterns | length' 2>/dev/null || echo "0")
390
-
391
- # Update golden loop with retrieved count
392
- if [ "$PATTERN_COUNT" -gt 0 ]; then
393
- jq -n \
394
- --arg phase "inject" \
395
- --argjson turn "$TURN_NUMBER" \
396
- --arg session "$SESSION_ID" \
397
- --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
398
- --argjson retrieved "$PATTERN_COUNT" \
399
- '{
400
- phase: $phase,
401
- turn: $turn,
402
- session: $session,
403
- timestamp: $timestamp,
404
- stats: {
405
- retrieved: $retrieved,
406
- applied: 0,
407
- forged: 0
408
- }
409
- }' > "$GOLDEN_LOOP_FILE" 2>/dev/null || true
410
-
411
- # Format patterns for injection (MCP response: .result.results.patterns)
412
- RETRIEVED_PATTERNS=$(echo "$SEARCH_RESPONSE" | jq -r '
413
- .result.results.patterns[]? |
414
- "**\(.title)**\n\(.problem // .guidance // "")\n\n## Solution\n\(.solution // .content // "")\n\nSuccess Rate: \((.success_rate // 0) * 100)%\nApplied: \(.applied_count // 0) times\n"
415
- ' 2>/dev/null || echo "")
416
- fi
417
-
418
- # ═══════════════════════════════════════════════════════════════════════════
419
- # DIRECTIVE HANDLING: Use cache if valid, otherwise process from response
420
- # ═══════════════════════════════════════════════════════════════════════════
421
- if [ "$DIRECTIVE_CACHE_VALID" = "true" ]; then
422
- # Load directives from cache
423
- DIRECTIVE_COUNT=$(jq -r '.count // 0' "$DIRECTIVE_CACHE_FILE" 2>/dev/null || echo "0")
424
- RETRIEVED_DIRECTIVES=$(jq -r '.formatted // ""' "$DIRECTIVE_CACHE_FILE" 2>/dev/null || echo "")
425
- else
426
- # Extract and format DIRECTIVES from API response
427
- DIRECTIVE_COUNT=$(echo "$SEARCH_RESPONSE" | jq -r '.result.results.directives | length' 2>/dev/null || echo "0")
428
-
429
- if [ "$DIRECTIVE_COUNT" -gt 0 ]; then
430
- # Group directives by type for clean display
431
- MUST_DIRECTIVES=$(echo "$SEARCH_RESPONSE" | jq -r '.result.results.directives[] | select(.type == "MUST") | " • \(.rule)"' 2>/dev/null || echo "")
432
- NEVER_DIRECTIVES=$(echo "$SEARCH_RESPONSE" | jq -r '.result.results.directives[] | select(.type == "NEVER") | " • \(.rule)"' 2>/dev/null || echo "")
433
- PREFER_DIRECTIVES=$(echo "$SEARCH_RESPONSE" | jq -r '.result.results.directives[] | select(.type == "PREFER") | " • \(.rule)"' 2>/dev/null || echo "")
434
- AVOID_DIRECTIVES=$(echo "$SEARCH_RESPONSE" | jq -r '.result.results.directives[] | select(.type == "AVOID") | " • \(.rule)"' 2>/dev/null || echo "")
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
- # Context tracking removed - Claude Code handles its own context management
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 (arrays and uuid_to_words defined at top of file)
535
+ # Generate session name
487
536
  SESSION_NAME=""
488
537
  if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ] && [ "$SESSION_ID" != "null" ]; then
489
- SESSION_NAME=$(uuid_to_words "$SESSION_ID")
538
+ SESSION_NAME=$(uuid_to_words "$SESSION_ID")
490
539
  fi
491
540
 
492
- # ═══════════════════════════════════════════════════════════════════════════
493
- # "/continue" COMMAND: Delegated to Skill system (DO NOT INTERCEPT)
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
- echo ""
536
- echo -e "${MAGENTA}${BOLD}$SKILL_REMINDER${RESET}"
546
+ echo ""
547
+ echo -e "${MAGENTA}${BOLD}$SKILL_REMINDER${RESET}"
537
548
  fi
538
549
 
539
- # 💉 GOLDEN LOOP: INJECT PHASE - Inject directives FIRST (highest priority)
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
- echo ""
543
- echo "<system-reminder>"
544
- echo -e "$RETRIEVED_DIRECTIVES"
545
- echo "</system-reminder>"
552
+ echo ""
553
+ echo "<system-reminder>"
554
+ echo -e "$RETRIEVED_DIRECTIVES"
555
+ echo "</system-reminder>"
546
556
  fi
547
557
 
548
- # 💉 GOLDEN LOOP: INJECT PHASE - Inject retrieved patterns into context
558
+ # Inject retrieved patterns into context
549
559
  if [ -n "$RETRIEVED_PATTERNS" ] && [ "$PATTERN_COUNT" -gt 0 ]; then
550
- echo ""
551
- echo "<system-reminder>"
552
- echo "🔍 RETRIEVED PATTERNS FROM ekkOS MEMORY ($PATTERN_COUNT patterns found)"
553
- echo ""
554
- echo -e "$RETRIEVED_PATTERNS"
555
- echo ""
556
- echo "These patterns were automatically retrieved based on your query. Review and apply relevant solutions."
557
- echo "</system-reminder>"
558
- echo ""
559
- echo "🔴 MANDATORY PATTERN ACKNOWLEDGMENT (PatternGuard):"
560
- echo "You MUST acknowledge ALL $PATTERN_COUNT patterns above - either SELECT or SKIP each one."
561
- echo ""
562
- echo "For patterns you USE:"
563
- echo "[ekkOS_SELECT]"
564
- echo "- id: <pattern_id>"
565
- echo " reason: <1-line why using>"
566
- echo " confidence: <0.0-1.0>"
567
- echo "[/ekkOS_SELECT]"
568
- echo ""
569
- echo "For patterns NOT relevant:"
570
- echo "[ekkOS_SKIP]"
571
- echo "- id: <pattern_id>"
572
- echo " reason: <1-line why not relevant>"
573
- echo "[/ekkOS_SKIP]"
574
- echo ""
575
- echo "AUTOMATIC APPLICATION: When pattern matches your task, APPLY IT immediately without asking."
576
- echo "Track outcome after: ekkOS_Outcome({success: true/false})"
577
- echo ""
578
- echo "100% coverage required. This is how the system learns what works."
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 (helps Claude remember session name)
591
+ # Inject footer format reminder
582
592
  if [ -n "$SESSION_NAME" ] && [ "$SESSION_NAME" != "unknown-session" ]; then
583
- echo ""
584
- echo "<footer-format>End responses with: Claude Code ({Model}) · 🧠 **ekkOS_™** · Turn ${TURN_NUMBER} · ${SESSION_NAME} · 📅 ${CURRENT_TIME}</footer-format>"
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