@ekkos/cli 1.0.34 → 1.0.35

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 (51) hide show
  1. package/dist/capture/jsonl-rewriter.js +72 -7
  2. package/dist/commands/dashboard.js +186 -557
  3. package/dist/commands/init.js +3 -15
  4. package/dist/commands/run.js +222 -256
  5. package/dist/commands/setup.js +0 -47
  6. package/dist/commands/swarm-dashboard.js +4 -13
  7. package/dist/deploy/instructions.d.ts +2 -5
  8. package/dist/deploy/instructions.js +8 -11
  9. package/dist/deploy/settings.js +21 -15
  10. package/dist/deploy/skills.d.ts +0 -8
  11. package/dist/deploy/skills.js +0 -26
  12. package/dist/index.js +2 -2
  13. package/dist/lib/usage-parser.js +1 -2
  14. package/dist/utils/platform.d.ts +0 -3
  15. package/dist/utils/platform.js +1 -4
  16. package/dist/utils/session-binding.d.ts +1 -1
  17. package/dist/utils/session-binding.js +2 -3
  18. package/package.json +4 -2
  19. package/templates/CLAUDE.md +23 -135
  20. package/templates/agents/README.md +182 -0
  21. package/templates/agents/code-reviewer.md +166 -0
  22. package/templates/agents/debug-detective.md +169 -0
  23. package/templates/agents/ekkOS_Vercel.md +99 -0
  24. package/templates/agents/extension-manager.md +229 -0
  25. package/templates/agents/git-companion.md +185 -0
  26. package/templates/agents/github-test-agent.md +321 -0
  27. package/templates/agents/railway-manager.md +179 -0
  28. package/templates/ekkos-manifest.json +8 -8
  29. package/templates/hooks/assistant-response.ps1 +160 -256
  30. package/templates/hooks/assistant-response.sh +66 -130
  31. package/templates/hooks/hooks.json +0 -6
  32. package/templates/hooks/lib/contract.sh +31 -43
  33. package/templates/hooks/lib/count-tokens.cjs +0 -0
  34. package/templates/hooks/lib/ekkos-reminders.sh +0 -0
  35. package/templates/hooks/lib/state.sh +1 -53
  36. package/templates/hooks/session-start.ps1 +391 -91
  37. package/templates/hooks/session-start.sh +166 -201
  38. package/templates/hooks/stop.ps1 +341 -202
  39. package/templates/hooks/stop.sh +948 -275
  40. package/templates/hooks/user-prompt-submit.ps1 +548 -224
  41. package/templates/hooks/user-prompt-submit.sh +456 -382
  42. package/templates/plan-template.md +0 -0
  43. package/templates/spec-template.md +0 -0
  44. package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
  45. package/templates/windsurf-hooks/hooks.json +2 -9
  46. package/templates/windsurf-hooks/install.sh +0 -0
  47. package/templates/windsurf-hooks/lib/contract.sh +0 -2
  48. package/templates/windsurf-hooks/post-cascade-response.sh +0 -0
  49. package/templates/windsurf-hooks/pre-user-prompt.sh +0 -0
  50. package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
  51. package/README.md +0 -57
@@ -10,126 +10,207 @@
10
10
 
11
11
  set +e
12
12
 
13
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
- PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
15
-
16
13
  # ═══════════════════════════════════════════════════════════════════════════
17
- # CONFIG PATHS - No jq dependency (v1.2 spec)
18
- # Session words live in ~/.ekkos/ so they work in ANY project
14
+ # SESSION NAME GENERATION (must be defined early - used by ekkos-capture)
15
+ # Format: adj-noun-verb (e.g., "cosmic-penguin-runs")
16
+ # 100 × 100 × 100 = 1,000,000 combinations
19
17
  # ═══════════════════════════════════════════════════════════════════════════
20
- EKKOS_CONFIG_DIR="${EKKOS_CONFIG_DIR:-$HOME/.ekkos}"
21
- SESSION_WORDS_JSON="$EKKOS_CONFIG_DIR/session-words.json"
22
- SESSION_WORDS_DEFAULT="$EKKOS_CONFIG_DIR/.defaults/session-words.json"
23
- JSON_PARSE_HELPER="$EKKOS_CONFIG_DIR/.helpers/json-parse.cjs"
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
+ )
24
63
 
25
- # ═══════════════════════════════════════════════════════════════════════════
26
- # JSON PARSING HELPER - No jq required
27
- # ═══════════════════════════════════════════════════════════════════════════
28
- parse_json_value() {
29
- local json="$1"
30
- local path="$2"
31
- echo "$json" | node -e "
32
- const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
33
- const path = '$path'.replace(/^\./,'').split('.').filter(Boolean);
34
- let result = data;
35
- for (const p of path) {
36
- if (result === undefined || result === null) { result = undefined; break; }
37
- result = result[p];
38
- }
39
- if (result !== undefined && result !== null) console.log(result);
40
- " 2>/dev/null || echo ""
64
+ 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]}"
41
79
  }
42
80
 
81
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
82
+ PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
83
+
43
84
  INPUT=$(cat)
