gitmem-mcp 1.4.1 → 1.4.2

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 CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.2] - 2026-02-22
11
+
12
+ ### Fixed
13
+ - **Scar usage `execution_successful` nulls eliminated**: N_A confirmations now record `true` (was null/undefined). Q6 text matches now include `execution_successful: true` (was omitted). Fixes 80% null rate in scar effectiveness data.
14
+ - **Auto-bridge fires on all session closes**: Previously required Q6 `scars_applied` to be non-empty. Now fires whenever no explicit `scars_to_record` is provided, ensuring confirmations from `confirm_scars` always get recorded.
15
+ - **Surfaced scars survive MCP restart**: `getSurfacedScars()` now recovers from the active-sessions registry when `currentSession` is null after MCP restart. Scars surfaced early in a session are no longer silently lost.
16
+ - **Session close display shows scar titles**: `reference_context` now leads with the scar title instead of boilerplate. Display uses +/! indicators for applied/refuted scars.
17
+
10
18
  ## [1.4.1] - 2026-02-22
11
19
 
12
20
  ### Added
@@ -12,7 +12,9 @@
12
12
  * This allows recall() to always assign variants even without explicit parameters.
13
13
  */
14
14
  import fs from "fs";
15
+ import * as os from "os";
15
16
  import { getSessionPath } from "./gitmem-dir.js";
17
+ import { listActiveSessions } from "./active-sessions.js";
16
18
  // Global session state (single active session per MCP server instance)
17
19
  let currentSession = null;
