@ekkos/cli 0.2.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/dist/cache/LocalSessionStore.d.ts +129 -0
- package/dist/cache/LocalSessionStore.js +688 -0
- package/dist/cache/capture.d.ts +26 -0
- package/dist/cache/capture.js +461 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.js +23 -0
- package/dist/cache/types.d.ts +147 -0
- package/dist/cache/types.js +40 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +478 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +829 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.js +658 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +109 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +157 -0
- package/dist/deploy/agents.d.ts +15 -0
- package/dist/deploy/agents.js +72 -0
- package/dist/deploy/hooks.d.ts +16 -0
- package/dist/deploy/hooks.js +121 -0
- package/dist/deploy/index.d.ts +7 -0
- package/dist/deploy/index.js +24 -0
- package/dist/deploy/instructions.d.ts +12 -0
- package/dist/deploy/instructions.js +36 -0
- package/dist/deploy/mcp.d.ts +19 -0
- package/dist/deploy/mcp.js +109 -0
- package/dist/deploy/plugins.d.ts +19 -0
- package/dist/deploy/plugins.js +62 -0
- package/dist/deploy/settings.d.ts +8 -0
- package/dist/deploy/settings.js +84 -0
- package/dist/deploy/skills.d.ts +19 -0
- package/dist/deploy/skills.js +60 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +71 -0
- package/dist/restore/RestoreOrchestrator.d.ts +48 -0
- package/dist/restore/RestoreOrchestrator.js +481 -0
- package/dist/restore/index.d.ts +4 -0
- package/dist/restore/index.js +20 -0
- package/dist/utils/platform.d.ts +29 -0
- package/dist/utils/platform.js +65 -0
- package/dist/utils/session-words.json +119 -0
- package/dist/utils/state.d.ts +57 -0
- package/dist/utils/state.js +186 -0
- package/dist/utils/templates.d.ts +24 -0
- package/dist/utils/templates.js +118 -0
- package/package.json +48 -0
- package/templates/CLAUDE.md +287 -0
- package/templates/README.md +378 -0
- package/templates/agents/README.md +182 -0
- package/templates/agents/code-reviewer.md +166 -0
- package/templates/agents/debug-detective.md +169 -0
- package/templates/agents/ekkOS_Vercel.md +99 -0
- package/templates/agents/extension-manager.md +229 -0
- package/templates/agents/git-companion.md +185 -0
- package/templates/agents/github-test-agent.md +321 -0
- package/templates/agents/railway-manager.md +179 -0
- package/templates/claude-plugins/PHASE2_COMPLETION.md +346 -0
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +1776 -0
- package/templates/claude-plugins/README.md +587 -0
- package/templates/claude-plugins/agents/code-reviewer.json +14 -0
- package/templates/claude-plugins/agents/debug-detective.json +15 -0
- package/templates/claude-plugins/agents/git-companion.json +14 -0
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/blog-manager/commands/blog.md +691 -0
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +434 -0
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +282 -0
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +181 -0
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/pattern-coach/commands/forge.md +365 -0
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +582 -0
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +819 -0
- package/templates/claude-plugins-admin/README.md +446 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +595 -0
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +798 -0
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +554 -0
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +881 -0
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +85 -0
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +569 -0
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +863 -0
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +732 -0
- package/templates/commands/continue.md +47 -0
- package/templates/cursor-hooks/after-agent-response.sh +117 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +419 -0
- package/templates/cursor-hooks/hooks.json +20 -0
- package/templates/cursor-hooks/lib/contract.sh +320 -0
- package/templates/cursor-hooks/stop.sh +75 -0
- package/templates/cursor-rules/ekkos-memory.md +187 -0
- package/templates/hooks/assistant-response.sh +96 -0
- package/templates/hooks/hooks.json +28 -0
- package/templates/hooks/lib/contract.sh +320 -0
- package/templates/hooks/lib/state.sh +158 -0
- package/templates/hooks/session-start.ps1 +41 -0
- package/templates/hooks/session-start.sh +318 -0
- package/templates/hooks/stop.ps1 +16 -0
- package/templates/hooks/stop.sh +989 -0
- package/templates/hooks/user-prompt-submit.ps1 +174 -0
- package/templates/hooks/user-prompt-submit.sh +587 -0
- package/templates/hooks-node/lib/state.js +187 -0
- package/templates/hooks-node/stop.js +416 -0
- package/templates/hooks-node/user-prompt-submit.js +337 -0
- package/templates/plan-template.md +306 -0
- package/templates/rules/00-hooks-contract.mdc +89 -0
- package/templates/rules/30-ekkos-core.mdc +188 -0
- package/templates/rules/31-ekkos-messages.mdc +78 -0
- package/templates/skills/continue/SKILL.md +169 -0
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +282 -0
- package/templates/skills/ekkOS_Learn/Skill.md +265 -0
- package/templates/skills/ekkOS_Memory_First/Skill.md +206 -0
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +302 -0
- package/templates/skills/ekkOS_Preferences/Skill.md +247 -0
- package/templates/skills/ekkOS_Reflect/Skill.md +257 -0
- package/templates/skills/ekkOS_Safety/Skill.md +265 -0
- package/templates/skills/ekkOS_Schema/Skill.md +251 -0
- package/templates/skills/ekkOS_Summary/Skill.md +257 -0
- package/templates/skills/ekkOS_Vault/Skill.md +287 -0
- package/templates/skills/permissions/Skill.md +322 -0
- package/templates/spec-template.md +159 -0
- package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
- package/templates/windsurf-hooks/hooks.json +10 -0
- package/templates/windsurf-hooks/lib/contract.sh +320 -0
- package/templates/windsurf-rules/ekkos-memory.md +129 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"type": "command",
|
|
6
|
+
"command": "bash .claude/hooks/session-start.sh"
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"UserPromptSubmit": [
|
|
10
|
+
{
|
|
11
|
+
"type": "command",
|
|
12
|
+
"command": "bash .claude/hooks/user-prompt-submit.sh"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"Stop": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "bash .claude/hooks/stop.sh"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"AssistantResponse": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"command": "bash .claude/hooks/assistant-response.sh"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# ekkOS_ Turn Contract Library
|
|
4
|
+
#
|
|
5
|
+
# Shared functions for Golden Loop compliance enforcement.
|
|
6
|
+
# Used by BOTH Claude Code (.claude/) and Cursor (.cursor/) hooks.
|
|
7
|
+
#
|
|
8
|
+
# TURN CONTRACT: Evidence that retrieval occurred before answering.
|
|
9
|
+
# This is the SINGLE SOURCE OF TRUTH for compliance auditing.
|
|
10
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
# Get contract directory based on environment
|
|
13
|
+
get_contract_dir() {
|
|
14
|
+
local source="${1:-claude-code}"
|
|
15
|
+
local project_root="${2:-$PROJECT_ROOT}"
|
|
16
|
+
|
|
17
|
+
if [ "$source" = "cursor" ]; then
|
|
18
|
+
echo "$project_root/.cursor/state/ekkos"
|
|
19
|
+
else
|
|
20
|
+
echo "$project_root/.claude/state/ekkos"
|
|
21
|
+
fi
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Generate stable hash of user prompt (for deduplication)
|
|
25
|
+
generate_query_hash() {
|
|
26
|
+
local query="$1"
|
|
27
|
+
# Use md5 on macOS, md5sum on Linux
|
|
28
|
+
if command -v md5 >/dev/null 2>&1; then
|
|
29
|
+
echo -n "$query" | md5 | cut -c1-16
|
|
30
|
+
elif command -v md5sum >/dev/null 2>&1; then
|
|
31
|
+
echo -n "$query" | md5sum | cut -c1-16
|
|
32
|
+
else
|
|
33
|
+
# Fallback: simple hash using cksum
|
|
34
|
+
echo -n "$query" | cksum | cut -d' ' -f1
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Write turn contract at RETRIEVAL time
|
|
39
|
+
# This is the EVIDENCE that retrieval happened before answering
|
|
40
|
+
write_turn_contract() {
|
|
41
|
+
local session_id="$1"
|
|
42
|
+
local retrieval_ok="$2"
|
|
43
|
+
local retrieval_source="$3"
|
|
44
|
+
local pattern_ids="$4" # Comma-separated list
|
|
45
|
+
local directive_ids="$5" # Comma-separated list
|
|
46
|
+
local query_hash="$6"
|
|
47
|
+
local project_root="${7:-$PROJECT_ROOT}"
|
|
48
|
+
|
|
49
|
+
local contract_dir
|
|
50
|
+
contract_dir=$(get_contract_dir "$retrieval_source" "$project_root")
|
|
51
|
+
mkdir -p "$contract_dir" 2>/dev/null || return 1
|
|
52
|
+
|
|
53
|
+
local contract_file="$contract_dir/turn-contract-${session_id}.json"
|
|
54
|
+
local timestamp
|
|
55
|
+
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
56
|
+
|
|
57
|
+
# Convert comma-separated IDs to JSON array
|
|
58
|
+
local pattern_array
|
|
59
|
+
local directive_array
|
|
60
|
+
if [ -n "$pattern_ids" ]; then
|
|
61
|
+
pattern_array=$(echo "$pattern_ids" | tr ',' '\n' | grep -v '^$' | jq -R . | jq -s .)
|
|
62
|
+
else
|
|
63
|
+
pattern_array="[]"
|
|
64
|
+
fi
|
|
65
|
+
if [ -n "$directive_ids" ]; then
|
|
66
|
+
directive_array=$(echo "$directive_ids" | tr ',' '\n' | grep -v '^$' | jq -R . | jq -s .)
|
|
67
|
+
else
|
|
68
|
+
directive_array="[]"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Write contract
|
|
72
|
+
cat > "$contract_file" << EOF
|
|
73
|
+
{
|
|
74
|
+
"session_id": "$session_id",
|
|
75
|
+
"retrieval_ok": $retrieval_ok,
|
|
76
|
+
"retrieval_source": "$retrieval_source",
|
|
77
|
+
"retrieved_pattern_ids": $pattern_array,
|
|
78
|
+
"retrieved_directive_ids": $directive_array,
|
|
79
|
+
"timestamp": "$timestamp",
|
|
80
|
+
"query_hash": "$query_hash",
|
|
81
|
+
"ekkos_strict": ${EKKOS_STRICT:-0}
|
|
82
|
+
}
|
|
83
|
+
EOF
|
|
84
|
+
|
|
85
|
+
return 0
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Read turn contract
|
|
89
|
+
read_turn_contract() {
|
|
90
|
+
local session_id="$1"
|
|
91
|
+
local retrieval_source="$2"
|
|
92
|
+
local project_root="${3:-$PROJECT_ROOT}"
|
|
93
|
+
|
|
94
|
+
local contract_dir
|
|
95
|
+
contract_dir=$(get_contract_dir "$retrieval_source" "$project_root")
|
|
96
|
+
local contract_file="$contract_dir/turn-contract-${session_id}.json"
|
|
97
|
+
|
|
98
|
+
if [ -f "$contract_file" ]; then
|
|
99
|
+
cat "$contract_file"
|
|
100
|
+
return 0
|
|
101
|
+
else
|
|
102
|
+
return 1
|
|
103
|
+
fi
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Extract field from contract JSON
|
|
107
|
+
get_contract_field() {
|
|
108
|
+
local contract_json="$1"
|
|
109
|
+
local field="$2"
|
|
110
|
+
|
|
111
|
+
echo "$contract_json" | jq -r ".$field // \"\"" 2>/dev/null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Get array field as comma-separated string
|
|
115
|
+
get_contract_array() {
|
|
116
|
+
local contract_json="$1"
|
|
117
|
+
local field="$2"
|
|
118
|
+
|
|
119
|
+
echo "$contract_json" | jq -r ".$field // [] | join(\",\")" 2>/dev/null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Cleanup turn contract (only after successful capture)
|
|
123
|
+
cleanup_turn_contract() {
|
|
124
|
+
local session_id="$1"
|
|
125
|
+
local retrieval_source="$2"
|
|
126
|
+
local project_root="${3:-$PROJECT_ROOT}"
|
|
127
|
+
|
|
128
|
+
local contract_dir
|
|
129
|
+
contract_dir=$(get_contract_dir "$retrieval_source" "$project_root")
|
|
130
|
+
local contract_file="$contract_dir/turn-contract-${session_id}.json"
|
|
131
|
+
|
|
132
|
+
rm -f "$contract_file" 2>/dev/null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Check if strict mode is enabled
|
|
136
|
+
is_strict_mode() {
|
|
137
|
+
[ "${EKKOS_STRICT:-0}" = "1" ]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Generate strict mode blocker message for Claude Code
|
|
141
|
+
get_strict_blocker_message() {
|
|
142
|
+
cat << 'EOF'
|
|
143
|
+
════════════════════════════════════════════════════════════════════════════
|
|
144
|
+
⛔ EKKOS_STRICT MODE: RETRIEVAL FAILED — DO NOT ANSWER
|
|
145
|
+
|
|
146
|
+
ekkOS memory retrieval failed or was not authenticated.
|
|
147
|
+
In STRICT mode, you MUST NOT answer technical questions without memory context.
|
|
148
|
+
|
|
149
|
+
ACTION REQUIRED:
|
|
150
|
+
1. Check if ekkOS is connected: Run `ekkOS: Connect` in VS Code
|
|
151
|
+
2. Verify auth token exists: ~/.ekkos/config.json
|
|
152
|
+
3. Retry the request after connecting
|
|
153
|
+
|
|
154
|
+
DO NOT proceed with answering. The Golden Loop requires retrieval FIRST.
|
|
155
|
+
════════════════════════════════════════════════════════════════════════════
|
|
156
|
+
EOF
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# Validate PatternGuard coverage (returns 0-100)
|
|
160
|
+
calculate_pattern_guard_coverage() {
|
|
161
|
+
local assistant_response="$1"
|
|
162
|
+
local pattern_ids="$2" # Comma-separated
|
|
163
|
+
|
|
164
|
+
# Count total patterns
|
|
165
|
+
local total_count
|
|
166
|
+
total_count=$(echo "$pattern_ids" | tr ',' '\n' | grep -c '.' || echo 0)
|
|
167
|
+
|
|
168
|
+
if [ "$total_count" -eq 0 ]; then
|
|
169
|
+
echo "100" # No patterns = 100% coverage by definition
|
|
170
|
+
return 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Extract acknowledged IDs from [ekkOS_SELECT] and [ekkOS_SKIP] blocks
|
|
174
|
+
local acknowledged_count=0
|
|
175
|
+
|
|
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
|
|
193
|
+
|
|
194
|
+
# Legacy: Check for [ekkOS_APPLY] markers (fallback)
|
|
195
|
+
if [ "$acknowledged_count" -eq 0 ]; then
|
|
196
|
+
local apply_count
|
|
197
|
+
apply_count=$(echo "$assistant_response" | grep -c '\[ekkOS_APPLY\]' || echo 0)
|
|
198
|
+
acknowledged_count=$apply_count
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# Calculate coverage percentage
|
|
202
|
+
local coverage
|
|
203
|
+
coverage=$((acknowledged_count * 100 / total_count))
|
|
204
|
+
|
|
205
|
+
# Cap at 100%
|
|
206
|
+
if [ "$coverage" -gt 100 ]; then
|
|
207
|
+
coverage=100
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
echo "$coverage"
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Check for ekkOS footer presence
|
|
214
|
+
check_footer_present() {
|
|
215
|
+
local assistant_response="$1"
|
|
216
|
+
|
|
217
|
+
# Look for the mandatory footer format:
|
|
218
|
+
# 🧠 **ekkOS_™** · 📅 YYYY-MM-DD
|
|
219
|
+
# OR
|
|
220
|
+
# {IDE} ({Model}) · 🧠 **ekkOS_™** · 📅 {Timestamp}
|
|
221
|
+
|
|
222
|
+
if echo "$assistant_response" | grep -qE '🧠.*ekkOS.*📅.*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
|
|
223
|
+
echo "true"
|
|
224
|
+
return 0
|
|
225
|
+
else
|
|
226
|
+
echo "false"
|
|
227
|
+
return 1
|
|
228
|
+
fi
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Build compliance metadata for capture
|
|
232
|
+
build_compliance_metadata() {
|
|
233
|
+
local retrieval_ok="$1"
|
|
234
|
+
local pattern_guard_coverage="$2"
|
|
235
|
+
local footer_present="$3"
|
|
236
|
+
local ekkos_strict="$4"
|
|
237
|
+
local retrieved_count="$5"
|
|
238
|
+
|
|
239
|
+
local pattern_guard_required="false"
|
|
240
|
+
if [ "$retrieved_count" -gt 0 ]; then
|
|
241
|
+
pattern_guard_required="true"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
cat << EOF
|
|
245
|
+
{
|
|
246
|
+
"retrieval_ok": $retrieval_ok,
|
|
247
|
+
"pattern_guard_required": $pattern_guard_required,
|
|
248
|
+
"pattern_guard_coverage_pct": $pattern_guard_coverage,
|
|
249
|
+
"footer_present": $footer_present,
|
|
250
|
+
"ekkos_strict": $ekkos_strict,
|
|
251
|
+
"retrieved_count": $retrieved_count,
|
|
252
|
+
"compliance_version": "1.0"
|
|
253
|
+
}
|
|
254
|
+
EOF
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Determine if turn is compliant
|
|
258
|
+
is_turn_compliant() {
|
|
259
|
+
local retrieval_ok="$1"
|
|
260
|
+
local pattern_guard_coverage="$2"
|
|
261
|
+
local footer_present="$3"
|
|
262
|
+
local pattern_count="$4"
|
|
263
|
+
|
|
264
|
+
# Retrieval must have succeeded
|
|
265
|
+
if [ "$retrieval_ok" != "true" ]; then
|
|
266
|
+
echo "false"
|
|
267
|
+
return 1
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
# If patterns were retrieved, PatternGuard must be 100%
|
|
271
|
+
if [ "$pattern_count" -gt 0 ] && [ "$pattern_guard_coverage" -lt 100 ]; then
|
|
272
|
+
echo "false"
|
|
273
|
+
return 1
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# Footer must be present
|
|
277
|
+
if [ "$footer_present" != "true" ]; then
|
|
278
|
+
echo "false"
|
|
279
|
+
return 1
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
echo "true"
|
|
283
|
+
return 0
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# Generate violation reason
|
|
287
|
+
get_violation_reason() {
|
|
288
|
+
local retrieval_ok="$1"
|
|
289
|
+
local pattern_guard_coverage="$2"
|
|
290
|
+
local footer_present="$3"
|
|
291
|
+
local pattern_count="$4"
|
|
292
|
+
|
|
293
|
+
local reasons=""
|
|
294
|
+
|
|
295
|
+
if [ "$retrieval_ok" != "true" ]; then
|
|
296
|
+
reasons="retrieval_failed"
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
if [ "$pattern_count" -gt 0 ] && [ "$pattern_guard_coverage" -lt 100 ]; then
|
|
300
|
+
if [ -n "$reasons" ]; then
|
|
301
|
+
reasons="$reasons,pattern_guard_incomplete"
|
|
302
|
+
else
|
|
303
|
+
reasons="pattern_guard_incomplete"
|
|
304
|
+
fi
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
if [ "$footer_present" != "true" ]; then
|
|
308
|
+
if [ -n "$reasons" ]; then
|
|
309
|
+
reasons="$reasons,footer_missing"
|
|
310
|
+
else
|
|
311
|
+
reasons="footer_missing"
|
|
312
|
+
fi
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
if [ -z "$reasons" ]; then
|
|
316
|
+
reasons="none"
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
echo "$reasons"
|
|
320
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# ekkOS™ Hook State Management Library (Portable)
|
|
4
|
+
# Shared state functions for coordinating between hooks
|
|
5
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
# Get project root (where .claude directory lives)
|
|
8
|
+
get_project_root() {
|
|
9
|
+
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
echo "$(dirname "$(dirname "$script_dir")")"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
PROJECT_ROOT=$(get_project_root)
|
|
14
|
+
STATE_DIR="$PROJECT_ROOT/.claude/state"
|
|
15
|
+
|
|
16
|
+
# Ensure state directory exists
|
|
17
|
+
mkdir -p "$STATE_DIR"
|
|
18
|
+
|
|
19
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
# State File Management
|
|
21
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
# Save patterns for session
|
|
24
|
+
save_patterns() {
|
|
25
|
+
local session_id="$1"
|
|
26
|
+
local patterns="$2"
|
|
27
|
+
local model_used="$3"
|
|
28
|
+
|
|
29
|
+
local state_file="$STATE_DIR/patterns-${session_id}.json"
|
|
30
|
+
|
|
31
|
+
jq -n \
|
|
32
|
+
--argjson patterns "$patterns" \
|
|
33
|
+
--arg model "$model_used" \
|
|
34
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
35
|
+
'{
|
|
36
|
+
patterns: $patterns,
|
|
37
|
+
model_used: $model,
|
|
38
|
+
saved_at: $timestamp
|
|
39
|
+
}' > "$state_file"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Load patterns for session
|
|
43
|
+
load_patterns() {
|
|
44
|
+
local session_id="$1"
|
|
45
|
+
local state_file="$STATE_DIR/patterns-${session_id}.json"
|
|
46
|
+
|
|
47
|
+
if [ -f "$state_file" ]; then
|
|
48
|
+
cat "$state_file"
|
|
49
|
+
else
|
|
50
|
+
echo '{}'
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Clear patterns for session (after capture)
|
|
55
|
+
clear_patterns() {
|
|
56
|
+
local session_id="$1"
|
|
57
|
+
rm -f "$STATE_DIR/patterns-${session_id}.json"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
61
|
+
# Capture Deduplication
|
|
62
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
63
|
+
|
|
64
|
+
# Generate hash for conversation turn
|
|
65
|
+
generate_turn_hash() {
|
|
66
|
+
local user_query="$1"
|
|
67
|
+
local assistant_response="$2"
|
|
68
|
+
|
|
69
|
+
echo "${user_query}${assistant_response}" | md5sum | cut -d' ' -f1 2>/dev/null || \
|
|
70
|
+
echo "${user_query}${assistant_response}" | md5 2>/dev/null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Check if turn was already captured
|
|
74
|
+
was_turn_captured() {
|
|
75
|
+
local session_id="$1"
|
|
76
|
+
local turn_hash="$2"
|
|
77
|
+
|
|
78
|
+
local capture_log="$STATE_DIR/captures-${session_id}.log"
|
|
79
|
+
|
|
80
|
+
if [ -f "$capture_log" ]; then
|
|
81
|
+
grep -q "^${turn_hash}$" "$capture_log"
|
|
82
|
+
return $?
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
return 1 # Not captured
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Mark turn as captured
|
|
89
|
+
mark_turn_captured() {
|
|
90
|
+
local session_id="$1"
|
|
91
|
+
local turn_hash="$2"
|
|
92
|
+
|
|
93
|
+
local capture_log="$STATE_DIR/captures-${session_id}.log"
|
|
94
|
+
|
|
95
|
+
echo "$turn_hash" >> "$capture_log"
|
|
96
|
+
|
|
97
|
+
# Keep only last 100 hashes
|
|
98
|
+
if [ $(wc -l < "$capture_log" 2>/dev/null || echo 0) -gt 100 ]; then
|
|
99
|
+
tail -100 "$capture_log" > "${capture_log}.tmp"
|
|
100
|
+
mv "${capture_log}.tmp" "$capture_log"
|
|
101
|
+
fi
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
105
|
+
# Hook Coordination
|
|
106
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
107
|
+
|
|
108
|
+
# Lock for preventing race conditions
|
|
109
|
+
acquire_lock() {
|
|
110
|
+
local session_id="$1"
|
|
111
|
+
local lock_file="$STATE_DIR/lock-${session_id}.lock"
|
|
112
|
+
local timeout=5
|
|
113
|
+
local waited=0
|
|
114
|
+
|
|
115
|
+
while [ -f "$lock_file" ] && [ $waited -lt $timeout ]; do
|
|
116
|
+
sleep 0.1
|
|
117
|
+
waited=$((waited + 1))
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
if [ -f "$lock_file" ]; then
|
|
121
|
+
# Stale lock (older than 10 seconds)
|
|
122
|
+
local lock_age=$(($(date +%s) - $(stat -f %m "$lock_file" 2>/dev/null || stat -c %Y "$lock_file" 2>/dev/null || echo 0)))
|
|
123
|
+
if [ $lock_age -gt 10 ]; then
|
|
124
|
+
rm -f "$lock_file"
|
|
125
|
+
else
|
|
126
|
+
return 1 # Failed to acquire
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Acquire lock
|
|
131
|
+
echo $$ > "$lock_file"
|
|
132
|
+
return 0
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Release lock
|
|
136
|
+
release_lock() {
|
|
137
|
+
local session_id="$1"
|
|
138
|
+
local lock_file="$STATE_DIR/lock-${session_id}.lock"
|
|
139
|
+
|
|
140
|
+
if [ -f "$lock_file" ] && [ "$(cat "$lock_file")" = "$$" ]; then
|
|
141
|
+
rm -f "$lock_file"
|
|
142
|
+
fi
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
146
|
+
# Cleanup
|
|
147
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
148
|
+
|
|
149
|
+
# Clean up old state files
|
|
150
|
+
cleanup_old_state() {
|
|
151
|
+
# Remove files older than 24 hours
|
|
152
|
+
find "$STATE_DIR" -name "*.json" -mtime +1 -delete 2>/dev/null || true
|
|
153
|
+
find "$STATE_DIR" -name "*.log" -mtime +1 -delete 2>/dev/null || true
|
|
154
|
+
find "$STATE_DIR" -name "*.lock" -mtime +1 -delete 2>/dev/null || true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Cleanup on exit
|
|
158
|
+
trap cleanup_old_state EXIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
# ekkOS_ Hook: SessionStart - Initialize session (Windows)
|
|
3
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
$ErrorActionPreference = "SilentlyContinue"
|
|
6
|
+
|
|
7
|
+
# Read input
|
|
8
|
+
$inputJson = [Console]::In.ReadToEnd()
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
$input = $inputJson | ConvertFrom-Json
|
|
12
|
+
$sessionId = $input.session_id
|
|
13
|
+
} catch {
|
|
14
|
+
$sessionId = "unknown"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Initialize state directory
|
|
18
|
+
$stateDir = Join-Path $env:USERPROFILE ".claude\state"
|
|
19
|
+
if (-not (Test-Path $stateDir)) {
|
|
20
|
+
New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Reset turn counter for new session
|
|
24
|
+
$stateFile = Join-Path $stateDir "hook-state.json"
|
|
25
|
+
$state = @{
|
|
26
|
+
turn = 0
|
|
27
|
+
session_id = $sessionId
|
|
28
|
+
started_at = (Get-Date).ToString("o")
|
|
29
|
+
} | ConvertTo-Json
|
|
30
|
+
|
|
31
|
+
Set-Content -Path $stateFile -Value $state -Force
|
|
32
|
+
|
|
33
|
+
# Save session ID for other hooks
|
|
34
|
+
$sessionFile = Join-Path $stateDir "current-session.json"
|
|
35
|
+
$sessionData = @{
|
|
36
|
+
session_id = $sessionId
|
|
37
|
+
} | ConvertTo-Json
|
|
38
|
+
|
|
39
|
+
Set-Content -Path $sessionFile -Value $sessionData -Force
|
|
40
|
+
|
|
41
|
+
Write-Output "ekkOS session initialized"
|