44
- USER_QUERY=$(parse_json_value "$INPUT" '.query')
45
- [ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ] && USER_QUERY=$(parse_json_value "$INPUT" '.message')
46
- [ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ] && USER_QUERY=$(parse_json_value "$INPUT" '.prompt')
47
- RAW_SESSION_ID=$(parse_json_value "$INPUT" '.session_id')
48
- [ -z "$RAW_SESSION_ID" ] && RAW_SESSION_ID="unknown"
49
- TRANSCRIPT_PATH=$(parse_json_value "$INPUT" '.transcript_path')
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 // ""')
50
88
 
51
89
  [ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ] && exit 0
52
90
 
53
91
  # Fallback: read session_id from saved state if not in INPUT
54
92
  if [ "$RAW_SESSION_ID" = "unknown" ] || [ "$RAW_SESSION_ID" = "null" ] || [ -z "$RAW_SESSION_ID" ]; then
55
93
  STATE_FILE="$HOME/.claude/state/current-session.json"
56
- if [ -f "$STATE_FILE" ] && [ -f "$JSON_PARSE_HELPER" ]; then
57
- RAW_SESSION_ID=$(node "$JSON_PARSE_HELPER" "$STATE_FILE" '.session_id' 2>/dev/null || echo "unknown")
58
- fi
59
-
60
- # VSCode extension fallback: Extract session ID from transcript path
61
- # Path format: ~/.claude/projects/<project>/<session-uuid>.jsonl
62
- if [ "$RAW_SESSION_ID" = "unknown" ] && [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
63
- RAW_SESSION_ID=$(basename "$TRANSCRIPT_PATH" .jsonl)
94
+ if [ -f "$STATE_FILE" ]; then
95
+ RAW_SESSION_ID=$(jq -r '.session_id // "unknown"' "$STATE_FILE" 2>/dev/null || echo "unknown")
64
96
  fi
65
97
  fi
66
98
 
67
99
  # ═══════════════════════════════════════════════════════════════════════════
68
- # SKILL AUTO-FIRE: Detect keywords and inject skill reminders
100
+ # INTELLIGENT TOOL ROUTER: Multi-trigger skill detection
101
+ # Detects ALL applicable skills/tools and injects as system reminder
69
102
  # ═══════════════════════════════════════════════════════════════════════════
70
- SKILL_REMINDER=""
103
+ SKILL_REMINDERS=()
71
104
  QUERY_LOWER=$(echo "$USER_QUERY" | tr '[:upper:]' '[:lower:]')
72
105
 
73
- # Memory First triggers
74
- if echo "$QUERY_LOWER" | grep -qE '(how do i|debug|error|bug|fix|not working|broken|fails|issue)'; then
75
- SKILL_REMINDER="🔧 SKILL REQUIRED: Call Skill(skill: \"ekkOS_Memory_First\") FIRST before debugging"
106
+ # ─────────────────────────────────────────────────────────────────────────────
107
+ # MANDATORY TRIGGERS (Always check ekkOS first)
108
+ # ─────────────────────────────────────────────────────────────────────────────
109
+
110
+ # Memory First - Debug/Error/Problem solving
111
+ 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")
76
113
  fi
77
114
 
78
- # Recall triggers
79
- if echo "$QUERY_LOWER" | grep -qE '(yesterday|last week|remember when|what did we|where did we leave)'; then
80
- SKILL_REMINDER="📅 SKILL REQUIRED: Call Skill(skill: \"ekkOS_Recall\") for time-based memory"
115
+ # ─────────────────────────────────────────────────────────────────────────────
116
+ # RECALL TRIGGERS (Time-based memory)
117
+ # ─────────────────────────────────────────────────────────────────────────────
118
+ 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")
81
120
  fi
82
121
 
83
- # Preferences triggers
84
- if echo "$QUERY_LOWER" | grep -qE '(always |never |i prefer|dont |don.t |avoid )'; then
85
- SKILL_REMINDER="⚙️ SKILL REQUIRED: Call Skill(skill: \"ekkOS_Preferences\") to capture directive"
122
+ # ─────────────────────────────────────────────────────────────────────────────
123
+ # DIRECTIVE TRIGGERS (User preferences)
124
+ # ─────────────────────────────────────────────────────────────────────────────
125
+ 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")
86
127
  fi
87
128
 
88
- # Safety triggers
89
- if echo "$QUERY_LOWER" | grep -qE '(delete|drop |rm -rf|deploy|push.*main|push.*master)'; then
90
- SKILL_REMINDER="⚠️ SKILL REQUIRED: Call Skill(skill: \"ekkOS_Safety\") before destructive action"
129
+ # ─────────────────────────────────────────────────────────────────────────────
130
+ # SAFETY TRIGGERS (Destructive actions)
131
+ # ─────────────────────────────────────────────────────────────────────────────
132
+ 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")
91
134
  fi
92
135
 
93
- # Schema triggers
94
- if echo "$QUERY_LOWER" | grep -qE '(sql|query|supabase|prisma|database|table|column)'; then
95
- SKILL_REMINDER="🗄️ SKILL REQUIRED: Call Skill(skill: \"ekkOS_Schema\") for correct field names"
136
+ # ─────────────────────────────────────────────────────────────────────────────
137
+ # SCHEMA TRIGGERS (Database operations)
138
+ # ─────────────────────────────────────────────────────────────────────────────
139
+ 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")
141
+ fi
142
+
143
+ # ─────────────────────────────────────────────────────────────────────────────
144
+ # SECRET TRIGGERS (API keys, credentials)
145
+ # ─────────────────────────────────────────────────────────────────────────────
146
+ 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")
148
+ fi
149
+
150
+ # ─────────────────────────────────────────────────────────────────────────────
151
+ # PLAN TRIGGERS (Complex multi-step tasks)
152
+ # ─────────────────────────────────────────────────────────────────────────────
153
+ 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")
155
+ fi
156
+
157
+ # ─────────────────────────────────────────────────────────────────────────────
158
+ # LEARN TRIGGERS (User expressing success/failure)
159
+ # ─────────────────────────────────────────────────────────────────────────────
160
+ 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")
162
+ fi
163
+
164
+ # ─────────────────────────────────────────────────────────────────────────────
165
+ # CODEBASE TRIGGERS (Project-specific code search)
166
+ # ─────────────────────────────────────────────────────────────────────────────
167
+ 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")
169
+ fi
170
+
171
+ # Combine skill reminders (only take first 3 to avoid noise)
172
+ SKILL_REMINDER=""
173
+ REMINDER_COUNT=${#SKILL_REMINDERS[@]}
174
+ 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
180
+ "
181
+ SKILL_REMINDER="$SKILL_REMINDER${SKILL_REMINDERS[$i]}"
182
+ done
96
183
  fi
97
184
 
98
185
  # ═══════════════════════════════════════════════════════════════════════════
99
- # Load auth - No jq
186
+ # Load auth
100
187
  # ═══════════════════════════════════════════════════════════════════════════
101
188
  EKKOS_CONFIG="$HOME/.ekkos/config.json"
102
189
  AUTH_TOKEN=""
103
- if [ -f "$EKKOS_CONFIG" ] && [ -f "$JSON_PARSE_HELPER" ]; then
104
- AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.hookApiKey' 2>/dev/null || echo "")
105
- if [ -z "$AUTH_TOKEN" ]; then
106
- AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.apiKey' 2>/dev/null || echo "")
107
- fi
190
+ if [ -f "$EKKOS_CONFIG" ]; then
191
+ AUTH_TOKEN=$(jq -r '.hookApiKey // .apiKey // ""' "$EKKOS_CONFIG" 2>/dev/null || echo "")
108
192
  fi
109
193
  [ -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 "")
110
194
 
111
195
  MEMORY_API_URL="https://mcp.ekkos.dev"
112
196
 
113
197
  # ═══════════════════════════════════════════════════════════════════════════
114
- # Session ID
198
+ # Session ID - NEW ID per conversation (not persisted 24h anymore)
199
+ # Each Claude Code session gets unique ID for proper Time Machine grouping
115
200
  # ═══════════════════════════════════════════════════════════════════════════
116
201
  STATE_DIR="$PROJECT_ROOT/.claude/state"
117
202
  mkdir -p "$STATE_DIR" 2>/dev/null || true
118
203
  SESSION_FILE="$STATE_DIR/current-session.json"
119
204
 
205
+ # Use Claude's RAW_SESSION_ID exclusively
120
206
  SESSION_ID="$RAW_SESSION_ID"
121
207
 
208
+ # Skip if no valid session ID from Claude
122
209
  if [ -z "$SESSION_ID" ] || [ "$SESSION_ID" = "unknown" ] || [ "$SESSION_ID" = "null" ]; then
123
210
  exit 0
124
211
  fi
125
212
 
126
- # Check if SESSION_ID is a UUID (8-4-4-4-12 format)
127
- UUID_REGEX='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
128
- IS_UUID=false
129
- if [[ "$SESSION_ID" =~ $UUID_REGEX ]]; then
130
- IS_UUID=true
131
- fi
132
-
213
+ # Save for other hooks to reference (but don't reuse across conversations)
133
214
  echo "{\"session_id\": \"$SESSION_ID\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$SESSION_FILE"
134
215
 
135
216
  # ═══════════════════════════════════════════════════════════════════════════
@@ -138,376 +219,369 @@ echo "{\"session_id\": \"$SESSION_ID\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:
138
219
  PROJECT_SESSION_DIR="$STATE_DIR/sessions"
139
220
  mkdir -p "$PROJECT_SESSION_DIR" 2>/dev/null || true
140
221
  TURN_COUNTER_FILE="$PROJECT_SESSION_DIR/${SESSION_ID}.turn"
141
- CONTEXT_SIZE_FILE="$PROJECT_SESSION_DIR/${SESSION_ID}.context"
142
222
 
143
- # Count API round-trips from transcript to match TUI turn counter
223
+ # Count actual user messages in transcript for accurate turn number
144
224
  TURN_NUMBER=1
145
225
  if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
226
+ # Count user message entries in JSONL transcript
146
227
  TURN_NUMBER=$(grep -c '"type":"user"' "$TRANSCRIPT_PATH" 2>/dev/null || echo "1")
147
228
  [ "$TURN_NUMBER" -eq 0 ] && TURN_NUMBER=1
148
229
  fi
149
230
 
150
- # Detect post-clear: saved count higher than transcript means /clear happened
231
+ # PRESERVE HISTORY: Don't overwrite if saved count is higher (after /clear)
151
232
  SAVED_TURN_COUNT=0
152
233
  [ -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
153
235
  POST_CLEAR_DETECTED=false
154
-
155
236
  if [ "$SAVED_TURN_COUNT" -gt "$TURN_NUMBER" ]; then
156
- POST_CLEAR_DETECTED=true
237
+ # Post-clear: INCREMENT from saved count (not just copy it)
157
238
  TURN_NUMBER=$((SAVED_TURN_COUNT + 1))
239
+ POST_CLEAR_DETECTED=true
158
240
  fi
159
-
160
241
  echo "$TURN_NUMBER" > "$TURN_COUNTER_FILE"
161
242
 
162
243
  # ═══════════════════════════════════════════════════════════════════════════
163
- # Context size tracking - Uses tokenizer script (single source)
244
+ # 🧠 WORKING MEMORY: Fast capture each turn (async, non-blocking)
164
245
  # ═══════════════════════════════════════════════════════════════════════════
165
- PREV_CONTEXT_PERCENT=0
166
- [ -f "$CONTEXT_SIZE_FILE" ] && PREV_CONTEXT_PERCENT=$(cat "$CONTEXT_SIZE_FILE" 2>/dev/null || echo "0")
167
-
168
- TOKEN_PERCENT=0
169
- MAX_TOKENS=200000
170
- TOKENIZER_SCRIPT="$SCRIPT_DIR/lib/count-tokens.cjs"
171
-
172
- if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] && [ -f "$TOKENIZER_SCRIPT" ]; then
173
- TOKEN_COUNT=$(node "$TOKENIZER_SCRIPT" "$TRANSCRIPT_PATH" 2>/dev/null || echo "0")
174
- if [[ "$TOKEN_COUNT" =~ ^[0-9]+$ ]] && [ "$TOKEN_COUNT" -gt 0 ]; then
175
- TOKEN_PERCENT=$((TOKEN_COUNT * 100 / MAX_TOKENS))
176
- [ "$TOKEN_PERCENT" -gt 100 ] && TOKEN_PERCENT=100
177
- # In proxy mode, IPC compresses ~65-70% — show estimated post-compression %
178
- IPC_PERCENT=$((TOKEN_PERCENT * 30 / 100))
179
- [ "$IPC_PERCENT" -lt 1 ] && IPC_PERCENT=1
246
+ 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) &
180
256
  fi
181
257
  fi
182
258
 
183
- echo "$TOKEN_PERCENT" > "$CONTEXT_SIZE_FILE"
259
+ # ═══════════════════════════════════════════════════════════════════════════
260
+ # 💾 LOCAL CACHE: Tier 0 capture for instant /continue (async, non-blocking)
261
+ # ═══════════════════════════════════════════════════════════════════════════
262
+ 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) &
270
+ fi
184
271
 