18
20
  /**
@@ -105,7 +107,7 @@ export function getSurfacedScars() {
105
107
  if (currentSession?.surfacedScars && currentSession.surfacedScars.length > 0) {
106
108
  return currentSession.surfacedScars;
107
109
  }
108
- // Fallback: recover from per-session file if in-memory was lost (MCP restart)
110
+ // Fallback 1: recover from per-session file if in-memory was lost (MCP restart)
109
111
  if (currentSession?.sessionId) {
110
112
  try {
111
113
  const sessionFilePath = getSessionPath(currentSession.sessionId, "session.json");
@@ -122,6 +124,32 @@ export function getSurfacedScars() {
122
124
  console.warn("[session-state] Failed to recover surfaced scars from file:", error);
123
125
  }
124
126
  }
127
+ // Fallback 2: if currentSession is null (MCP restarted completely), find session from registry.
128
+ // After MCP restart the PID changes, but the active-sessions registry may still have
129
+ // the previous session entry (pruneStale adopts dead-PID sessions on same hostname).
130
+ if (!currentSession) {
131
+ try {
132
+ const hostname = os.hostname();
133
+ const sessions = listActiveSessions();
134
+ const candidates = sessions
135
+ .filter(s => s.hostname === hostname)
136
+ .sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
137
+ if (candidates.length > 0) {
138
+ const recoveredId = candidates[0].session_id;
139
+ const sessionFilePath = getSessionPath(recoveredId, "session.json");
140
+ if (fs.existsSync(sessionFilePath)) {
141
+ const data = JSON.parse(fs.readFileSync(sessionFilePath, "utf-8"));
142
+ if (data.surfaced_scars && Array.isArray(data.surfaced_scars) && data.surfaced_scars.length > 0) {
143
+ console.error(`[session-state] Recovered ${data.surfaced_scars.length} surfaced scars from registry session ${recoveredId.slice(0, 8)}`);
144
+ return data.surfaced_scars;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ catch (error) {
150
+ console.warn("[session-state] Failed to recover surfaced scars from registry:", error);
151
+ }
152
+ }
125
153
  return [];
126
154
  }
127
155
  /**
@@ -288,15 +288,12 @@ function formatCloseDisplay(sessionId, compliance, params, learningsCount, succe
288
288
  lines.push(` ${dimText("d")} ${truncate(d.title, 68)}`);
289
289
  }
290
290
  }
291
- // Scars applied — compact table, only if any were applied
291
+ // Scars applied — show titles with status indicator
292
292
  if (scarsApplied > 0) {
293
293
  lines.push("");
294
294
  for (const s of params.scars_to_record.filter(s => s.reference_type !== "none")) {
295
- const ref = s.reference_type === "explicit" ? "applied" :
296
- s.reference_type === "implicit" ? "implicit" :
297
- s.reference_type === "acknowledged" ? "ack'd" :
298
- s.reference_type === "refuted" ? `${ANSI.yellow}REFUTED${ANSI.reset}` : (s.reference_type || "?");
299
- lines.push(` ${dimText(ref.padEnd(8))} ${truncate(s.reference_context || "", 60)}`);
295
+ const indicator = s.reference_type === "refuted" ? `${ANSI.yellow}!${ANSI.reset}` : STATUS.pass;
296
+ lines.push(` ${indicator} ${truncate(s.reference_context || s.scar_identifier || "", 70)}`);
300
297
  }
301
298
  }
302
299
  // Transcript — only on failure
@@ -599,12 +596,13 @@ function bridgeScarsToUsageRecords(normalizedScarsApplied, sessionId, agentIdent
599
596
  if (reflection) {
600
597
  // Reflection provides definitive signal
601
598
  executionSuccessful = reflection.outcome === "OBEYED" ? true : false;
602
- context = `Confirmed: ${confirmation.decision} → Reflected: ${reflection.outcome} — ${reflection.evidence.slice(0, 80)}`;
599
+ context = `${scar.scar_title.slice(0, 60)} (${reflection.outcome})`;
603
600
  }
604
601
  else {
605
- // Fall back to confirmation-based default (Option A)
606
- executionSuccessful = confirmation.decision === "APPLYING" ? true : undefined;
607
- context = `Confirmed via confirm_scars: ${confirmation.decision} ${confirmation.evidence.slice(0, 100)}`;
602
+ // Fall back to confirmation-based default
603
+ // APPLYING/N_A = task proceeded normally (true), REFUTED = outcome unknown (null)
604
+ executionSuccessful = confirmation.decision === "REFUTED" ? undefined : true;
605
+ context = `${scar.scar_title.slice(0, 60)} (${confirmation.decision})`;
608
606
  }
609
607
  autoBridgedScars.push({
610
608
  scar_identifier: scar.scar_id,
@@ -636,7 +634,8 @@ function bridgeScarsToUsageRecords(normalizedScarsApplied, sessionId, agentIdent
636
634
  agent: agentIdentity,
637
635
  surfaced_at: match.surfaced_at,
638
636
  reference_type: "acknowledged",
639
- reference_context: `Auto-bridged from Q6 answer: "${scarApplied}"`,
637
+ reference_context: `${match.scar_title.slice(0, 60)} (Q6 match)`,
638
+ execution_successful: true,
640
639
  variant_id: match.variant_id,
641
640
  });
642
641
  }
@@ -651,7 +650,7 @@ function bridgeScarsToUsageRecords(normalizedScarsApplied, sessionId, agentIdent
651
650
  agent: agentIdentity,
652
651
  surfaced_at: scar.surfaced_at,
653
652
  reference_type: "none",
654
- reference_context: `Surfaced during ${scar.source} but not mentioned in closing reflection`,
653
+ reference_context: `${scar.scar_title.slice(0, 60)} (not addressed)`,
655
654
  execution_successful: false,
656
655
  variant_id: scar.variant_id,
657
656
  });
@@ -1046,8 +1045,9 @@ export async function sessionClose(params) {
1046
1045
  }
1047
1046
  // Auto-bridge Q6 answers to scar_usage records
1048
1047
  const normalizedScarsApplied = normalizeScarsApplied(params.closing_reflection?.scars_applied);
1049
- if ((!params.scars_to_record || params.scars_to_record.length === 0) &&
1050
- normalizedScarsApplied.length > 0) {
1048
+ // Auto-bridge surfaced scars to usage records whenever no explicit scars_to_record.
1049
+ // Fires even when Q6 is empty — Pass 1 matches confirmations, Pass 3 catches ignored scars.
1050
+ if (!params.scars_to_record || params.scars_to_record.length === 0) {
1051
1051
  const bridgedScars = bridgeScarsToUsageRecords(normalizedScarsApplied, sessionId, agentIdentity);
1052
1052
  if (bridgedScars.length > 0) {
1053
1053
  params = { ...params, scars_to_record: bridgedScars };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmem-mcp",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "mcpName": "io.github.gitmem-dev/gitmem",
5
5
  "description": "Persistent learning memory for AI coding agents. Memory that compounds.",
6
6
  "type": "module",