gitmem-mcp 1.0.15 → 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/CHANGELOG.md +17 -0
- package/README.md +2 -2
- package/bin/gitmem.js +264 -123
- package/bin/init-wizard.js +332 -93
- package/bin/uninstall.js +187 -45
- package/cursorrules.template +92 -0
- package/dist/commands/check.js +8 -1
- package/dist/schemas/absorb-observations.js +3 -3
- package/dist/schemas/create-decision.js +8 -8
- package/dist/schemas/create-learning.js +13 -13
- package/dist/schemas/prepare-context.js +2 -2
- package/dist/schemas/thread.js +10 -10
- package/dist/server.js +1 -1
- package/dist/services/behavioral-decay.js +3 -1
- package/dist/services/embedding.js +2 -0
- package/dist/services/local-file-storage.js +3 -1
- package/dist/services/thread-manager.js +5 -2
- package/dist/tools/confirm-scars.js +6 -3
- package/dist/tools/recall.js +3 -1
- package/hooks/scripts/recall-check.sh +51 -52
- package/package.json +2 -1
|
@@ -56,8 +56,10 @@ export function normalizeThreads(raw, sourceSession) {
|
|
|
56
56
|
id: parsed.id,
|
|
57
57
|
text: parsed.note,
|
|
58
58
|
status: parsed.status,
|
|
59
|
+
// Preserve existing created_at — only default to now() for genuinely new threads
|
|
59
60
|
created_at: parsed.created_at || new Date().toISOString(),
|
|
60
|
-
...(sourceSession && { source_session: sourceSession }),
|
|
61
|
+
...(sourceSession && !parsed.source_session && { source_session: sourceSession }),
|
|
62
|
+
...(parsed.source_session && { source_session: parsed.source_session }),
|
|
61
63
|
...(parsed.resolved_at && { resolved_at: parsed.resolved_at }),
|
|
62
64
|
};
|
|
63
65
|
}
|
|
@@ -88,8 +90,9 @@ export function normalizeThreads(raw, sourceSession) {
|
|
|
88
90
|
id: inner.id,
|
|
89
91
|
text: inner.text || inner.note,
|
|
90
92
|
status: inner.status || item.status,
|
|
93
|
+
// Preserve the earliest available created_at — never overwrite with now()
|
|
91
94
|
created_at: inner.created_at || item.created_at || new Date().toISOString(),
|
|
92
|
-
...(sourceSession && { source_session: sourceSession }),
|
|
95
|
+
...(sourceSession && !item.source_session && { source_session: sourceSession }),
|
|
93
96
|
...(inner.resolved_at && { resolved_at: inner.resolved_at }),
|
|
94
97
|
};
|
|
95
98
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* N_A — Scar doesn't apply, scenario comparison required
|
|
16
16
|
* REFUTED — Overriding scar, risk acknowledgment required
|
|
17
17
|
*/
|
|
18
|
-
import { getCurrentSession, getSurfacedScars, addConfirmations, } from "../services/session-state.js";
|
|
18
|
+
import { getCurrentSession, getSurfacedScars, addConfirmations, getConfirmations, } from "../services/session-state.js";
|
|
19
19
|
import { Timer, buildPerformanceData } from "../services/metrics.js";
|
|
20
20
|
import { getSessionPath } from "../services/gitmem-dir.js";
|
|
21
21
|
import { wrapDisplay } from "../services/display-protocol.js";
|
|
@@ -23,7 +23,8 @@ import * as fs from "fs";
|
|
|
23
23
|
// Minimum evidence length per decision type
|
|
24
24
|
const MIN_EVIDENCE_LENGTH = 50;
|
|
25
25
|
// Future-tense patterns — APPLYING must use past tense
|
|
26
|
-
|
|
26
|
+
// Only catch first-person forward-looking language, not third-person "will"
|
|
27
|
+
const FUTURE_PATTERNS = /\b(I will|I'll|we will|we'll|I'm going to|we're going to|I plan to|I intend to|I shall|I aim to|I expect to)\b/i;
|
|
27
28
|
/**
|
|
28
29
|
* Validate a single confirmation against its surfaced scar.
|
|
29
30
|
* Returns null if valid, or an error string if invalid.
|
|
@@ -192,9 +193,11 @@ export async function confirmScars(params) {
|
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
// Check for missing scars (all recall scars must be addressed)
|
|
196
|
+
// Credit scars already confirmed in a previous call this session
|
|
197
|
+
const previouslyConfirmedIds = new Set(getConfirmations().map(c => c.scar_id));
|
|
195
198
|
const missingScars = [];
|
|
196
199
|
for (const scar of recallScars) {
|
|
197
|
-
if (!confirmedIds.has(scar.scar_id)) {
|
|
200
|
+
if (!confirmedIds.has(scar.scar_id) && !previouslyConfirmedIds.has(scar.scar_id)) {
|
|
198
201
|
missingScars.push(scar.scar_title);
|
|
199
202
|
}
|
|
200
203
|
}
|
package/dist/tools/recall.js
CHANGED
|
@@ -172,7 +172,9 @@ export async function recall(params) {
|
|
|
172
172
|
const matchCount = params.match_count || 3;
|
|
173
173
|
const issueId = params.issue_id; // For variant assignment
|
|
174
174
|
// Similarity threshold — suppress weak matches
|
|
175
|
-
|
|
175
|
+
// Pro tier: 0.45 calibrated from UX audit (66% N_A rate at 0.35, APPLYING avg 0.55, N_A avg 0.51)
|
|
176
|
+
// Free tier: 0.4 (BM25 scores are relative — top result always 1.0)
|
|
177
|
+
const defaultThreshold = hasSupabase() ? 0.45 : 0.4;
|
|
176
178
|
const similarityThreshold = params.similarity_threshold ?? defaultThreshold;
|
|
177
179
|
// Free tier: use local keyword search
|
|
178
180
|
if (!hasSupabase()) {
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# GitMem Hooks Plugin — PreToolUse Hook (Recall Check + Confirmation Gate)
|
|
3
3
|
#
|
|
4
|
-
# Two enforcement mechanisms
|
|
4
|
+
# Two enforcement mechanisms with different trigger scopes:
|
|
5
5
|
#
|
|
6
|
-
# 1. CONFIRMATION GATE (hard block):
|
|
6
|
+
# 1. CONFIRMATION GATE (hard block, consequential actions only):
|
|
7
7
|
# If recall() surfaced scars but confirm_scars() hasn't been called → BLOCK.
|
|
8
8
|
# Uses JSON "decision: block" pattern (same as session-close-check.sh).
|
|
9
9
|
# Only blocks on recall-source scars; session_start scars don't require confirmation.
|
|
10
|
+
# Consequential = git push, npm publish, deploy, .sql, .env files.
|
|
10
11
|
#
|
|
11
|
-
# 2. RECALL NAG (soft reminder):
|
|
12
|
-
# If recall hasn't been called
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# 2. RECALL NAG (soft reminder, ALL Bash/Write/Edit actions):
|
|
13
|
+
# If recall hasn't been called AND agent has made 10+ tool calls → nudge.
|
|
14
|
+
# Never blocks — just injects additionalContext.
|
|
15
|
+
# Cooldown: no more than once per 90 seconds.
|
|
16
|
+
# NOTE: hooks.json matchers already limit this to Bash/Write/Edit.
|
|
15
17
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
18
|
+
# UX audit finding: 12 sessions >30min with zero recalls because the nag was
|
|
19
|
+
# gated behind the consequential filter — agents writing .ts files, running
|
|
20
|
+
# tests, etc. were never nudged. Now the nag fires for all code changes.
|
|
19
21
|
#
|
|
20
22
|
# Input: JSON via stdin with tool_name and tool_input
|
|
21
23
|
# Output: JSON with decision:block OR additionalContext OR empty (exit 0)
|
|
@@ -88,7 +90,35 @@ read_session_count() {
|
|
|
88
90
|
TOOL_NAME=$(parse_json "$HOOK_INPUT" ".tool_name")
|
|
89
91
|
|
|
90
92
|
# ============================================================================
|
|
91
|
-
#
|
|
93
|
+
# Read session state (shared by both gate and nag)
|
|
94
|
+
# ============================================================================
|
|
95
|
+
|
|
96
|
+
RECALL_SCAR_COUNT=$(read_session_count \
|
|
97
|
+
'[.surfaced_scars // [] | .[] | select(.source == "recall")] | length' \
|
|
98
|
+
"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')}")
|
|
99
|
+
|
|
100
|
+
CONFIRMATION_COUNT=$(read_session_count \
|
|
101
|
+
'[.confirmations // [] | .[]] | length' \
|
|
102
|
+
"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')}")
|
|
103
|
+
|
|
104
|
+
# ============================================================================
|
|
105
|
+
# Session state tracking (for nag logic — runs for ALL matched tool calls)
|
|
106
|
+
# ============================================================================
|
|
107
|
+
|
|
108
|
+
SESSION_ID="${CLAUDE_SESSION_ID:-$$}"
|
|
109
|
+
STATE_DIR="/tmp/gitmem-hooks-${SESSION_ID}"
|
|
110
|
+
mkdir -p "$STATE_DIR"
|
|
111
|
+
|
|
112
|
+
# Increment tool call count (counts all Bash/Write/Edit, not just consequential)
|
|
113
|
+
TOOL_COUNT=0
|
|
114
|
+
if [ -f "$STATE_DIR/tool_call_count" ]; then
|
|
115
|
+
TOOL_COUNT=$(cat "$STATE_DIR/tool_call_count")
|
|
116
|
+
fi
|
|
117
|
+
TOOL_COUNT=$((TOOL_COUNT + 1))
|
|
118
|
+
echo "$TOOL_COUNT" > "$STATE_DIR/tool_call_count"
|
|
119
|
+
|
|
120
|
+
# ============================================================================
|
|
121
|
+
# Filter layer: Is this a consequential action? (used for gate only)
|
|
92
122
|
# ============================================================================
|
|
93
123
|
|
|
94
124
|
IS_CONSEQUENTIAL=false
|
|
@@ -111,26 +141,13 @@ case "$TOOL_NAME" in
|
|
|
111
141
|
;;
|
|
112
142
|
esac
|
|
113
143
|
|
|
114
|
-
# Not consequential → pass through silently
|
|
115
|
-
if [ "$IS_CONSEQUENTIAL" != "true" ]; then
|
|
116
|
-
exit 0
|
|
117
|
-
fi
|
|
118
|
-
|
|
119
144
|
# ============================================================================
|
|
120
|
-
# CONFIRMATION GATE (
|
|
145
|
+
# CONFIRMATION GATE (hard block — consequential actions only)
|
|
121
146
|
# ============================================================================
|
|
122
147
|
# Block if recall() surfaced scars but confirm_scars() hasn't been called.
|
|
123
148
|
# Only blocks on recall-source scars; session_start scars don't require confirmation.
|
|
124
149
|
|
|
125
|
-
RECALL_SCAR_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
|
|
150
|
+
if [ "$IS_CONSEQUENTIAL" = "true" ] && [ "$RECALL_SCAR_COUNT" -gt 0 ] 2>/dev/null && [ "$CONFIRMATION_COUNT" -eq 0 ] 2>/dev/null; then
|
|
134
151
|
# Get scar titles for the error message
|
|
135
152
|
if command -v jq &>/dev/null; then
|
|
136
153
|
SCAR_TITLES=$(jq -r '[.surfaced_scars // [] | .[] | select(.source == "recall") | .scar_title] | join(", ")' "$SESSION_FILE" 2>/dev/null || echo "(unknown)")
|
|
@@ -152,24 +169,11 @@ HOOKJSON
|
|
|
152
169
|
fi
|
|
153
170
|
|
|
154
171
|
# ============================================================================
|
|
155
|
-
#
|
|
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
|
+
# RECALL NAG (soft reminder — ALL Bash/Write/Edit, not just consequential)
|
|
172
173
|
# ============================================================================
|
|
174
|
+
# UX audit: 12 sessions >30min with zero recalls because nag was gated behind
|
|
175
|
+
# consequential filter. Now fires for any code change after 10+ tool calls.
|
|
176
|
+
# Cooldown: 90s between nags. Never blocks — additionalContext only.
|
|
173
177
|
|
|
174
178
|
NOW=$(date +%s)
|
|
175
179
|
LAST_NAG=0
|
|
@@ -178,21 +182,16 @@ if [ -f "$STATE_DIR/last_nag_time" ]; then
|
|
|
178
182
|
fi
|
|
179
183
|
|
|
180
184
|
ELAPSED_SINCE_NAG=$((NOW - LAST_NAG))
|
|
181
|
-
if [ "$ELAPSED_SINCE_NAG" -lt
|
|
185
|
+
if [ "$ELAPSED_SINCE_NAG" -lt 90 ]; then
|
|
182
186
|
exit 0
|
|
183
187
|
fi
|
|
184
188
|
|
|
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
189
|
SHOULD_NAG=false
|
|
192
190
|
|
|
193
191
|
if [ "$RECALL_SCAR_COUNT" -eq 0 ] 2>/dev/null; then
|
|
194
|
-
# No recall scars found — recall
|
|
195
|
-
|
|
192
|
+
# No recall scars found — recall hasn't been called this session
|
|
193
|
+
# Threshold: 10 tool calls to avoid nagging during initial exploration
|
|
194
|
+
if [ "$TOOL_COUNT" -gt 10 ]; then
|
|
196
195
|
SHOULD_NAG=true
|
|
197
196
|
fi
|
|
198
197
|
fi
|
|
@@ -205,7 +204,7 @@ if [ "$SHOULD_NAG" = "true" ]; then
|
|
|
205
204
|
echo "$NOW" > "$STATE_DIR/last_nag_time"
|
|
206
205
|
cat <<'HOOKJSON'
|
|
207
206
|
{
|
|
208
|
-
"additionalContext": "GITMEM RECALL REMINDER: You'
|
|
207
|
+
"additionalContext": "GITMEM RECALL REMINDER: You've been working for a while without checking institutional memory. Consider calling `recall` (or `gitmem-r`) with your current plan — it surfaces relevant lessons from past sessions that may save time or prevent mistakes. Example: recall({ plan: \"what I'm about to do\" })"
|
|
209
208
|
}
|
|
210
209
|
HOOKJSON
|
|
211
210
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitmem-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Institutional memory for AI coding agents. Memory that compounds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"!hooks/tests",
|
|
56
56
|
"schema",
|
|
57
57
|
"CLAUDE.md.template",
|
|
58
|
+
"cursorrules.template",
|
|
58
59
|
"README.md",
|
|
59
60
|
"CHANGELOG.md"
|
|
60
61
|
],
|