185
272
  # ═══════════════════════════════════════════════════════════════════════════
186
- # COLORS
273
+ # 📥 GOLDEN LOOP: CAPTURE PHASE - Track turn start
187
274
  # ═══════════════════════════════════════════════════════════════════════════
188
- CYAN='\033[0;36m'
189
- GREEN='\033[0;32m'
190
- MAGENTA='\033[0;35m'
191
- DIM='\033[2m'
192
- BOLD='\033[1m'
193
- RESET='\033[0m'
275
+ GOLDEN_LOOP_FILE="$PROJECT_ROOT/.ekkos/golden-loop-current.json"
276
+ mkdir -p "$PROJECT_ROOT/.ekkos" 2>/dev/null || true
277
+
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
194
295
 
195
- CURRENT_TIME=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
296
+ # ═══════════════════════════════════════════════════════════════════════════
297
+ # 🔍 GOLDEN LOOP: RETRIEVE PHASE - Auto-retrieve patterns from ekkOS
298
+ # ═══════════════════════════════════════════════════════════════════════════
299
+ EKKOS_API_KEY=""
300
+ 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 "")
304
+ fi
305
+
306
+ RETRIEVED_PATTERNS=""
307
+ PATTERN_COUNT=0
308
+ RETRIEVED_DIRECTIVES=""
309
+ DIRECTIVE_COUNT=0
196
310
 
