@ekkos/cli 1.0.33 → 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 +221 -259
  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
@@ -1,14 +1,6 @@
1
1
  #!/bin/bash
2
- # ═══════════════════════════════════════════════════════════════════════════
3
- # ekkOS_ Hook: AssistantResponse - Validates and enforces footer format
4
- # MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
5
- # EKKOS_MANAGED=1
6
- # ═══════════════════════════════════════════════════════════════════════════
2
+ # Post-response hook: Validates and enforces ekkOS footer format
7
3
  # Runs AFTER assistant response, checks footer compliance
8
- # Per spec v1.2 Addendum: NO jq, NO hardcoded arrays
9
- # ═══════════════════════════════════════════════════════════════════════════
10
-
11
- set +e
12
4
 
13
5
  RESPONSE_FILE="$1"
14
6
  HOOK_ENV="$2"
@@ -18,143 +10,87 @@ if [[ ! -f "$RESPONSE_FILE" ]]; then
18
10
  exit 0
19
11
  fi
20
12
 
21
- # ═══════════════════════════════════════════════════════════════════════════
22
- # CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
23
- # ═══════════════════════════════════════════════════════════════════════════
24
- EKKOS_CONFIG_DIR="${EKKOS_CONFIG_DIR:-$HOME/.ekkos}"
25
- SESSION_WORDS_JSON="$EKKOS_CONFIG_DIR/session-words.json"
26
- SESSION_WORDS_DEFAULT="$EKKOS_CONFIG_DIR/.defaults/session-words.json"
27
- JSON_PARSE_HELPER="$EKKOS_CONFIG_DIR/.helpers/json-parse.cjs"
28
-
29
- # Parse metadata from hook environment using Node (no jq)
30
- parse_hook_env() {
31
- local json="$1"
32
- local path="$2"
33
- echo "$json" | node -e "
34
- const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
35
- const path = '$path'.replace(/^\./,'').split('.').filter(Boolean);
36
- let result = data;
37
- for (const p of path) {
38
- if (result === undefined || result === null) { result = undefined; break; }
39
- result = result[p];
40
- }
41
- if (result !== undefined && result !== null) console.log(result);
42
- " 2>/dev/null || echo ""
43
- }
44
-
45
- SESSION_ID=$(parse_hook_env "$HOOK_ENV" '.sessionId')
46
- [ -z "$SESSION_ID" ] && SESSION_ID="unknown"
47
-
48
- MODEL=$(parse_hook_env "$HOOK_ENV" '.model')
49
- [ -z "$MODEL" ] && MODEL="Claude Code (Opus 4.5)"
50
-
51
- # ═══════════════════════════════════════════════════════════════════════════
52
- # Session name conversion - Uses external session-words.json (NO hardcoded arrays)
53
- # ═══════════════════════════════════════════════════════════════════════════
54
- declare -a ADJECTIVES
55
- declare -a NOUNS
56
- declare -a VERBS
57
- SESSION_WORDS_LOADED=false
58
-
59
- load_session_words() {
60
- if [ "$SESSION_WORDS_LOADED" = "true" ]; then
61
- return 0
62
- fi
63
-
64
- local words_file="$SESSION_WORDS_JSON"
65
- if [ ! -f "$words_file" ]; then
66
- words_file="$SESSION_WORDS_DEFAULT"
67
- fi
68
-
69
- if [ ! -f "$words_file" ] || [ ! -f "$JSON_PARSE_HELPER" ]; then
70
- return 1
71
- fi
72
-
73
- if command -v node &>/dev/null; then
74
- if [ "${BASH_VERSINFO[0]}" -ge 4 ]; then
75
- readarray -t ADJECTIVES < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
76
- readarray -t NOUNS < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
77
- readarray -t VERBS < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
78
- else
79
- local i=0
80
- while IFS= read -r line; do
81
- ADJECTIVES[i]="$line"
82
- ((i++))
83
- done < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
84
- i=0
85
- while IFS= read -r line; do
86
- NOUNS[i]="$line"
87
- ((i++))
88
- done < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
89
- i=0
90
- while IFS= read -r line; do
91
- VERBS[i]="$line"
92
- ((i++))
93
- done < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
94
- fi
95
-
96
- if [ ${#ADJECTIVES[@]} -gt 0 ] && [ ${#NOUNS[@]} -gt 0 ] && [ ${#VERBS[@]} -gt 0 ]; then
97
- SESSION_WORDS_LOADED=true
98
- return 0
99
- fi
100
- fi
101
- return 1
102
- }
13
+ # Parse metadata from hook environment
14
+ SESSION_ID=$(echo "$HOOK_ENV" | jq -r '.sessionId // "unknown"')
15
+ TURN=$(echo "$HOOK_ENV" | jq -r '.turn // 0')
16
+ CONTEXT_PERCENT=$(echo "$HOOK_ENV" | jq -r '.contextUsagePercent // 0')
17
+ MODEL=$(echo "$HOOK_ENV" | jq -r '.model // "Claude Code (Opus 4.5)"')
103
18
 
19
+ # Convert session UUID to word-based name
104
20
  convert_uuid_to_name() {
105
- local uuid="$1"
106
-
107
- load_session_words || {
108
- echo "unknown-session"
109
- return
110
- }
111
-
112
- local hex="${uuid//-/}"
113
- hex="${hex:0:12}"
114
-
115
- if [[ ! "$hex" =~ ^[0-9a-fA-F]+$ ]]; then
116
- echo "unknown-session"
117
- return
118
- fi
119
-
120
- local adj_seed=$((16#${hex:0:4}))
121
- local noun_seed=$((16#${hex:4:4}))
122
- local verb_seed=$((16#${hex:8:4}))
123
- local adj_idx=$((adj_seed % ${#ADJECTIVES[@]}))
124
- local noun_idx=$((noun_seed % ${#NOUNS[@]}))
125
- local verb_idx=$((verb_seed % ${#VERBS[@]}))
126
-
127
- echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}-${VERBS[$verb_idx]}"
21
+ local uuid="$1"
22
+
23
+ # Word lists (same as MCP)
24
+ local ADJECTIVES=("cosmic" "turbo" "mega" "hyper" "quantum" "atomic" "stellar" "epic"
25
+ "mighty" "groovy" "zippy" "snappy" "jazzy" "funky" "zesty" "peppy"
26
+ "spicy" "crispy" "fluffy" "sparkly" "chunky" "bouncy" "bubbly" "sassy"
27
+ "slick" "sleek" "bold" "nifty" "perky" "plucky" "witty" "nimble"
28
+ "dapper" "fancy" "quirky" "punchy" "swift" "brave" "clever" "dandy"
29
+ "eager" "fiery" "golden" "hasty" "icy" "jolly" "keen" "lively"
30
+ "merry" "noble" "odd" "plush" "quick" "royal" "silly" "tidy"
31
+ "ultra" "vivid" "wacky" "zany" "alpha" "beta" "cyber" "delta"
32
+ "electric" "foggy" "giga" "hazy" "ionic" "jumpy" "kinky" "lunar"
33
+ "magic" "nerdy" "omega" "pixel" "quaint" "retro" "solar" "techno"
34
+ "unified" "viral" "wonky" "xerox" "yappy" "zen" "agile" "binary"
35
+ "chrome" "disco" "elastic" "fizzy" "glossy" "humble" "itchy" "jiffy"
36
+ "kooky" "loopy" "moody" "noisy")
37
+
38
+ local NOUNS=("penguin" "panda" "otter" "narwhal" "alpaca" "llama" "badger" "walrus"
39
+ "waffle" "pickle" "noodle" "pretzel" "muffin" "taco" "nugget" "biscuit"
40
+ "rocket" "comet" "nebula" "quasar" "meteor" "photon" "pulsar" "nova"
41
+ "ninja" "pirate" "wizard" "robot" "yeti" "phoenix" "sphinx" "kraken"
42
+ "thunder" "blizzard" "tornado" "avalanche" "mango" "kiwi" "banana" "coconut"
43
+ "donut" "espresso" "falafel" "gyro" "hummus" "icecream" "jambon" "kebab"
44
+ "latte" "mocha" "nachos" "olive" "pasta" "quinoa" "ramen" "sushi"
45
+ "tamale" "udon" "velvet" "wasabi" "xmas" "yogurt" "ziti" "anchor"
46
+ "beacon" "canyon" "drifter" "echo" "falcon" "glacier" "harbor" "island"
47
+ "jetpack" "kayak" "lagoon" "meadow" "orbit" "parrot" "quest"
48
+ "rapids" "summit" "tunnel" "umbrella" "volcano" "whisper" "xylophone" "yacht"
49
+ "zephyr" "acorn" "bobcat" "cactus" "dolphin" "eagle" "ferret" "gopher"
50
+ "hedgehog" "iguana" "jackal" "koala")
51
+
52
+ # Extract first 8 hex chars
53
+ local hex="${uuid:0:8}"
54
+ hex="${hex//-/}"
55
+
56
+ # Convert to number
57
+ local num=$((16#$hex))
58
+
59
+ # Calculate indices
60
+ local adj_idx=$((num % 100))
61
+ local noun_idx=$(((num / 100) % 100))
62
+
63
+ echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}"
128
64
  }
129
65
 
130
66
  SESSION_NAME=$(convert_uuid_to_name "$SESSION_ID")
131
- TIMESTAMP=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
67
+ TIMESTAMP=$(date "+%Y-%m-%d %I:%M %p %Z")
132
68
 
133
69
  # Required footer format
134
70
  REQUIRED_FOOTER="---
135
- $MODEL · 🧠 ekkOS_™ · $SESSION_NAME · 📅 $TIMESTAMP"
71
+ $MODEL · $SESSION_NAME · Turn $TURN · ${CONTEXT_PERCENT}% · 🧠 **ekkOS_™** · 📅 $TIMESTAMP"
136
72
 
137
73
  # Check if response has correct footer
138
74
  RESPONSE_CONTENT=$(cat "$RESPONSE_FILE")
139
75
  LAST_LINE=$(echo "$RESPONSE_CONTENT" | tail -1)
140
76
 
141
77
  # Check if footer exists and is correct
142
- if [[ "$LAST_LINE" == *"ekkOS"* ]] && [[ "$LAST_LINE" == *"$SESSION_NAME"* ]]; then
143
- # Footer exists - validate format
144
- if [[ "$LAST_LINE" == *"$SESSION_NAME"* ]] && [[ "$LAST_LINE" == *"📅"* ]]; then
145
- # Footer is correct
146
- exit 0
147
- else
148
- # Footer exists but is malformed - replace it
149
- RESPONSE_WITHOUT_FOOTER=$(echo "$RESPONSE_CONTENT" | head -n -2)
150
- echo "$RESPONSE_WITHOUT_FOOTER" > "$RESPONSE_FILE"
151
- echo "" >> "$RESPONSE_FILE"
152
- echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
153
- fi
154
- else
155
- # Footer missing - append it
78
+ if [[ "$LAST_LINE" == *"ekkOS"* ]] && [[ "$LAST_LINE" == *"Turn"* ]]; then
79
+ # Footer exists - validate format
80
+ if [[ "$LAST_LINE" == *"Turn $TURN"* ]] && [[ "$LAST_LINE" == *"${CONTEXT_PERCENT}%"* ]] && [[ "$LAST_LINE" == *"$SESSION_NAME"* ]]; then
81
+ # Footer is correct
82
+ exit 0
83
+ else
84
+ # Footer exists but is malformed - replace it
85
+ RESPONSE_WITHOUT_FOOTER=$(echo "$RESPONSE_CONTENT" | head -n -2) # Remove last 2 lines (--- and footer)
86
+ echo "$RESPONSE_WITHOUT_FOOTER" > "$RESPONSE_FILE"
156
87
  echo "" >> "$RESPONSE_FILE"
157
88
  echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
89
+ fi
90
+ else
91
+ # Footer missing - append it
92
+ echo "" >> "$RESPONSE_FILE"
93
+ echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
158
94
  fi
159
95
 
160
96
  exit 0
@@ -17,12 +17,6 @@
17
17
  "type": "command",
18
18
  "command": "bash .claude/hooks/stop.sh"
19
19
  }
20
- ],
21
- "AssistantResponse": [
22
- {
23
- "type": "command",
24
- "command": "bash .claude/hooks/assistant-response.sh"
25
- }
26
20
  ]
27
21
  }
28
22
  }
@@ -78,7 +78,7 @@ write_turn_contract() {
78
78
  "retrieved_directive_ids": $directive_array,
79
79
  "timestamp": "$timestamp",
80
80
  "query_hash": "$query_hash",
81
- "ekkos_strict": ${EKKOS_STRICT:-1}
81
+ "ekkos_strict": ${EKKOS_STRICT:-0}
82
82
  }
83
83
  EOF
84
84
 
@@ -134,7 +134,7 @@ cleanup_turn_contract() {
134
134
 
135
135
  # Check if strict mode is enabled
136
136
  is_strict_mode() {
137
- [ "${EKKOS_STRICT:-1}" = "1" ] # DEFAULT: ON
137
+ [ "${EKKOS_STRICT:-0}" = "1" ]
138
138
  }
139
139
 
140
140
  # Generate strict mode blocker message for Claude Code
@@ -157,57 +157,50 @@ EOF
157
157
  }
158
158
 
159
159
  # Validate PatternGuard coverage (returns 0-100)
160
- # PORTABLE: Works on macOS without Perl regex (grep -P)
161
160
  calculate_pattern_guard_coverage() {
162
161
  local assistant_response="$1"
163
162
  local pattern_ids="$2" # Comma-separated
164
163
 
165
- # Count total patterns - more robust counting
166
- local total_count=0
167
- if [ -n "$pattern_ids" ]; then
168
- total_count=$(echo "$pattern_ids" | tr ',' '\n' | grep -v '^$' | wc -l | tr -d ' ')
169
- fi
164
+ # Count total patterns
165
+ local total_count
166
+ total_count=$(echo "$pattern_ids" | tr ',' '\n' | grep -c '.' || echo 0)
170
167
 
171
168
  if [ "$total_count" -eq 0 ]; then
172
169
  echo "100" # No patterns = 100% coverage by definition
173
170
  return 0
174
171
  fi
175
172
 
176
- # Extract acknowledged IDs using portable awk (works on macOS and Linux)
173
+ # Extract acknowledged IDs from [ekkOS_SELECT] and [ekkOS_SKIP] blocks
177
174
  local acknowledged_count=0
178
175
 
179
- # Use awk to extract SELECT block and count IDs - PORTABLE approach
180
- local select_count=0
181
- select_count=$(echo "$assistant_response" | awk '
182
- /\[ekkOS_SELECT\]/{in_block=1; next}
183
- /\[\/ekkOS_SELECT\]/{in_block=0}
184
- in_block && /id:/{count++}
185
- END{print count+0}
186
- ')
187
- acknowledged_count=$((acknowledged_count + select_count))
188
-
189
- # Use awk to extract SKIP block and count IDs
190
- local skip_count=0
191
- skip_count=$(echo "$assistant_response" | awk '
192
- /\[ekkOS_SKIP\]/{in_block=1; next}
193
- /\[\/ekkOS_SKIP\]/{in_block=0}
194
- in_block && /id:/{count++}
195
- END{print count+0}
196
- ')
197
- acknowledged_count=$((acknowledged_count + skip_count))
176
+ # Check SELECT block
177
+ local select_block
178
+ select_block=$(echo "$assistant_response" | grep -ozP '\[ekkOS_SELECT\][\s\S]*?\[/ekkOS_SELECT\]' 2>/dev/null | tr '\0' '\n' || true)
179
+ if [ -n "$select_block" ]; then
180
+ local select_count
181
+ select_count=$(echo "$select_block" | grep -oE 'id:\s*[a-f0-9-]+' | wc -l | tr -d ' ')
182
+ acknowledged_count=$((acknowledged_count + select_count))
183
+ fi
184
+
185
+ # Check SKIP block
186
+ local skip_block
187
+ skip_block=$(echo "$assistant_response" | grep -ozP '\[ekkOS_SKIP\][\s\S]*?\[/ekkOS_SKIP\]' 2>/dev/null | tr '\0' '\n' || true)
188
+ if [ -n "$skip_block" ]; then
189
+ local skip_count
190
+ skip_count=$(echo "$skip_block" | grep -oE 'id:\s*[a-f0-9-]+' | wc -l | tr -d ' ')
191
+ acknowledged_count=$((acknowledged_count + skip_count))
192
+ fi
198
193
 
199
194
  # Legacy: Check for [ekkOS_APPLY] markers (fallback)
200
195
  if [ "$acknowledged_count" -eq 0 ]; then
201
196
  local apply_count
202
- apply_count=$(echo "$assistant_response" | grep -c '\[ekkOS_APPLY\]' 2>/dev/null || echo 0)
203
- acknowledged_count=$((acknowledged_count + apply_count))
197
+ apply_count=$(echo "$assistant_response" | grep -c '\[ekkOS_APPLY\]' || echo 0)
198
+ acknowledged_count=$apply_count
204
199
  fi
205
200
 
206
201
  # Calculate coverage percentage
207
- local coverage=0
208
- if [ "$total_count" -gt 0 ]; then
209
- coverage=$((acknowledged_count * 100 / total_count))
210
- fi
202
+ local coverage
203
+ coverage=$((acknowledged_count * 100 / total_count))
211
204
 
212
205
  # Cap at 100%
213
206
  if [ "$coverage" -gt 100 ]; then
@@ -262,16 +255,11 @@ EOF
262
255
  }
263
256
 
264
257
  # Determine if turn is compliant
265
- # ROBUST: Handles edge cases with non-numeric or empty values
266
258
  is_turn_compliant() {
267
- local retrieval_ok="${1:-true}"
268
- local pattern_guard_coverage="${2:-100}"
269
- local footer_present="${3:-true}"
270
- local pattern_count="${4:-0}"
271
-
272
- # Sanitize numeric values (default to safe values)
273
- pattern_guard_coverage=$(echo "$pattern_guard_coverage" | grep -oE '^[0-9]+$' || echo "100")
274
- pattern_count=$(echo "$pattern_count" | grep -oE '^[0-9]+$' || echo "0")
259
+ local retrieval_ok="$1"
260
+ local pattern_guard_coverage="$2"
261
+ local footer_present="$3"
262
+ local pattern_count="$4"
275
263
 
276
264
  # Retrieval must have succeeded
277
265
  if [ "$retrieval_ok" != "true" ]; then
File without changes
File without changes
@@ -20,40 +20,25 @@ mkdir -p "$STATE_DIR"
20
20
  # State File Management
21
21
  # ═══════════════════════════════════════════════════════════════════════════
22
22
 
23
- # Save patterns for session (with retrieval_token for verified tracking)
23
+ # Save patterns for session
24
24
  save_patterns() {
25
25
  local session_id="$1"
26
26
  local patterns="$2"
27
27
  local model_used="$3"
28
- local retrieval_token="${4:-}" # Optional retrieval_token for verified applications
29
28
 
30
29
  local state_file="$STATE_DIR/patterns-${session_id}.json"
31
30
 
32
31
  jq -n \
33
32
  --argjson patterns "$patterns" \
34
33
  --arg model "$model_used" \
35
- --arg token "$retrieval_token" \
36
34
  --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
37
35
  '{
38
36
  patterns: $patterns,
39
37
  model_used: $model,
40
- retrieval_token: $token,
41
38
  saved_at: $timestamp
42
39
  }' > "$state_file"
43
40
  }
44
41
 
45
- # Get retrieval token from saved patterns
46
- get_retrieval_token() {
47
- local session_id="$1"
48
- local state_file="$STATE_DIR/patterns-${session_id}.json"
49
-
50
- if [ -f "$state_file" ]; then
51
- jq -r '.retrieval_token // ""' "$state_file" 2>/dev/null
52
- else
53
- echo ""
54
- fi
55
- }
56
-
57
42
  # Load patterns for session
58
43
  load_patterns() {
59
44
  local session_id="$1"
@@ -72,43 +57,6 @@ clear_patterns() {
72
57
  rm -f "$STATE_DIR/patterns-${session_id}.json"
73
58
  }
74
59
 
75
- # ═══════════════════════════════════════════════════════════════════════════
76
- # Conversation Context (for Golden Loop MEASURE phase)
77
- # ═══════════════════════════════════════════════════════════════════════════
78
-
79
- # Save current conversation context for pattern tracking
80
- # Called by user-prompt-submit.sh when new query is submitted
81
- save_conversation_context() {
82
- local session_id="$1"
83
- local user_query="$2"
84
- local prev_response="$3"
85
-
86
- local context_file="$STATE_DIR/conversation-${session_id}.json"
87
-
88
- jq -n \
89
- --arg query "$user_query" \
90
- --arg response "$prev_response" \
91
- --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
92
- '{
93
- query: $query,
94
- response: $response,
95
- saved_at: $timestamp
96
- }' > "$context_file" 2>/dev/null || true
97
- }
98
-
99
- # Load current conversation context
100
- # Called by post-tool-use.sh when tracking pattern applications
101
- load_conversation_context() {
102
- local session_id="$1"
103
- local context_file="$STATE_DIR/conversation-${session_id}.json"
104
-
105
- if [ -f "$context_file" ]; then
106
- cat "$context_file"
107
- else
108
- echo '{"query":"","response":""}'
109
- fi
110
- }
111
-
112
60
  # ═══════════════════════════════════════════════════════════════════════════
113
61
  # Capture Deduplication
114
62
  # ═══════════════════════════════════════════════════════════════════════════