gitmem-mcp 0.2.0 → 1.0.1
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/CHANGELOG.md +17 -1
- package/CLAUDE.md.template +63 -55
- package/README.md +149 -120
- package/bin/gitmem.js +377 -25
- package/bin/init-wizard.js +642 -0
- package/bin/uninstall.js +288 -0
- package/dist/commands/check.js +20 -20
- package/dist/commands/check.js.map +1 -1
- package/dist/constants/closing-questions.d.ts +6 -0
- package/dist/constants/closing-questions.d.ts.map +1 -1
- package/dist/constants/closing-questions.js +65 -0
- package/dist/constants/closing-questions.js.map +1 -1
- package/dist/hooks/format-utils.d.ts +52 -0
- package/dist/hooks/format-utils.d.ts.map +1 -0
- package/dist/hooks/format-utils.js +89 -0
- package/dist/hooks/format-utils.js.map +1 -0
- package/dist/hooks/quick-retrieve.d.ts +30 -0
- package/dist/hooks/quick-retrieve.d.ts.map +1 -0
- package/dist/hooks/quick-retrieve.js +149 -0
- package/dist/hooks/quick-retrieve.js.map +1 -0
- package/dist/index.js +0 -0
- package/dist/schemas/active-sessions.d.ts +8 -8
- package/dist/schemas/analyze.d.ts +3 -3
- package/dist/schemas/common.d.ts +2 -2
- package/dist/schemas/common.d.ts.map +1 -1
- package/dist/schemas/common.js +1 -1
- package/dist/schemas/common.js.map +1 -1
- package/dist/schemas/create-decision.d.ts +3 -3
- package/dist/schemas/create-learning.d.ts +13 -13
- package/dist/schemas/log.d.ts +3 -3
- package/dist/schemas/prepare-context.d.ts +3 -3
- package/dist/schemas/recall.d.ts +3 -3
- package/dist/schemas/record-scar-usage-batch.d.ts +8 -3
- package/dist/schemas/record-scar-usage-batch.d.ts.map +1 -1
- package/dist/schemas/record-scar-usage.d.ts +3 -0
- package/dist/schemas/record-scar-usage.d.ts.map +1 -1
- package/dist/schemas/record-scar-usage.js +1 -0
- package/dist/schemas/record-scar-usage.js.map +1 -1
- package/dist/schemas/registry.d.ts +18 -0
- package/dist/schemas/registry.d.ts.map +1 -0
- package/dist/schemas/registry.js +158 -0
- package/dist/schemas/registry.js.map +1 -0
- package/dist/schemas/save-transcript.d.ts +3 -3
- package/dist/schemas/search-transcripts.d.ts +33 -0
- package/dist/schemas/search-transcripts.d.ts.map +1 -0
- package/dist/schemas/search-transcripts.js +26 -0
- package/dist/schemas/search-transcripts.js.map +1 -0
- package/dist/schemas/search.d.ts +3 -3
- package/dist/schemas/session-close.d.ts +43 -15
- package/dist/schemas/session-close.d.ts.map +1 -1
- package/dist/schemas/session-close.js +7 -2
- package/dist/schemas/session-close.js.map +1 -1
- package/dist/schemas/session-start.d.ts +3 -3
- package/dist/schemas/thread.d.ts +3 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +82 -28
- package/dist/server.js.map +1 -1
- package/dist/services/active-sessions.d.ts +2 -1
- package/dist/services/active-sessions.d.ts.map +1 -1
- package/dist/services/active-sessions.js +130 -84
- package/dist/services/active-sessions.js.map +1 -1
- package/dist/services/analytics.d.ts.map +1 -1
- package/dist/services/analytics.js +1 -0
- package/dist/services/analytics.js.map +1 -1
- package/dist/services/behavioral-decay.d.ts +40 -0
- package/dist/services/behavioral-decay.d.ts.map +1 -0
- package/dist/services/behavioral-decay.js +110 -0
- package/dist/services/behavioral-decay.js.map +1 -0
- package/dist/services/bm25.d.ts +39 -0
- package/dist/services/bm25.d.ts.map +1 -0
- package/dist/services/bm25.js +132 -0
- package/dist/services/bm25.js.map +1 -0
- package/dist/services/cache.d.ts.map +1 -1
- package/dist/services/cache.js +9 -8
- package/dist/services/cache.js.map +1 -1
- package/dist/services/cache.test.js +17 -17
- package/dist/services/cache.test.js.map +1 -1
- package/dist/services/compliance-validator.d.ts.map +1 -1
- package/dist/services/compliance-validator.js +12 -1
- package/dist/services/compliance-validator.js.map +1 -1
- package/dist/services/display-protocol.d.ts +31 -0
- package/dist/services/display-protocol.d.ts.map +1 -0
- package/dist/services/display-protocol.js +73 -0
- package/dist/services/display-protocol.js.map +1 -0
- package/dist/services/effect-tracker.d.ts +81 -0
- package/dist/services/effect-tracker.d.ts.map +1 -0
- package/dist/services/effect-tracker.js +181 -0
- package/dist/services/effect-tracker.js.map +1 -0
- package/dist/services/file-lock.d.ts +31 -0
- package/dist/services/file-lock.d.ts.map +1 -0
- package/dist/services/file-lock.js +124 -0
- package/dist/services/file-lock.js.map +1 -0
- package/dist/services/gitmem-dir.d.ts +7 -0
- package/dist/services/gitmem-dir.d.ts.map +1 -1
- package/dist/services/gitmem-dir.js +21 -0
- package/dist/services/gitmem-dir.js.map +1 -1
- package/dist/services/local-file-storage.d.ts +3 -2
- package/dist/services/local-file-storage.d.ts.map +1 -1
- package/dist/services/local-file-storage.js +30 -43
- package/dist/services/local-file-storage.js.map +1 -1
- package/dist/services/local-vector-search.d.ts +10 -9
- package/dist/services/local-vector-search.d.ts.map +1 -1
- package/dist/services/local-vector-search.js +28 -23
- package/dist/services/local-vector-search.js.map +1 -1
- package/dist/services/metrics.d.ts +7 -2
- package/dist/services/metrics.d.ts.map +1 -1
- package/dist/services/metrics.js +41 -33
- package/dist/services/metrics.js.map +1 -1
- package/dist/services/session-state.d.ts +8 -0
- package/dist/services/session-state.d.ts.map +1 -1
- package/dist/services/session-state.js +9 -2
- package/dist/services/session-state.js.map +1 -1
- package/dist/services/startup.d.ts +12 -13
- package/dist/services/startup.d.ts.map +1 -1
- package/dist/services/startup.js +104 -57
- package/dist/services/startup.js.map +1 -1
- package/dist/services/supabase-client.d.ts +2 -1
- package/dist/services/supabase-client.d.ts.map +1 -1
- package/dist/services/supabase-client.js +22 -16
- package/dist/services/supabase-client.js.map +1 -1
- package/dist/services/thread-dedup.d.ts +9 -0
- package/dist/services/thread-dedup.d.ts.map +1 -1
- package/dist/services/thread-dedup.js +27 -0
- package/dist/services/thread-dedup.js.map +1 -1
- package/dist/services/thread-manager.d.ts.map +1 -1
- package/dist/services/thread-manager.js +38 -16
- package/dist/services/thread-manager.js.map +1 -1
- package/dist/services/thread-suggestions.d.ts.map +1 -1
- package/dist/services/thread-suggestions.js +1 -1
- package/dist/services/thread-suggestions.js.map +1 -1
- package/dist/services/thread-supabase.d.ts +0 -1
- package/dist/services/thread-supabase.d.ts.map +1 -1
- package/dist/services/thread-supabase.js +83 -54
- package/dist/services/thread-supabase.js.map +1 -1
- package/dist/services/timezone.d.ts.map +1 -1
- package/dist/services/timezone.js +1 -0
- package/dist/services/timezone.js.map +1 -1
- package/dist/services/transcript-chunker.d.ts.map +1 -1
- package/dist/services/transcript-chunker.js +18 -4
- package/dist/services/transcript-chunker.js.map +1 -1
- package/dist/services/variant-generation.d.ts +41 -0
- package/dist/services/variant-generation.d.ts.map +1 -0
- package/dist/services/variant-generation.js +263 -0
- package/dist/services/variant-generation.js.map +1 -0
- package/dist/tools/absorb-observations.d.ts.map +1 -1
- package/dist/tools/absorb-observations.js +9 -0
- package/dist/tools/absorb-observations.js.map +1 -1
- package/dist/tools/analyze.d.ts.map +1 -1
- package/dist/tools/analyze.js +13 -2
- package/dist/tools/analyze.js.map +1 -1
- package/dist/tools/archive-learning.d.ts +28 -0
- package/dist/tools/archive-learning.d.ts.map +1 -0
- package/dist/tools/archive-learning.js +81 -0
- package/dist/tools/archive-learning.js.map +1 -0
- package/dist/tools/cleanup-threads.d.ts +1 -0
- package/dist/tools/cleanup-threads.d.ts.map +1 -1
- package/dist/tools/cleanup-threads.js +111 -18
- package/dist/tools/cleanup-threads.js.map +1 -1
- package/dist/tools/confirm-scars.d.ts.map +1 -1
- package/dist/tools/confirm-scars.js +8 -2
- package/dist/tools/confirm-scars.js.map +1 -1
- package/dist/tools/create-decision.d.ts.map +1 -1
- package/dist/tools/create-decision.js +11 -8
- package/dist/tools/create-decision.js.map +1 -1
- package/dist/tools/create-learning.d.ts.map +1 -1
- package/dist/tools/create-learning.js +35 -11
- package/dist/tools/create-learning.js.map +1 -1
- package/dist/tools/create-linear-issue.d.ts +18 -0
- package/dist/tools/create-linear-issue.d.ts.map +1 -0
- package/dist/tools/create-linear-issue.js +197 -0
- package/dist/tools/create-linear-issue.js.map +1 -0
- package/dist/tools/create-thread.d.ts +2 -1
- package/dist/tools/create-thread.d.ts.map +1 -1
- package/dist/tools/create-thread.js +9 -4
- package/dist/tools/create-thread.js.map +1 -1
- package/dist/tools/definitions.d.ts +785 -34
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +239 -95
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/dismiss-suggestion.d.ts +1 -0
- package/dist/tools/dismiss-suggestion.d.ts.map +1 -1
- package/dist/tools/dismiss-suggestion.js +4 -0
- package/dist/tools/dismiss-suggestion.js.map +1 -1
- package/dist/tools/graph-traverse.d.ts +1 -0
- package/dist/tools/graph-traverse.d.ts.map +1 -1
- package/dist/tools/graph-traverse.js +24 -9
- package/dist/tools/graph-traverse.js.map +1 -1
- package/dist/tools/list-threads.d.ts.map +1 -1
- package/dist/tools/list-threads.js +49 -5
- package/dist/tools/list-threads.js.map +1 -1
- package/dist/tools/log.d.ts +1 -0
- package/dist/tools/log.d.ts.map +1 -1
- package/dist/tools/log.js +84 -17
- package/dist/tools/log.js.map +1 -1
- package/dist/tools/prepare-context.d.ts +1 -0
- package/dist/tools/prepare-context.d.ts.map +1 -1
- package/dist/tools/prepare-context.js +15 -85
- package/dist/tools/prepare-context.js.map +1 -1
- package/dist/tools/promote-suggestion.d.ts +1 -0
- package/dist/tools/promote-suggestion.d.ts.map +1 -1
- package/dist/tools/promote-suggestion.js +5 -0
- package/dist/tools/promote-suggestion.js.map +1 -1
- package/dist/tools/recall.d.ts +2 -0
- package/dist/tools/recall.d.ts.map +1 -1
- package/dist/tools/recall.js +43 -10
- package/dist/tools/recall.js.map +1 -1
- package/dist/tools/recall.test.js +6 -6
- package/dist/tools/recall.test.js.map +1 -1
- package/dist/tools/record-scar-usage-batch.d.ts.map +1 -1
- package/dist/tools/record-scar-usage-batch.js +13 -0
- package/dist/tools/record-scar-usage-batch.js.map +1 -1
- package/dist/tools/record-scar-usage.d.ts.map +1 -1
- package/dist/tools/record-scar-usage.js +6 -0
- package/dist/tools/record-scar-usage.js.map +1 -1
- package/dist/tools/resolve-thread.d.ts.map +1 -1
- package/dist/tools/resolve-thread.js +57 -6
- package/dist/tools/resolve-thread.js.map +1 -1
- package/dist/tools/save-transcript.d.ts +1 -0
- package/dist/tools/save-transcript.d.ts.map +1 -1
- package/dist/tools/save-transcript.js +3 -1
- package/dist/tools/save-transcript.js.map +1 -1
- package/dist/tools/search-transcripts.d.ts +44 -0
- package/dist/tools/search-transcripts.d.ts.map +1 -0
- package/dist/tools/search-transcripts.js +158 -0
- package/dist/tools/search-transcripts.js.map +1 -0
- package/dist/tools/search.d.ts +1 -0
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +74 -3
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/session-close.d.ts.map +1 -1
- package/dist/tools/session-close.js +563 -326
- package/dist/tools/session-close.js.map +1 -1
- package/dist/tools/session-start.d.ts +10 -6
- package/dist/tools/session-start.d.ts.map +1 -1
- package/dist/tools/session-start.js +317 -426
- package/dist/tools/session-start.js.map +1 -1
- package/dist/types/index.d.ts +37 -4
- package/dist/types/index.d.ts.map +1 -1
- package/hooks/.claude-plugin/plugin.json +8 -0
- package/hooks/README.md +107 -0
- package/hooks/hooks/hooks.json +123 -0
- package/hooks/scripts/auto-retrieve-hook.sh +163 -0
- package/hooks/scripts/post-tool-use.sh +112 -0
- package/hooks/scripts/recall-check.sh +213 -0
- package/hooks/scripts/session-close-check.sh +116 -0
- package/hooks/scripts/session-start.sh +233 -0
- package/hooks/tests/test-hooks.sh +577 -0
- package/package.json +4 -2
- package/schema/setup.sql +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GitMem Hooks Plugin — UserPromptSubmit Hook (Auto-Retrieve)
|
|
3
|
+
#
|
|
4
|
+
# Automatically searches institutional memory and injects relevant scars
|
|
5
|
+
# into the agent's context before it starts working.
|
|
6
|
+
#
|
|
7
|
+
# Pipeline:
|
|
8
|
+
# 1. Read user prompt from stdin (hook input JSON)
|
|
9
|
+
# 2. Classify task type via keyword matching (<50ms)
|
|
10
|
+
# 3. If retrieval needed, call quick-retrieve.js (Node module)
|
|
11
|
+
# 4. Inject results as additionalContext
|
|
12
|
+
#
|
|
13
|
+
# Task Types (priority order):
|
|
14
|
+
# trivial → none (confirmations, slash cmds, short prompts)
|
|
15
|
+
# architecture → full (design, decisions, trade-offs)
|
|
16
|
+
# research → full (synthesis, comparison, analysis)
|
|
17
|
+
# content → full (writing, documentation)
|
|
18
|
+
# implementation → scars (building, fixing, issue work)
|
|
19
|
+
# default → scars (conservative default)
|
|
20
|
+
#
|
|
21
|
+
# Design: fail open — if anything breaks, exit 0 (never block the user).
|
|
22
|
+
#
|
|
23
|
+
# Disable: GITMEM_AUTO_RETRIEVE=false
|
|
24
|
+
|
|
25
|
+
set -e
|
|
26
|
+
|
|
27
|
+
# Check if disabled
|
|
28
|
+
if [ "$GITMEM_AUTO_RETRIEVE" = "false" ] || [ "$GITMEM_AUTO_RETRIEVE" = "0" ]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Read hook input from stdin
|
|
33
|
+
HOOK_INPUT=$(cat -)
|
|
34
|
+
|
|
35
|
+
# ============================================================================
|
|
36
|
+
# Extract prompt from hook input
|
|
37
|
+
# ============================================================================
|
|
38
|
+
|
|
39
|
+
# Use node if available, fallback to basic extraction
|
|
40
|
+
PROMPT=""
|
|
41
|
+
if command -v node &>/dev/null; then
|
|
42
|
+
PROMPT=$(echo "$HOOK_INPUT" | node -e "
|
|
43
|
+
let d='';
|
|
44
|
+
process.stdin.on('data',c=>d+=c);
|
|
45
|
+
process.stdin.on('end',()=>{
|
|
46
|
+
try {
|
|
47
|
+
const j=JSON.parse(d);
|
|
48
|
+
process.stdout.write(j.prompt||'');
|
|
49
|
+
} catch(e) { }
|
|
50
|
+
});
|
|
51
|
+
" 2>/dev/null) || true
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Empty or missing prompt → nothing to do
|
|
55
|
+
if [ -z "$PROMPT" ]; then
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
PROMPT_LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
|
|
60
|
+
PROMPT_LEN=${#PROMPT}
|
|
61
|
+
|
|
62
|
+
# ============================================================================
|
|
63
|
+
# Task Classification (keyword matching, <50ms)
|
|
64
|
+
# ============================================================================
|
|
65
|
+
|
|
66
|
+
RETRIEVAL_LEVEL=""
|
|
67
|
+
|
|
68
|
+
# Priority 1: Trivial — skip retrieval entirely
|
|
69
|
+
if echo "$PROMPT_LOWER" | grep -qE '^(yes|no|ok|k|y|n|sure|thanks|thank you|continue|go ahead|proceed|correct|right|exactly|got it|sounds good|lgtm|looks good|done|nope|yep|yup|agreed)$'; then
|
|
70
|
+
exit 0
|
|
71
|
+
elif echo "$PROMPT_LOWER" | grep -qE '^/'; then
|
|
72
|
+
exit 0
|
|
73
|
+
elif echo "$PROMPT_LOWER" | grep -qE '^(closing|done for now|wrapping up|wrap up|that.s it|that.s all|gitmem)'; then
|
|
74
|
+
exit 0
|
|
75
|
+
elif echo "$PROMPT_LOWER" | grep -qE '\b(fix\s+typo|typo\b|formatting\b|indent(ation)?\b|whitespace\b|lint(ing)?\b)'; then
|
|
76
|
+
exit 0
|
|
77
|
+
elif [ "$PROMPT_LEN" -lt 12 ]; then
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# Priority 2: Architecture → full retrieval
|
|
82
|
+
if echo "$PROMPT_LOWER" | grep -qE '\b(architect(ure)?|design\s+(the\s+)?\w+|decide\s+(between|on|whether|how|if)|should\s+we|trade-?offs?\b|rfc\b|adr\b|system\s+design)\b'; then
|
|
83
|
+
RETRIEVAL_LEVEL="full"
|
|
84
|
+
|
|
85
|
+
# Priority 3: Research → full retrieval
|
|
86
|
+
elif echo "$PROMPT_LOWER" | grep -qE '\b(research\b|synthesize|synthesis\b|compare\s+(and|the|our)|contrast\b|analyze\s+(the\s+)?(options|approaches|alternatives)|evaluate\s+(frameworks|tools|approaches))\b'; then
|
|
87
|
+
RETRIEVAL_LEVEL="full"
|
|
88
|
+
|
|
89
|
+
# Priority 4: Content creation → full retrieval
|
|
90
|
+
elif echo "$PROMPT_LOWER" | grep -qE '\b(write\s+(a|an|the|about)|article\b|blog\s*(post)?|document\s+(the|how|what)|explain\s+(how|what|the)|guide\s+(to|for)|tutorial\b)\b'; then
|
|
91
|
+
RETRIEVAL_LEVEL="full"
|
|
92
|
+
|
|
93
|
+
# Priority 5: Implementation → scars only
|
|
94
|
+
elif echo "$PROMPT_LOWER" | grep -qE '\b(implement|build\b|fix\s+(the\s+)?bug|refactor|migrate\b|pickup\b|pick\s*up|create\s+(a|the)|add\s+(a|the))\b'; then
|
|
95
|
+
RETRIEVAL_LEVEL="scars"
|
|
96
|
+
|
|
97
|
+
# Default: scars (conservative — better to surface something than nothing)
|
|
98
|
+
else
|
|
99
|
+
RETRIEVAL_LEVEL="scars"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# ============================================================================
|
|
103
|
+
# Invoke quick-retrieve (Node module)
|
|
104
|
+
# ============================================================================
|
|
105
|
+
|
|
106
|
+
# Require node for retrieval
|
|
107
|
+
if ! command -v node &>/dev/null; then
|
|
108
|
+
exit 0
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Locate quick-retrieve.js relative to plugin root
|
|
112
|
+
# Hook scripts are at: ${CLAUDE_PLUGIN_ROOT}/scripts/
|
|
113
|
+
# Built JS is at: ${CLAUDE_PLUGIN_ROOT}/../dist/hooks/
|
|
114
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
115
|
+
QUICK_RETRIEVE="${SCRIPT_DIR}/../../dist/hooks/quick-retrieve.js"
|
|
116
|
+
|
|
117
|
+
# Fallback: try relative to CLAUDE_PLUGIN_ROOT
|
|
118
|
+
if [ ! -f "$QUICK_RETRIEVE" ]; then
|
|
119
|
+
QUICK_RETRIEVE="${CLAUDE_PLUGIN_ROOT}/../dist/hooks/quick-retrieve.js"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# If still not found, try the gitmem package location
|
|
123
|
+
if [ ! -f "$QUICK_RETRIEVE" ]; then
|
|
124
|
+
# Check common install locations
|
|
125
|
+
for CANDIDATE in \
|
|
126
|
+
"/workspace/gitmem/dist/hooks/quick-retrieve.js" \
|
|
127
|
+
"$(npm root -g 2>/dev/null)/gitmem/dist/hooks/quick-retrieve.js" \
|
|
128
|
+
"$(dirname "$(which gitmem 2>/dev/null)")/../lib/node_modules/gitmem/dist/hooks/quick-retrieve.js"; do
|
|
129
|
+
if [ -f "$CANDIDATE" ]; then
|
|
130
|
+
QUICK_RETRIEVE="$CANDIDATE"
|
|
131
|
+
break
|
|
132
|
+
fi
|
|
133
|
+
done
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [ ! -f "$QUICK_RETRIEVE" ]; then
|
|
137
|
+
# Can't find quick-retrieve module — fail open
|
|
138
|
+
exit 0
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Call quick-retrieve with prompt and level
|
|
142
|
+
# Timeout: 2.5s (leave 500ms buffer within 3s hook timeout)
|
|
143
|
+
RESULT=$(timeout 2.5 node "$QUICK_RETRIEVE" "$PROMPT" "$RETRIEVAL_LEVEL" 2>/dev/null) || true
|
|
144
|
+
|
|
145
|
+
# Empty result → nothing relevant found
|
|
146
|
+
if [ -z "$RESULT" ]; then
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# ============================================================================
|
|
151
|
+
# Inject as additionalContext
|
|
152
|
+
# ============================================================================
|
|
153
|
+
|
|
154
|
+
# Escape the result for JSON embedding
|
|
155
|
+
ESCAPED_RESULT=$(node -e "process.stdout.write(JSON.stringify(process.argv[1]))" "$RESULT" 2>/dev/null) || exit 0
|
|
156
|
+
|
|
157
|
+
cat <<HOOKJSON
|
|
158
|
+
{
|
|
159
|
+
"additionalContext": ${ESCAPED_RESULT}
|
|
160
|
+
}
|
|
161
|
+
HOOKJSON
|
|
162
|
+
|
|
163
|
+
exit 0
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GitMem Hooks Plugin — PostToolUse Hook (Audit Trail)
|
|
3
|
+
# Logs LOOKED events (after recall/search) and ACTION events (after consequential actions).
|
|
4
|
+
#
|
|
5
|
+
# Event types:
|
|
6
|
+
# LOOKED — Agent checked institutional memory (recall, search)
|
|
7
|
+
# ACTION — Agent took a consequential action (git push, Linear Done, SQL migration)
|
|
8
|
+
#
|
|
9
|
+
# Audit trail: append-only JSONL at /tmp/gitmem-hooks-{SESSION_ID}/audit.jsonl
|
|
10
|
+
# Non-consequential actions pass through silently.
|
|
11
|
+
#
|
|
12
|
+
# Input: JSON via stdin with tool_name, tool_input, tool_output
|
|
13
|
+
# Output: empty (exit 0) — PostToolUse hooks never block
|
|
14
|
+
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
# Read hook input from stdin
|
|
18
|
+
HOOK_INPUT=$(cat -)
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# Graceful degradation: skip if no active gitmem session
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
# Check active sessions registry (Phase 1 multi-session, GIT-19)
|
|
25
|
+
ACTIVE_SESSIONS=".gitmem/active-sessions.json"
|
|
26
|
+
if [ ! -f "$ACTIVE_SESSIONS" ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# JSON parsing helper (jq preferred, node fallback)
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
parse_json() {
|
|
35
|
+
local INPUT="$1"
|
|
36
|
+
local FIELD="$2"
|
|
37
|
+
if command -v jq &>/dev/null; then
|
|
38
|
+
echo "$INPUT" | jq -r "$FIELD // empty" 2>/dev/null
|
|
39
|
+
else
|
|
40
|
+
echo "$INPUT" | node -e "
|
|
41
|
+
let d='';
|
|
42
|
+
process.stdin.on('data',c=>d+=c);
|
|
43
|
+
process.stdin.on('end',()=>{
|
|
44
|
+
try {
|
|
45
|
+
const j=JSON.parse(d);
|
|
46
|
+
const path='$FIELD'.replace(/^\./,'').split('.');
|
|
47
|
+
let v=j;
|
|
48
|
+
for(const p of path) v=v?.[p];
|
|
49
|
+
process.stdout.write(String(v||''));
|
|
50
|
+
} catch(e) { process.stdout.write(''); }
|
|
51
|
+
});
|
|
52
|
+
" 2>/dev/null
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
TOOL_NAME=$(parse_json "$HOOK_INPUT" ".tool_name")
|
|
57
|
+
|
|
58
|
+
# ============================================================================
|
|
59
|
+
# Classify event: LOOKED or ACTION
|
|
60
|
+
# ============================================================================
|
|
61
|
+
|
|
62
|
+
EVENT_TYPE=""
|
|
63
|
+
DETAIL=""
|
|
64
|
+
|
|
65
|
+
case "$TOOL_NAME" in
|
|
66
|
+
mcp__gitmem__recall|mcp__gitmem__gitmem-r)
|
|
67
|
+
EVENT_TYPE="LOOKED"
|
|
68
|
+
PLAN=$(parse_json "$HOOK_INPUT" ".tool_input.plan")
|
|
69
|
+
DETAIL="plan: ${PLAN:-<no plan>}"
|
|
70
|
+
;;
|
|
71
|
+
mcp__gitmem__search|mcp__gitmem__gm-scar)
|
|
72
|
+
EVENT_TYPE="LOOKED"
|
|
73
|
+
QUERY=$(parse_json "$HOOK_INPUT" ".tool_input.query")
|
|
74
|
+
DETAIL="query: ${QUERY:-<no query>}"
|
|
75
|
+
;;
|
|
76
|
+
Bash)
|
|
77
|
+
COMMAND=$(parse_json "$HOOK_INPUT" ".tool_input.command")
|
|
78
|
+
if echo "$COMMAND" | grep -qE '(git\s+push|git\s+tag|npm\s+publish|npx\s+supabase\s+db\s+push|deploy|supabase\s+functions\s+deploy)'; then
|
|
79
|
+
EVENT_TYPE="ACTION"
|
|
80
|
+
DETAIL="command: $COMMAND"
|
|
81
|
+
fi
|
|
82
|
+
;;
|
|
83
|
+
Write|Edit)
|
|
84
|
+
FILE_PATH=$(parse_json "$HOOK_INPUT" ".tool_input.file_path")
|
|
85
|
+
if echo "$FILE_PATH" | grep -qE '\.(sql|env)$'; then
|
|
86
|
+
EVENT_TYPE="ACTION"
|
|
87
|
+
DETAIL="file: $FILE_PATH"
|
|
88
|
+
fi
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
|
|
92
|
+
# Not a tracked event → pass through silently
|
|
93
|
+
if [ -z "$EVENT_TYPE" ]; then
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# ============================================================================
|
|
98
|
+
# Append to audit trail (JSONL)
|
|
99
|
+
# ============================================================================
|
|
100
|
+
|
|
101
|
+
SESSION_ID="${CLAUDE_SESSION_ID:-$$}"
|
|
102
|
+
STATE_DIR="/tmp/gitmem-hooks-${SESSION_ID}"
|
|
103
|
+
mkdir -p "$STATE_DIR"
|
|
104
|
+
|
|
105
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
106
|
+
|
|
107
|
+
# Escape detail for JSON (replace quotes and backslashes)
|
|
108
|
+
DETAIL_ESCAPED=$(echo "$DETAIL" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
|
|
109
|
+
|
|
110
|
+
echo "{\"timestamp\":\"${TIMESTAMP}\",\"type\":\"${EVENT_TYPE}\",\"tool\":\"${TOOL_NAME}\",\"detail\":\"${DETAIL_ESCAPED}\"}" >> "$STATE_DIR/audit.jsonl"
|
|
111
|
+
|
|
112
|
+
exit 0
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GitMem Hooks Plugin — PreToolUse Hook (Recall Check + Confirmation Gate)
|
|
3
|
+
#
|
|
4
|
+
# Two enforcement mechanisms for consequential actions:
|
|
5
|
+
#
|
|
6
|
+
# 1. CONFIRMATION GATE (hard block):
|
|
7
|
+
# If recall() surfaced scars but confirm_scars() hasn't been called → BLOCK.
|
|
8
|
+
# Uses JSON "decision: block" pattern (same as session-close-check.sh).
|
|
9
|
+
# Only blocks on recall-source scars; session_start scars don't require confirmation.
|
|
10
|
+
#
|
|
11
|
+
# 2. RECALL NAG (soft reminder):
|
|
12
|
+
# If recall hasn't been called recently → nudge (additionalContext, never blocks).
|
|
13
|
+
# - If recall never called AND >3 tool calls → nag
|
|
14
|
+
# - Cooldown: no more than once per 60 seconds
|
|
15
|
+
#
|
|
16
|
+
# Filter layer: Only triggers on consequential actions:
|
|
17
|
+
# - Bash: git push, git tag, npm publish, deploy commands
|
|
18
|
+
# - Write/Edit: .sql migrations, .env files
|
|
19
|
+
#
|
|
20
|
+
# Input: JSON via stdin with tool_name and tool_input
|
|
21
|
+
# Output: JSON with decision:block OR additionalContext OR empty (exit 0)
|
|
22
|
+
|
|
23
|
+
set -e
|
|
24
|
+
|
|
25
|
+
# Read hook input from stdin
|
|
26
|
+
HOOK_INPUT=$(cat -)
|
|
27
|
+
|
|
28
|
+
# ============================================================================
|
|
29
|
+
# Resolve active session from registry (Phase 1 multi-session, GIT-19)
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
ACTIVE_SESSIONS=".gitmem/active-sessions.json"
|
|
33
|
+
if [ ! -f "$ACTIVE_SESSIONS" ]; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Get first session ID from registry to find per-session data file
|
|
38
|
+
SESSION_FILE=""
|
|
39
|
+
if command -v jq &>/dev/null; then
|
|
40
|
+
SID=$(jq -r '.sessions[0].session_id // empty' "$ACTIVE_SESSIONS" 2>/dev/null)
|
|
41
|
+
[ -n "$SID" ] && SESSION_FILE=".gitmem/sessions/${SID}/session.json"
|
|
42
|
+
elif command -v node &>/dev/null; then
|
|
43
|
+
SID=$(node -e "const fs=require('fs');try{const r=JSON.parse(fs.readFileSync('$ACTIVE_SESSIONS','utf8'));const s=(r.sessions||[])[0];process.stdout.write(s?.session_id||'')}catch(e){}" 2>/dev/null)
|
|
44
|
+
[ -n "$SID" ] && SESSION_FILE=".gitmem/sessions/${SID}/session.json"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if [ -z "$SESSION_FILE" ] || [ ! -f "$SESSION_FILE" ]; then
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# ============================================================================
|
|
52
|
+
# Extract tool info using jq (node fallback)
|
|
53
|
+
# ============================================================================
|
|
54
|
+
|
|
55
|
+
parse_json() {
|
|
56
|
+
local INPUT="$1"
|
|
57
|
+
local FIELD="$2"
|
|
58
|
+
if command -v jq &>/dev/null; then
|
|
59
|
+
echo "$INPUT" | jq -r "$FIELD // empty" 2>/dev/null
|
|
60
|
+
else
|
|
61
|
+
echo "$INPUT" | node -e "
|
|
62
|
+
let d='';
|
|
63
|
+
process.stdin.on('data',c=>d+=c);
|
|
64
|
+
process.stdin.on('end',()=>{
|
|
65
|
+
try {
|
|
66
|
+
const j=JSON.parse(d);
|
|
67
|
+
const path='$FIELD'.replace(/^\./,'').split('.');
|
|
68
|
+
let v=j;
|
|
69
|
+
for(const p of path) v=v?.[p];
|
|
70
|
+
process.stdout.write(String(v||''));
|
|
71
|
+
} catch(e) { process.stdout.write(''); }
|
|
72
|
+
});
|
|
73
|
+
" 2>/dev/null
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Read a numeric field from per-session file safely
|
|
78
|
+
read_session_count() {
|
|
79
|
+
local JQ_FILTER="$1"
|
|
80
|
+
local NODE_SCRIPT="$2"
|
|
81
|
+
if command -v jq &>/dev/null; then
|
|
82
|
+
jq "$JQ_FILTER" "$SESSION_FILE" 2>/dev/null || echo "0"
|
|
83
|
+
else
|
|
84
|
+
node -e "$NODE_SCRIPT" 2>/dev/null || echo "0"
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
TOOL_NAME=$(parse_json "$HOOK_INPUT" ".tool_name")
|
|
89
|
+
|
|
90
|
+
# ============================================================================
|
|
91
|
+
# Filter layer: Is this a consequential action?
|
|
92
|
+
# ============================================================================
|
|
93
|
+
|
|
94
|
+
IS_CONSEQUENTIAL=false
|
|
95
|
+
|
|
96
|
+
case "$TOOL_NAME" in
|
|
97
|
+
Bash)
|
|
98
|
+
# Extract the command being run
|
|
99
|
+
COMMAND=$(parse_json "$HOOK_INPUT" ".tool_input.command")
|
|
100
|
+
# Check for consequential bash commands
|
|
101
|
+
if echo "$COMMAND" | grep -qE '(git\s+push|git\s+tag|npm\s+publish|npx\s+supabase\s+db\s+push|deploy|supabase\s+functions\s+deploy)'; then
|
|
102
|
+
IS_CONSEQUENTIAL=true
|
|
103
|
+
fi
|
|
104
|
+
;;
|
|
105
|
+
Write|Edit)
|
|
106
|
+
# Check for sensitive file types
|
|
107
|
+
FILE_PATH=$(parse_json "$HOOK_INPUT" ".tool_input.file_path")
|
|
108
|
+
if echo "$FILE_PATH" | grep -qE '\.(sql|env)$'; then
|
|
109
|
+
IS_CONSEQUENTIAL=true
|
|
110
|
+
fi
|
|
111
|
+
;;
|
|
112
|
+
esac
|
|
113
|
+
|
|
114
|
+
# Not consequential → pass through silently
|
|
115
|
+
if [ "$IS_CONSEQUENTIAL" != "true" ]; then
|
|
116
|
+
exit 0
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# ============================================================================
|
|
120
|
+
# CONFIRMATION GATE (runs first — hard block takes priority over soft nag)
|
|
121
|
+
# ============================================================================
|
|
122
|
+
# Block if recall() surfaced scars but confirm_scars() hasn't been called.
|
|
123
|
+
# Only blocks on recall-source scars; session_start scars don't require confirmation.
|
|
124
|
+
|
|
125
|
+
RECALL_SCAR_COUNT=$(read_session_count \
|
|
126
|
+
'[.surfaced_scars // [] | .[] | select(.source == "recall")] | length' \
|
|
127
|
+
"const fs=require('fs');try{const s=JSON.parse(fs.readFileSync('$SESSION_FILE','utf8'));const c=(s.surfaced_scars||[]).filter(x=>x.source==='recall');process.stdout.write(String(c.length))}catch(e){process.stdout.write('0')}")
|
|
128
|
+
|
|
129
|
+
CONFIRMATION_COUNT=$(read_session_count \
|
|
130
|
+
'[.confirmations // [] | .[]] | length' \
|
|
131
|
+
"const fs=require('fs');try{const s=JSON.parse(fs.readFileSync('$SESSION_FILE','utf8'));process.stdout.write(String((s.confirmations||[]).length))}catch(e){process.stdout.write('0')}")
|
|
132
|
+
|
|
133
|
+
if [ "$RECALL_SCAR_COUNT" -gt 0 ] 2>/dev/null && [ "$CONFIRMATION_COUNT" -eq 0 ] 2>/dev/null; then
|
|
134
|
+
# Get scar titles for the error message
|
|
135
|
+
if command -v jq &>/dev/null; then
|
|
136
|
+
SCAR_TITLES=$(jq -r '[.surfaced_scars // [] | .[] | select(.source == "recall") | .scar_title] | join(", ")' "$SESSION_FILE" 2>/dev/null || echo "(unknown)")
|
|
137
|
+
else
|
|
138
|
+
SCAR_TITLES=$(node -e "
|
|
139
|
+
const fs=require('fs');
|
|
140
|
+
try{const s=JSON.parse(fs.readFileSync('$SESSION_FILE','utf8'));
|
|
141
|
+
const t=(s.surfaced_scars||[]).filter(x=>x.source==='recall').map(x=>x.scar_title);
|
|
142
|
+
process.stdout.write(t.join(', '))}catch(e){process.stdout.write('(unknown)')}" 2>/dev/null || echo "(unknown)")
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
cat <<HOOKJSON
|
|
146
|
+
{
|
|
147
|
+
"decision": "block",
|
|
148
|
+
"reason": "SCAR CONFIRMATION REQUIRED: recall() surfaced ${RECALL_SCAR_COUNT} scar(s) that have not been confirmed. Call confirm_scars() (or gm-confirm) with APPLYING/N_A/REFUTED for each scar before proceeding.\n\nUnconfirmed scars: ${SCAR_TITLES}\n\nEach scar must be addressed with:\n- APPLYING: past-tense evidence of compliance\n- N_A: explain why the scar doesn't apply\n- REFUTED: acknowledge risk of overriding"
|
|
149
|
+
}
|
|
150
|
+
HOOKJSON
|
|
151
|
+
exit 0
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# ============================================================================
|
|
155
|
+
# Session state tracking (for nag logic)
|
|
156
|
+
# ============================================================================
|
|
157
|
+
|
|
158
|
+
SESSION_ID="${CLAUDE_SESSION_ID:-$$}"
|
|
159
|
+
STATE_DIR="/tmp/gitmem-hooks-${SESSION_ID}"
|
|
160
|
+
mkdir -p "$STATE_DIR"
|
|
161
|
+
|
|
162
|
+
# Increment tool call count
|
|
163
|
+
TOOL_COUNT=0
|
|
164
|
+
if [ -f "$STATE_DIR/tool_call_count" ]; then
|
|
165
|
+
TOOL_COUNT=$(cat "$STATE_DIR/tool_call_count")
|
|
166
|
+
fi
|
|
167
|
+
TOOL_COUNT=$((TOOL_COUNT + 1))
|
|
168
|
+
echo "$TOOL_COUNT" > "$STATE_DIR/tool_call_count"
|
|
169
|
+
|
|
170
|
+
# ============================================================================
|
|
171
|
+
# Cooldown check: don't nag more than once per 60 seconds
|
|
172
|
+
# ============================================================================
|
|
173
|
+
|
|
174
|
+
NOW=$(date +%s)
|
|
175
|
+
LAST_NAG=0
|
|
176
|
+
if [ -f "$STATE_DIR/last_nag_time" ]; then
|
|
177
|
+
LAST_NAG=$(cat "$STATE_DIR/last_nag_time")
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
ELAPSED_SINCE_NAG=$((NOW - LAST_NAG))
|
|
181
|
+
if [ "$ELAPSED_SINCE_NAG" -lt 60 ]; then
|
|
182
|
+
exit 0
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# ============================================================================
|
|
186
|
+
# RECALL NAG: Nudge if recall hasn't been called
|
|
187
|
+
# ============================================================================
|
|
188
|
+
# Check if any recall-source scars exist. If RECALL_SCAR_COUNT is 0 and
|
|
189
|
+
# we've had >3 tool calls, the agent hasn't called recall at all → nag.
|
|
190
|
+
|
|
191
|
+
SHOULD_NAG=false
|
|
192
|
+
|
|
193
|
+
if [ "$RECALL_SCAR_COUNT" -eq 0 ] 2>/dev/null; then
|
|
194
|
+
# No recall scars found — recall probably wasn't called
|
|
195
|
+
if [ "$TOOL_COUNT" -gt 3 ]; then
|
|
196
|
+
SHOULD_NAG=true
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# ============================================================================
|
|
201
|
+
# Output nag or pass through
|
|
202
|
+
# ============================================================================
|
|
203
|
+
|
|
204
|
+
if [ "$SHOULD_NAG" = "true" ]; then
|
|
205
|
+
echo "$NOW" > "$STATE_DIR/last_nag_time"
|
|
206
|
+
cat <<'HOOKJSON'
|
|
207
|
+
{
|
|
208
|
+
"additionalContext": "GITMEM RECALL REMINDER: You're about to take a consequential action but haven't checked institutional memory recently. Consider calling `recall` (or `gitmem-r`) with your plan before proceeding. This surfaces relevant scars that may prevent repeating past mistakes."
|
|
209
|
+
}
|
|
210
|
+
HOOKJSON
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
exit 0
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GitMem Hooks Plugin — Stop Hook (Session Close Check)
|
|
3
|
+
# Ensures session_close is called before the session ends.
|
|
4
|
+
#
|
|
5
|
+
# Logic:
|
|
6
|
+
# 1. Check if session is meaningful (registry has entries, or >5 tool calls, or >5 min)
|
|
7
|
+
# 2. If session was closed properly (registry empty) → allow stop
|
|
8
|
+
# 3. If meaningful but not closed → block with reminder
|
|
9
|
+
# 4. Infinite loop guard: if stop_hook_active flag set → always allow
|
|
10
|
+
# 5. Trivial sessions skip enforcement
|
|
11
|
+
#
|
|
12
|
+
# Input: JSON via stdin (hook event data)
|
|
13
|
+
# Output: JSON with decision: "block" and message if enforcement triggered
|
|
14
|
+
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
# Read hook input from stdin
|
|
18
|
+
HOOK_INPUT=$(cat -)
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# Infinite loop guard
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
SESSION_ID="${CLAUDE_SESSION_ID:-$$}"
|
|
25
|
+
STATE_DIR="/tmp/gitmem-hooks-${SESSION_ID}"
|
|
26
|
+
|
|
27
|
+
if [ -f "$STATE_DIR/stop_hook_active" ]; then
|
|
28
|
+
# Already fired once and blocked — don't block again
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# ============================================================================
|
|
33
|
+
# Check if session is meaningful
|
|
34
|
+
# ============================================================================
|
|
35
|
+
|
|
36
|
+
IS_MEANINGFUL=false
|
|
37
|
+
|
|
38
|
+
ACTIVE_SESSIONS=".gitmem/active-sessions.json"
|
|
39
|
+
SESSION_STARTED=false
|
|
40
|
+
if [ -f "$ACTIVE_SESSIONS" ]; then
|
|
41
|
+
# Check if registry has active session entries (Phase 1 multi-session, GIT-19)
|
|
42
|
+
if command -v jq &>/dev/null; then
|
|
43
|
+
[ "$(jq '.sessions | length' "$ACTIVE_SESSIONS" 2>/dev/null || echo 0)" -gt 0 ] 2>/dev/null && SESSION_STARTED=true
|
|
44
|
+
elif command -v node &>/dev/null; then
|
|
45
|
+
[ "$(node -e "const fs=require('fs');try{const r=JSON.parse(fs.readFileSync('$ACTIVE_SESSIONS','utf8'));process.stdout.write(String((r.sessions||[]).length))}catch(e){process.stdout.write('0')}" 2>/dev/null)" -gt 0 ] 2>/dev/null && SESSION_STARTED=true
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Without state dir, we have no tracking data — can't determine meaningfulness.
|
|
50
|
+
# This happens when the SessionStart hook didn't fire (plugin hooks issue).
|
|
51
|
+
# Don't block with bogus defaults — skip enforcement gracefully.
|
|
52
|
+
if [ ! -d "$STATE_DIR" ]; then
|
|
53
|
+
if [ "$SESSION_STARTED" = "true" ]; then
|
|
54
|
+
# Session exists but no tracking — create state dir NOW so future
|
|
55
|
+
# Stop attempts can track from this point forward.
|
|
56
|
+
mkdir -p "$STATE_DIR"
|
|
57
|
+
date +%s > "$STATE_DIR/start_time"
|
|
58
|
+
echo "0" > "$STATE_DIR/tool_call_count"
|
|
59
|
+
fi
|
|
60
|
+
# First time seeing this — not meaningful yet
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Criterion 1: >5 tool calls
|
|
65
|
+
TOOL_COUNT=0
|
|
66
|
+
if [ -f "$STATE_DIR/tool_call_count" ]; then
|
|
67
|
+
TOOL_COUNT=$(cat "$STATE_DIR/tool_call_count" 2>/dev/null || echo "0")
|
|
68
|
+
fi
|
|
69
|
+
if [ "$TOOL_COUNT" -gt 5 ]; then
|
|
70
|
+
IS_MEANINGFUL=true
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Criterion 2: >5 minutes duration
|
|
74
|
+
NOW=$(date +%s)
|
|
75
|
+
if [ -f "$STATE_DIR/start_time" ]; then
|
|
76
|
+
START_TIME=$(cat "$STATE_DIR/start_time" 2>/dev/null || echo "$NOW")
|
|
77
|
+
DURATION=$((NOW - START_TIME))
|
|
78
|
+
if [ "$DURATION" -gt 300 ]; then
|
|
79
|
+
IS_MEANINGFUL=true
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# ============================================================================
|
|
84
|
+
# Trivial session → skip enforcement
|
|
85
|
+
# ============================================================================
|
|
86
|
+
|
|
87
|
+
if [ "$IS_MEANINGFUL" != "true" ]; then
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# Check if session was closed properly
|
|
93
|
+
# ============================================================================
|
|
94
|
+
|
|
95
|
+
if [ "$SESSION_STARTED" = "true" ]; then
|
|
96
|
+
# Registry still has entries → session not closed
|
|
97
|
+
# Set guard flag to prevent infinite blocking
|
|
98
|
+
mkdir -p "$STATE_DIR"
|
|
99
|
+
touch "$STATE_DIR/stop_hook_active"
|
|
100
|
+
|
|
101
|
+
cat <<'HOOKJSON'
|
|
102
|
+
{
|
|
103
|
+
"decision": "block",
|
|
104
|
+
"reason": "GITMEM SESSION STILL OPEN — Run the standard closing ceremony:\n\n1. YOU (the agent) ANSWER these 7 reflection questions based on the session. Display your answers to the human:\n - what_broke: What broke that you didn't expect?\n - what_took_longer: What took longer than it should have?\n - do_differently: What would you do differently next time?\n - what_worked: What pattern or approach worked well?\n - wrong_assumption: What assumption was wrong?\n - scars_applied: Which scars or institutional knowledge did you apply?\n - institutional_memory: What from this session should be captured?\n\n2. ASK the human: 'Any corrections or additions to my answers?' WAIT for their response.\n\n3. WRITE structured payload to .gitmem/closing-payload.json with closing_reflection (7 fields above, incorporating human corrections), task_completion (timestamps), and human_corrections.\n\n4. CALL session_close with session_id and close_type: 'standard'.\n\nFor trivial sessions (< 30min, exploratory only), use close_type: 'quick' instead — no questions needed."
|
|
105
|
+
}
|
|
106
|
+
HOOKJSON
|
|
107
|
+
exit 0
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Session was closed properly (registry empty) or never started
|
|
111
|
+
# Clean up state dir
|
|
112
|
+
if [ -d "$STATE_DIR" ]; then
|
|
113
|
+
rm -rf "$STATE_DIR"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
exit 0
|