197
311
  # ═══════════════════════════════════════════════════════════════════════════
198
- # WORD-BASED SESSION NAMES - No jq, uses ~/.ekkos/ config (works in ANY project)
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
199
317
  # ═══════════════════════════════════════════════════════════════════════════
200
- declare -a ADJECTIVES
201
- declare -a NOUNS
202
- declare -a VERBS
203
- SESSION_WORDS_LOADED=false
318
+ DIRECTIVE_CACHE_DIR="$HOME/.ekkos/cache"
319
+ DIRECTIVE_CACHE_FILE="$DIRECTIVE_CACHE_DIR/directives.json"
320
+ DIRECTIVE_CACHE_TTL=3600 # 1 hour in seconds
321
+ mkdir -p "$DIRECTIVE_CACHE_DIR" 2>/dev/null || true
322
+
323
+ # Check if we need to refresh directive cache
324
+ DIRECTIVE_CACHE_VALID=false
325
+ DIRECTIVE_TRIGGER_DETECTED=false
326
+
327
+ # Smart detection: Check if query mentions directive-related keywords
328
+ 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
330
+ fi
204
331
 
205
- load_session_words() {
206
- if [ "$SESSION_WORDS_LOADED" = "true" ]; then return 0; fi
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))
207
336
 
208
- local words_file="$SESSION_WORDS_JSON"
209
- [ ! -f "$words_file" ] && words_file="$SESSION_WORDS_DEFAULT"
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
341
+ fi
210
342
 
