@ekkos/cli 1.0.35 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/commands/dashboard.js +561 -186
- package/dist/deploy/settings.js +13 -26
- package/package.json +2 -4
- package/templates/CLAUDE.md +135 -23
- package/templates/ekkos-manifest.json +8 -8
- package/templates/hooks/assistant-response.ps1 +256 -160
- package/templates/hooks/assistant-response.sh +130 -66
- package/templates/hooks/hooks.json +24 -6
- package/templates/hooks/lib/contract.sh +43 -31
- package/templates/hooks/lib/count-tokens.cjs +0 -0
- package/templates/hooks/lib/ekkos-reminders.sh +0 -0
- package/templates/hooks/lib/state.sh +53 -1
- package/templates/hooks/session-start.ps1 +91 -391
- package/templates/hooks/session-start.sh +201 -166
- package/templates/hooks/stop.ps1 +202 -341
- package/templates/hooks/stop.sh +275 -948
- package/templates/hooks/user-prompt-submit.ps1 +224 -548
- package/templates/hooks/user-prompt-submit.sh +382 -456
- package/templates/plan-template.md +0 -0
- package/templates/spec-template.md +0 -0
- package/templates/windsurf-hooks/hooks.json +9 -2
- package/templates/windsurf-hooks/install.sh +0 -0
- package/templates/windsurf-hooks/lib/contract.sh +2 -0
- package/templates/windsurf-hooks/post-cascade-response.sh +0 -0
- package/templates/windsurf-hooks/pre-user-prompt.sh +0 -0
- package/templates/agents/README.md +0 -182
- package/templates/agents/code-reviewer.md +0 -166
- package/templates/agents/debug-detective.md +0 -169
- package/templates/agents/ekkOS_Vercel.md +0 -99
- package/templates/agents/extension-manager.md +0 -229
- package/templates/agents/git-companion.md +0 -185
- package/templates/agents/github-test-agent.md +0 -321
- package/templates/agents/railway-manager.md +0 -179
- package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
- package/templates/windsurf-skills/ekkos-memory/SKILL.md +0 -219
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# ekkOS_ Hook: AssistantResponse - Validates and enforces footer format
|
|
4
|
+
# MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
|
|
5
|
+
# EKKOS_MANAGED=1
|
|
6
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
7
|
# Runs AFTER assistant response, checks footer compliance
|
|
8
|
+
# Per spec v1.2 Addendum: NO jq, NO hardcoded arrays
|
|
9
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
set +e
|
|
4
12
|
|
|
5
13
|
RESPONSE_FILE="$1"
|
|
6
14
|
HOOK_ENV="$2"
|
|
@@ -10,87 +18,143 @@ if [[ ! -f "$RESPONSE_FILE" ]]; then
|
|
|
10
18
|
exit 0
|
|
11
19
|
fi
|
|
12
20
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
}
|
|
18
103
|
|
|
19
|
-
# Convert session UUID to word-based name
|
|
20
104
|
convert_uuid_to_name() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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]}"
|
|
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]}"
|
|
64
128
|
}
|
|
65
129
|
|
|
66
130
|
SESSION_NAME=$(convert_uuid_to_name "$SESSION_ID")
|
|
67
|
-
TIMESTAMP=$(date "+%Y-%m-%d %I:%M %p %Z")
|
|
131
|
+
TIMESTAMP=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
|
|
68
132
|
|
|
69
133
|
# Required footer format
|
|
70
134
|
REQUIRED_FOOTER="---
|
|
71
|
-
$MODEL ·
|
|
135
|
+
$MODEL · 🧠 ekkOS_™ · $SESSION_NAME · 📅 $TIMESTAMP"
|
|
72
136
|
|
|
73
137
|
# Check if response has correct footer
|
|
74
138
|
RESPONSE_CONTENT=$(cat "$RESPONSE_FILE")
|
|
75
139
|
LAST_LINE=$(echo "$RESPONSE_CONTENT" | tail -1)
|
|
76
140
|
|
|
77
141
|
# Check if footer exists and is correct
|
|
78
|
-
if [[ "$LAST_LINE" == *"ekkOS"* ]] && [[ "$LAST_LINE" == *"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
87
156
|
echo "" >> "$RESPONSE_FILE"
|
|
88
157
|
echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
|
|
89
|
-
fi
|
|
90
|
-
else
|
|
91
|
-
# Footer missing - append it
|
|
92
|
-
echo "" >> "$RESPONSE_FILE"
|
|
93
|
-
echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
|
|
94
158
|
fi
|
|
95
159
|
|
|
96
160
|
exit 0
|
|
@@ -2,20 +2,38 @@
|
|
|
2
2
|
"hooks": {
|
|
3
3
|
"SessionStart": [
|
|
4
4
|
{
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"matcher": "*",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash .claude/hooks/session-start.sh",
|
|
10
|
+
"timeout": 5000
|
|
11
|
+
}
|
|
12
|
+
]
|
|
7
13
|
}
|
|
8
14
|
],
|
|
9
15
|
"UserPromptSubmit": [
|
|
10
16
|
{
|
|
11
|
-
"
|
|
12
|
-
"
|
|
17
|
+
"matcher": "*",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "bash .claude/hooks/user-prompt-submit.sh",
|
|
22
|
+
"timeout": 5000
|
|
23
|
+
}
|
|
24
|
+
]
|
|
13
25
|
}
|
|
14
26
|
],
|
|
15
27
|
"Stop": [
|
|
16
28
|
{
|
|
17
|
-
"
|
|
18
|
-
"
|
|
29
|
+
"matcher": "*",
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "bash .claude/hooks/stop.sh",
|
|
34
|
+
"timeout": 5000
|
|
35
|
+
}
|
|
36
|
+
]
|
|
19
37
|
}
|
|
20
38
|
]
|
|
21
39
|
}
|
|
@@ -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:-
|
|
81
|
+
"ekkos_strict": ${EKKOS_STRICT:-1}
|
|
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:-
|
|
137
|
+
[ "${EKKOS_STRICT:-1}" = "1" ] # DEFAULT: ON
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
# Generate strict mode blocker message for Claude Code
|
|
@@ -157,50 +157,57 @@ EOF
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
# Validate PatternGuard coverage (returns 0-100)
|
|
160
|
+
# PORTABLE: Works on macOS without Perl regex (grep -P)
|
|
160
161
|
calculate_pattern_guard_coverage() {
|
|
161
162
|
local assistant_response="$1"
|
|
162
163
|
local pattern_ids="$2" # Comma-separated
|
|
163
164
|
|
|
164
|
-
# Count total patterns
|
|
165
|
-
local total_count
|
|
166
|
-
|
|
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
|
|
167
170
|
|
|
168
171
|
if [ "$total_count" -eq 0 ]; then
|
|
169
172
|
echo "100" # No patterns = 100% coverage by definition
|
|
170
173
|
return 0
|
|
171
174
|
fi
|
|
172
175
|
|
|
173
|
-
# Extract acknowledged IDs
|
|
176
|
+
# Extract acknowledged IDs using portable awk (works on macOS and Linux)
|
|
174
177
|
local acknowledged_count=0
|
|
175
178
|
|
|
176
|
-
#
|
|
177
|
-
local
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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))
|
|
193
198
|
|
|
194
199
|
# Legacy: Check for [ekkOS_APPLY] markers (fallback)
|
|
195
200
|
if [ "$acknowledged_count" -eq 0 ]; then
|
|
196
201
|
local apply_count
|
|
197
|
-
apply_count=$(echo "$assistant_response" | grep -c '\[ekkOS_APPLY\]' || echo 0)
|
|
198
|
-
acknowledged_count=$apply_count
|
|
202
|
+
apply_count=$(echo "$assistant_response" | grep -c '\[ekkOS_APPLY\]' 2>/dev/null || echo 0)
|
|
203
|
+
acknowledged_count=$((acknowledged_count + apply_count))
|
|
199
204
|
fi
|
|
200
205
|
|
|
201
206
|
# Calculate coverage percentage
|
|
202
|
-
local coverage
|
|
203
|
-
|
|
207
|
+
local coverage=0
|
|
208
|
+
if [ "$total_count" -gt 0 ]; then
|
|
209
|
+
coverage=$((acknowledged_count * 100 / total_count))
|
|
210
|
+
fi
|
|
204
211
|
|
|
205
212
|
# Cap at 100%
|
|
206
213
|
if [ "$coverage" -gt 100 ]; then
|
|
@@ -255,11 +262,16 @@ EOF
|
|
|
255
262
|
}
|
|
256
263
|
|
|
257
264
|
# Determine if turn is compliant
|
|
265
|
+
# ROBUST: Handles edge cases with non-numeric or empty values
|
|
258
266
|
is_turn_compliant() {
|
|
259
|
-
local retrieval_ok="$1"
|
|
260
|
-
local pattern_guard_coverage="$2"
|
|
261
|
-
local footer_present="$3"
|
|
262
|
-
local pattern_count="$4"
|
|
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")
|
|
263
275
|
|
|
264
276
|
# Retrieval must have succeeded
|
|
265
277
|
if [ "$retrieval_ok" != "true" ]; then
|
|
File without changes
|
|
File without changes
|
|
@@ -20,25 +20,40 @@ mkdir -p "$STATE_DIR"
|
|
|
20
20
|
# State File Management
|
|
21
21
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
22
22
|
|
|
23
|
-
# Save patterns for session
|
|
23
|
+
# Save patterns for session (with retrieval_token for verified tracking)
|
|
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
|
|
28
29
|
|
|
29
30
|
local state_file="$STATE_DIR/patterns-${session_id}.json"
|
|
30
31
|
|
|
31
32
|
jq -n \
|
|
32
33
|
--argjson patterns "$patterns" \
|
|
33
34
|
--arg model "$model_used" \
|
|
35
|
+
--arg token "$retrieval_token" \
|
|
34
36
|
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
35
37
|
'{
|
|
36
38
|
patterns: $patterns,
|
|
37
39
|
model_used: $model,
|
|
40
|
+
retrieval_token: $token,
|
|
38
41
|
saved_at: $timestamp
|
|
39
42
|
}' > "$state_file"
|
|
40
43
|
}
|
|
41
44
|
|
|
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
|
+
|
|
42
57
|
# Load patterns for session
|
|
43
58
|
load_patterns() {
|
|
44
59
|
local session_id="$1"
|
|
@@ -57,6 +72,43 @@ clear_patterns() {
|
|
|
57
72
|
rm -f "$STATE_DIR/patterns-${session_id}.json"
|
|
58
73
|
}
|
|
59
74
|
|
|
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
|
+
|
|
60
112
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
61
113
|
# Capture Deduplication
|
|
62
114
|
# ═══════════════════════════════════════════════════════════════════════════
|