gitmem-mcp 1.4.2 → 1.4.3
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 +9 -0
- package/dist/services/metrics.js +4 -1
- package/dist/tools/session-close.js +55 -4
- package/dist/tools/session-start.js +11 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.3] - 2026-02-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **NULL agent values in query metrics eliminated**: `recordMetrics()` now auto-detects agent via `getAgentIdentity()` when callers don't provide it. Previously 15 of 18 tools omitted the agent field, resulting in NULL values in `gitmem_query_metrics`.
|
|
14
|
+
|
|
15
|
+
### Performance
|
|
16
|
+
- **session_start ~200-300ms faster**: Sessions and threads queries now run in parallel (`Promise.all`) instead of sequentially inside `loadLastSession`.
|
|
17
|
+
- **session_close transcript upload no longer blocks**: Transcript save moved from blocking `await` to fire-and-forget via effect tracker. Removes 500-5000ms variable cost from `latency_ms`. Claude session ID extraction remains synchronous.
|
|
18
|
+
|
|
10
19
|
## [1.4.2] - 2026-02-22
|
|
11
20
|
|
|
12
21
|
### Fixed
|
package/dist/services/metrics.js
CHANGED
|
@@ -8,6 +8,7 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
8
8
|
import * as supabase from "./supabase-client.js";
|
|
9
9
|
import { getEffectTracker } from "./effect-tracker.js";
|
|
10
10
|
import { hasSupabase } from "./tier.js";
|
|
11
|
+
import { getAgentIdentity } from "./agent-detection.js";
|
|
11
12
|
/**
|
|
12
13
|
* Performance targets
|
|
13
14
|
*/
|
|
@@ -64,10 +65,12 @@ export class Timer {
|
|
|
64
65
|
export async function recordMetrics(metrics) {
|
|
65
66
|
if (!hasSupabase())
|
|
66
67
|
return; // No-op on free tier — don't record failures
|
|
68
|
+
// Auto-detect agent if not provided by caller
|
|
69
|
+
const agent = metrics.agent || getAgentIdentity() || null;
|
|
67
70
|
const record = {
|
|
68
71
|
id: metrics.id,
|
|
69
72
|
session_id: metrics.session_id || null,
|
|
70
|
-
agent
|
|
73
|
+
agent,
|
|
71
74
|
tool_name: metrics.tool_name,
|
|
72
75
|
query_text: metrics.query_text || null,
|
|
73
76
|
tables_searched: metrics.tables_searched || null,
|
|
@@ -53,6 +53,30 @@ function normalizeScarsApplied(scarsApplied) {
|
|
|
53
53
|
function countScarsApplied(scarsApplied) {
|
|
54
54
|
return normalizeScarsApplied(scarsApplied).length;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Find transcript file path: explicit param or auto-detect from Claude Code projects dir
|
|
58
|
+
*/
|
|
59
|
+
function findTranscriptPath(explicitPath) {
|
|
60
|
+
if (explicitPath) {
|
|
61
|
+
if (fs.existsSync(explicitPath)) {
|
|
62
|
+
console.error(`[session_close] Using explicit transcript path: ${explicitPath}`);
|
|
63
|
+
return explicitPath;
|
|
64
|
+
}
|
|
65
|
+
console.warn(`[session_close] Explicit transcript path does not exist: ${explicitPath}`);
|
|
66
|
+
}
|
|
67
|
+
const homeDir = os.homedir();
|
|
68
|
+
const projectsDir = path.join(homeDir, ".claude", "projects");
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const projectDirName = path.basename(cwd);
|
|
71
|
+
const found = findMostRecentTranscript(projectsDir, projectDirName, cwd);
|
|
72
|
+
if (found) {
|
|
73
|
+
console.error(`[session_close] Auto-detected transcript: ${found}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error(`[session_close] No transcript file found in ${projectsDir}`);
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
56
80
|
/**
|
|
57
81
|
* Find the most recently modified transcript file in Claude Code projects directory
|
|
58
82
|
* Search by recency, not by filename matching (supports post-compaction)
|
|
@@ -1033,14 +1057,41 @@ export async function sessionClose(params) {
|
|
|
1033
1057
|
console.error("[session_close] Failed to prune threads.json (non-fatal):", err);
|
|
1034
1058
|
}
|
|
1035
1059
|
// Capture transcript if enabled (default true for CLI/DAC)
|
|
1060
|
+
// Split into two phases: sync ID extraction (fast) + async upload (fire-and-forget)
|
|
1036
1061
|
let transcriptStatus;
|
|
1037
1062
|
const shouldCaptureTranscript = params.capture_transcript !== false &&
|
|
1038
1063
|
(agentIdentity === "cli" || agentIdentity === "desktop");
|
|
1039
1064
|
if (shouldCaptureTranscript) {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
if (
|
|
1043
|
-
|
|
1065
|
+
// Phase 1: Find transcript and extract Claude session ID (sync, ~10ms)
|
|
1066
|
+
const transcriptFilePath = findTranscriptPath(params.transcript_path);
|
|
1067
|
+
if (transcriptFilePath) {
|
|
1068
|
+
const transcriptContent = fs.readFileSync(transcriptFilePath, "utf-8");
|
|
1069
|
+
const claudeSessionId = extractClaudeSessionId(transcriptContent, transcriptFilePath) || undefined;
|
|
1070
|
+
if (claudeSessionId) {
|
|
1071
|
+
sessionData.claude_code_session_id = claudeSessionId;
|
|
1072
|
+
console.error(`[session_close] Extracted Claude session ID: ${claudeSessionId}`);
|
|
1073
|
+
}
|
|
1074
|
+
// Phase 2: Upload transcript (fire-and-forget — was blocking ~500-5000ms)
|
|
1075
|
+
const transcriptProject = isRetroactive ? "default" : existingSession?.project;
|
|
1076
|
+
getEffectTracker().track("transcript", "session_close", async () => {
|
|
1077
|
+
const saveResult = await saveTranscript({
|
|
1078
|
+
session_id: sessionId,
|
|
1079
|
+
transcript: transcriptContent,
|
|
1080
|
+
format: "json",
|
|
1081
|
+
project: transcriptProject,
|
|
1082
|
+
});
|
|
1083
|
+
if (saveResult.success && saveResult.transcript_path) {
|
|
1084
|
+
console.error(`[session_close] Transcript saved: ${saveResult.transcript_path} (${saveResult.size_kb}KB)`);
|
|
1085
|
+
// Process transcript for semantic search (chained fire-and-forget)
|
|
1086
|
+
processTranscript(sessionId, transcriptContent, transcriptProject)
|
|
1087
|
+
.then(result => {
|
|
1088
|
+
if (result.success) {
|
|
1089
|
+
console.error(`[session_close] Transcript processed: ${result.chunksCreated} chunks`);
|
|
1090
|
+
}
|
|
1091
|
+
})
|
|
1092
|
+
.catch((err) => console.error("[session_close] Transcript processing failed:", err instanceof Error ? err.message : err));
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1044
1095
|
}
|
|
1045
1096
|
}
|
|
1046
1097
|
// Auto-bridge Q6 answers to scar_usage records
|
|
@@ -101,19 +101,20 @@ async function loadLastSession(agent, project) {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
try {
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
const sessions = await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
// Parallel load: sessions + threads are independent queries
|
|
105
|
+
// (was sequential — ~200-300ms saved by parallelizing)
|
|
106
|
+
const [sessions, supabaseThreads] = await Promise.all([
|
|
107
|
+
supabase.listRecords({
|
|
108
|
+
table: getTableName("sessions_lite"),
|
|
109
|
+
filters: { agent, project },
|
|
110
|
+
limit: 10,
|
|
111
|
+
orderBy: { column: "created_at", ascending: false },
|
|
112
|
+
}),
|
|
113
|
+
loadActiveThreadsFromSupabase(project),
|
|
114
|
+
]);
|
|
113
115
|
let aggregated_open_threads;
|
|
114
116
|
let displayInfo = [];
|
|
115
117
|
let threadsFromSupabase = false;
|
|
116
|
-
const supabaseThreads = await loadActiveThreadsFromSupabase(project);
|
|
117
118
|
if (supabaseThreads !== null) {
|
|
118
119
|
// Supabase is source of truth for threads
|
|
119
120
|
aggregated_open_threads = supabaseThreads.open;
|