211
- if [ ! -f "$words_file" ] || [ ! -f "$JSON_PARSE_HELPER" ]; then
212
- ADJECTIVES=("unknown"); NOUNS=("session"); VERBS=("starts")
213
- return 1
214
- fi
343
+ # Decide whether to inject directives this turn
344
+ # SMART INJECTION: Only on Turn 1, post-clear, or directive trigger
345
+ SHOULD_INJECT_DIRECTIVES=false
346
+ if [ "$TURN_NUMBER" -eq 1 ] || [ "$POST_CLEAR_DETECTED" = "true" ] || [ "$DIRECTIVE_TRIGGER_DETECTED" = "true" ]; then
347
+ SHOULD_INJECT_DIRECTIVES=true
348
+ fi
215
349
 
216
- if command -v node &>/dev/null; then
217
- if [ "${BASH_VERSINFO[0]}" -ge 4 ]; then
218
- readarray -t ADJECTIVES < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
219
- readarray -t NOUNS < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
220
- readarray -t VERBS < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
221
- else
222
- local i=0
223
- while IFS= read -r line; do ADJECTIVES[i]="$line"; ((i++)); done < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
224
- i=0
225
- while IFS= read -r line; do NOUNS[i]="$line"; ((i++)); done < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
226
- i=0
227
- while IFS= read -r line; do VERBS[i]="$line"; ((i++)); done < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
228
- fi
229
- [ ${#ADJECTIVES[@]} -eq 0 ] && ADJECTIVES=("unknown")
230
- [ ${#NOUNS[@]} -eq 0 ] && NOUNS=("session")
231
- [ ${#VERBS[@]} -eq 0 ] && VERBS=("starts")
232
- SESSION_WORDS_LOADED=true
233
- return 0
234
- fi
235
- ADJECTIVES=("unknown"); NOUNS=("session"); VERBS=("starts")
236
- return 1
237
- }
350
+ 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
238
380
 
239
- uuid_to_words() {
240
- local uuid="$1"
241
- load_session_words
242
-
243
- local hex="${uuid//-/}"
244
- hex="${hex:0:12}"
245
- [[ ! "$hex" =~ ^[0-9a-fA-F]+$ ]] && echo "unknown-session-starts" && return
246
-
247
- local adj_seed=$((16#${hex:0:4}))
248
- local noun_seed=$((16#${hex:4:4}))
249
- local verb_seed=$((16#${hex:8:4}))
250
- local adj_idx=$((adj_seed % ${#ADJECTIVES[@]}))
251
- local noun_idx=$((noun_seed % ${#NOUNS[@]}))
252
- local verb_idx=$((verb_seed % ${#VERBS[@]}))
253
- echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}-${VERBS[$verb_idx]}"
254
- }
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
255
417
 
256
- # Generate session name (or use as-is if already word-based)
257
- SESSION_NAME=""
258
- if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ] && [ "$SESSION_ID" != "null" ]; then
259
- if [ "$IS_UUID" = true ]; then
260
- SESSION_NAME=$(uuid_to_words "$SESSION_ID")
261
- else
262
- SESSION_NAME="$SESSION_ID"
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
263
467
  fi
468
+ fi
264
469
  fi
265
470
 
471
+ # Context tracking removed - Claude Code handles its own context management
472
+
266
473
  # ═══════════════════════════════════════════════════════════════════════════
267
- # SINGLE SOURCE OF TRUTH: Update ALL session tracking systems
474
+ # COLORS
268
475
  # ═══════════════════════════════════════════════════════════════════════════
269
- if [ -n "$SESSION_NAME" ]; then
270
- TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
271
-
272
- # 1. Project-level state file
273
- echo "{\"session_id\": \"$SESSION_ID\", \"session_name\": \"$SESSION_NAME\", \"timestamp\": \"$TIMESTAMP\"}" > "$SESSION_FILE" 2>/dev/null || true
274
-
275
- # 2. Global ekkOS state (for extension LOCAL-FIRST read)
276
- EKKOS_GLOBAL_STATE="$HOME/.ekkos/current-session.json"
277
- mkdir -p "$HOME/.ekkos" 2>/dev/null || true
278
- echo "{\"session_id\": \"$SESSION_ID\", \"session_name\": \"$SESSION_NAME\", \"project\": \"$PROJECT_ROOT\", \"timestamp\": \"$TIMESTAMP\"}" > "$EKKOS_GLOBAL_STATE" 2>/dev/null || true
279
-
280
- # 3. CLI state file
281
- CLI_STATE_FILE="$HOME/.ekkos/state.json"
282
- echo "{\"sessionId\": \"$SESSION_ID\", \"sessionName\": \"$SESSION_NAME\", \"turnNumber\": $TURN_NUMBER, \"lastUpdated\": \"$TIMESTAMP\", \"projectPath\": \"$PROJECT_ROOT\"}" > "$CLI_STATE_FILE" 2>/dev/null || true
283
-
284
- # 4. Multi-session tracking - No jq
285
- ACTIVE_SESSIONS_FILE="$HOME/.ekkos/active-sessions.json"
286
- node -e "
287
- const fs = require('fs');
288
- const sid = process.argv[1], sname = process.argv[2], ts = process.argv[3], proj = process.argv[4];
289
- try {
290
- let sessions = [];
291
- if (fs.existsSync('$ACTIVE_SESSIONS_FILE')) {
292
- sessions = JSON.parse(fs.readFileSync('$ACTIVE_SESSIONS_FILE', 'utf8') || '[]');
293
- }
294
- const idx = sessions.findIndex(s => s.sessionId === sid);
295
- if (idx >= 0) {
296
- sessions[idx] = {...sessions[idx], sessionName: sname, lastHeartbeat: ts, projectPath: proj};
297
- } else {
298
- sessions.push({sessionId: sid, sessionName: sname, pid: 0, startedAt: ts, projectPath: proj, lastHeartbeat: ts});
299
- }
300
- fs.writeFileSync('$ACTIVE_SESSIONS_FILE', JSON.stringify(sessions, null, 2));
301
- } catch(e) {}
302
- " "$SESSION_ID" "$SESSION_NAME" "$TIMESTAMP" "$PROJECT_ROOT" 2>/dev/null || true
303
-
304
- # 5. Update Redis via API
305
- if [ -n "$AUTH_TOKEN" ]; then
306
- curl -s -X POST "$MEMORY_API_URL/api/v1/working/session/current" \
307
- -H "Authorization: Bearer $AUTH_TOKEN" \
308
- -H "Content-Type: application/json" \
309
- -d "{\"session_name\": \"$SESSION_NAME\"}" \
310
- --connect-timeout 2 \
311
- --max-time 3 >/dev/null 2>&1 &
312
- fi
476
+ CYAN='\033[0;36m'
477
+ GREEN='\033[0;32m'
478
+ YELLOW='\033[1;33m'
479
+ MAGENTA='\033[0;35m'
480
+ DIM='\033[2m'
481
+ BOLD='\033[1m'
482
+ RESET='\033[0m'
313
483
 
314
- # 6. CRITICAL: Bind session name to proxy for R2 eviction paths - No jq
315
- EKKOS_USER_ID=""
316
- if [ -f "$HOME/.ekkos/config.json" ] && [ -f "$JSON_PARSE_HELPER" ]; then
317
- EKKOS_USER_ID=$(node "$JSON_PARSE_HELPER" "$HOME/.ekkos/config.json" '.userId' 2>/dev/null || echo "")
318
- fi
319
- if [ -n "$EKKOS_USER_ID" ] && [ -n "$SESSION_NAME" ]; then
320
- PENDING_SESSION="${EKKOS_PENDING_SESSION:-_pending}"
321
- node -e "
322
- const https = require('https');
323
- const body = JSON.stringify({
324
- userId: process.argv[1],
325
- realSession: process.argv[2],
326
- projectPath: process.argv[3],
327
- pendingSession: process.argv[4]
328
- });
329
- const req = https.request({
330
- hostname: 'mcp.ekkos.dev',
331
- path: '/proxy/session/bind',
332
- method: 'POST',
333
- headers: {'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body)}
334
- }, () => {});
335
- req.on('error', () => {});
336
- req.write(body);
337
- req.end();
338
- " "$EKKOS_USER_ID" "$SESSION_NAME" "$PROJECT_ROOT" "$PENDING_SESSION" 2>/dev/null &
339
- fi
484
+ CURRENT_TIME=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
485
+
486
+ # Generate session name (arrays and uuid_to_words defined at top of file)
487
+ SESSION_NAME=""
488
+ if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ] && [ "$SESSION_ID" != "null" ]; then
489
+ SESSION_NAME=$(uuid_to_words "$SESSION_ID")
340
490
  fi
341
491
 
342
492
  # ═══════════════════════════════════════════════════════════════════════════
343
- # "/continue" COMMAND: Run AFTER /clear to restore last 5 turns
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
344
507
  # ═══════════════════════════════════════════════════════════════════════════
345
- QUERY_LOWER=$(echo "$USER_QUERY" | tr '[:upper:]' '[:lower:]')
346
-
347
- if [[ "$USER_QUERY" == "/continue" ]] || [[ "$USER_QUERY" =~ ^/continue[[:space:]] ]] || [[ "$QUERY_LOWER" == "continue" ]] || [[ "$QUERY_LOWER" == "continue." ]]; then
348
- if [ -n "$AUTH_TOKEN" ]; then
349
- RESTORE_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/turns/recall" \
350
- -H "Authorization: Bearer $AUTH_TOKEN" \
351
- -H "Content-Type: application/json" \
352
- -d "{\"session_id\": \"current\", \"last_n\": 5, \"format\": \"detailed\"}" \
353
- --connect-timeout 3 \
354
- --max-time 5 2>/dev/null || echo '{"turns":[]}')
355
-
356
- RESTORED_COUNT=$(echo "$RESTORE_RESPONSE" | node -e "
357
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
358
- console.log((d.turns || []).length);
359
- " 2>/dev/null || echo "0")
360
-
361
- LAST_TASK=$(echo "$RESTORE_RESPONSE" | node -e "
362
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
363
- const turns = d.turns || [];
364
- console.log((turns[turns.length-1]?.user_query || 'unknown task').substring(0, 200));
365
- " 2>/dev/null || echo "unknown task")
366
-
367
- LAST_RESPONSE=$(echo "$RESTORE_RESPONSE" | node -e "
368
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
369
- const turns = d.turns || [];
370
- console.log((turns[turns.length-1]?.assistant_response || '').substring(0, 500));
371
- " 2>/dev/null || echo "")
372
-
373
- echo ""
374
- echo -e "${GREEN}${BOLD}✓ Session continued${RESET} ${DIM}(${RESTORED_COUNT} turns restored)${RESET}"
375
- echo ""
376
-
377
- echo "<system-reminder>"
378
- echo "═══════════════════════════════════════════════════════════════════════"
379
- echo "CONTEXT RESTORED - Resume seamlessly. DO NOT ask 'what were we doing?'"
380
- echo "═══════════════════════════════════════════════════════════════════════"
381
- echo ""
382
- echo "## Last User Request:"
383
- echo "$LAST_TASK"
384
- echo ""
385
- echo "## Your Last Response (truncated):"
386
- echo "$LAST_RESPONSE"
387
- echo ""
388
-
389
- if [ "$RESTORED_COUNT" -gt 1 ]; then
390
- echo "## Recent Context (older → newer):"
391
- echo "$RESTORE_RESPONSE" | node -e "
392
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
393
- const turns = d.turns || [];
394
- turns.slice(0, -1).forEach(t => {
395
- const q = (t.user_query || '...').substring(0, 100);
396
- console.log('- Turn ' + (t.turn_number || '?') + ': ' + q + '...');
397
- });
398
- " 2>/dev/null || true
399
- echo ""
400
- fi
401
-
402
- echo "═══════════════════════════════════════════════════════════════════════"
403
- echo "INSTRUCTION: Start your response with '✓ **Continuing** -' then pick up"
404
- echo "exactly where you left off. If mid-task, continue it. If done, ask what's next."
405
- echo "═══════════════════════════════════════════════════════════════════════"
406
- echo "</system-reminder>"
407
508
 
408
- echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${CURRENT_TIME}${RESET}"
409
- exit 0
410
- fi
411
- fi
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"
412
511
 
413
512
  # ═══════════════════════════════════════════════════════════════════════════
414
- # COMPACTION DETECTION: If context dropped dramatically, auto-restore
415
- # Was >50% last turn, now <15% = compaction happened
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
416
528
  # ═══════════════════════════════════════════════════════════════════════════
417
- if [ "$PREV_CONTEXT_PERCENT" -gt 50 ] && [ "$TOKEN_PERCENT" -lt 15 ] && [ -n "$AUTH_TOKEN" ]; then
418
- echo ""
419
- echo -e "${GREEN}${BOLD}🔄 CONTEXT RESTORED${RESET} ${DIM}| Compaction detected | Auto-loading recent turns...${RESET}"
420
529
 
421
- RESTORE_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/turns/recall" \
422
- -H "Authorization: Bearer $AUTH_TOKEN" \
423
- -H "Content-Type: application/json" \
424
- -d "{\"session_id\": \"${SESSION_ID}\", \"last_n\": 10, \"format\": \"summary\"}" \
425
- --connect-timeout 3 \
426
- --max-time 5 2>/dev/null || echo '{"turns":[]}')
427
-
428
- RESTORED_COUNT=$(echo "$RESTORE_RESPONSE" | node -e "
429
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
430
- console.log((d.turns || []).length);
431
- " 2>/dev/null || echo "0")
432
-
433
- if [ "$RESTORED_COUNT" -gt 0 ]; then
434
- echo -e "${GREEN} ✓${RESET} Restored ${RESTORED_COUNT} turns from Layer 2"
435
- echo ""
436
- echo -e "${MAGENTA}${BOLD}## Recent Context (auto-restored)${RESET}"
437
- echo ""
438
-
439
- echo "$RESTORE_RESPONSE" | node -e "
440
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
441
- (d.turns || []).forEach(t => {
442
- const q = (t.user_query || '...').substring(0, 120);
443
- const a = (t.assistant_response || '...').substring(0, 250);
444
- console.log('**Turn ' + (t.turn_number || '?') + '**: ' + q + '...\n> ' + a + '...\n');
445
- });
446
- " 2>/dev/null || true
447
-
448
- echo ""
449
- echo -e "${DIM}Full history: \"turns 1-${TURN_NUMBER}\" or \"recall yesterday\"${RESET}"
450
- fi
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}"
451
532
 
533
+ # Output skill reminder if detected
534
+ if [ -n "$SKILL_REMINDER" ]; then
452
535
  echo ""
453
- echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
454
-
455
- elif [ "$POST_CLEAR_DETECTED" = true ] && [ -n "$AUTH_TOKEN" ]; then
456
- # /clear detected - show visible restoration banner
457
- echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" >&2
458
- echo -e "${GREEN}${BOLD}🔄 SESSION CONTINUED${RESET} ${DIM}| ${TURN_NUMBER} turns preserved | Context restored${RESET}" >&2
459
- echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" >&2
536
+ echo -e "${MAGENTA}${BOLD}$SKILL_REMINDER${RESET}"
537
+ fi
460
538
 
539
+ # 💉 GOLDEN LOOP: INJECT PHASE - Inject directives FIRST (highest priority)
540
+ # SMART INJECTION: Only on Turn 1, post-clear, or directive trigger
541
+ if [ "$SHOULD_INJECT_DIRECTIVES" = "true" ] && [ -n "$RETRIEVED_DIRECTIVES" ] && [ "$DIRECTIVE_COUNT" -gt 0 ]; then
461
542
  echo ""
462
- echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
463
- echo -e "${GREEN}${BOLD}🔄 SESSION CONTINUED${RESET} ${DIM}| ${TURN_NUMBER} turns preserved | Restoring context...${RESET}"
464
- echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
465
-
466
- RESTORE_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/turns/recall" \
467
- -H "Authorization: Bearer $AUTH_TOKEN" \
468
- -H "Content-Type: application/json" \
469
- -d "{\"session_id\": \"${SESSION_ID}\", \"last_n\": 10, \"format\": \"summary\"}" \
470
- --connect-timeout 3 \
471
- --max-time 5 2>/dev/null || echo '{"turns":[]}')
472
-
473
- RESTORED_COUNT=$(echo "$RESTORE_RESPONSE" | node -e "
474
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
475
- console.log((d.turns || []).length);
476
- " 2>/dev/null || echo "0")
477
-
478
- if [ "$RESTORED_COUNT" -gt 0 ]; then
479
- echo -e "${GREEN} ✓${RESET} Restored ${RESTORED_COUNT} recent turns"
480
- echo ""
481
-
482
- echo "$RESTORE_RESPONSE" | node -e "
483
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
484
- (d.turns || []).forEach(t => {
485
- const q = (t.query_preview || t.user_query || '...').substring(0, 80);
486
- const a = (t.response_preview || t.assistant_response || '...').substring(0, 150);
487
- console.log('**Turn ' + (t.turn_number || '?') + '**: ' + q + '...\n> ' + a + '...\n');
488
- });
489
- " 2>/dev/null || true
490
- else
491
- echo -e "${GREEN} ✓${RESET} History preserved (${TURN_NUMBER} turns)"
492
- fi
543
+ echo "<system-reminder>"
544
+ echo -e "$RETRIEVED_DIRECTIVES"
545
+ echo "</system-reminder>"
546
+ fi
493
547
 
548
+ # 💉 GOLDEN LOOP: INJECT PHASE - Inject retrieved patterns into context
549
+ if [ -n "$RETRIEVED_PATTERNS" ] && [ "$PATTERN_COUNT" -gt 0 ]; then
494
550
  echo ""
495
- echo -e "${DIM}Full history: \"recall\" or \"turns 1-${TURN_NUMBER}\"${RESET}"
496
- echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
551
+ echo "<system-reminder>"
552
+ echo "🔍 RETRIEVED PATTERNS FROM ekkOS MEMORY ($PATTERN_COUNT patterns found)"
497
553
  echo ""
498
- echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
499
-
500
- elif [ "$TOKEN_PERCENT" -ge 50 ]; then
501
- echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ~${IPC_PERCENT:-0}% IPC | ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
502
-
503
- else
504
- echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
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."
505
579
  fi
506
580
 
507
- # Output skill reminder if detected
508
- if [ -n "$SKILL_REMINDER" ]; then
581
+ # Inject footer format reminder (helps Claude remember session name)
582
+ if [ -n "$SESSION_NAME" ] && [ "$SESSION_NAME" != "unknown-session" ]; then
509
583
  echo ""
510
- echo -e "${MAGENTA}${BOLD}$SKILL_REMINDER${RESET}"
584
+ echo "<footer-format>End responses with: Claude Code ({Model}) · 🧠 **ekkOS_™** · Turn ${TURN_NUMBER} · ${SESSION_NAME} · 📅 ${CURRENT_TIME}</footer-format>"
511
585
  fi
512
586
 
513
587
  exit 0