gitmem-mcp 1.4.0 → 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 +13 -0
- package/bin/init-wizard.js +48 -0
- package/dist/services/session-state.js +29 -1
- package/dist/tools/session-close.js +14 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ 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
|
+
|
|
18
|
+
## [1.4.1] - 2026-02-22
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **AGENTS.md generation**: Init wizard now creates an IDE-agnostic `AGENTS.md` file alongside the client-specific instructions file. Contains tool table, core workflow, sub-agent patterns (`prepare_context`, `absorb_observations`), and example JSON tool calls. Read by Codex, Copilot, Gemini, Cursor, and other AI coding assistants for automatic project discovery.
|
|
22
|
+
|
|
10
23
|
## [1.4.0] - 2026-02-22
|
|
11
24
|
|
|
12
25
|
### Changed
|
package/bin/init-wizard.js
CHANGED
|
@@ -879,6 +879,51 @@ async function stepGitignore() {
|
|
|
879
879
|
return { done: true };
|
|
880
880
|
}
|
|
881
881
|
|
|
882
|
+
async function stepAgentsMd() {
|
|
883
|
+
const agentsPath = join(cwd, "AGENTS.md");
|
|
884
|
+
const templatePath = join(__dirname, "..", "AGENTS.md.template");
|
|
885
|
+
|
|
886
|
+
// Already exists — don't overwrite
|
|
887
|
+
if (existsSync(agentsPath)) {
|
|
888
|
+
const content = readFileSync(agentsPath, "utf-8");
|
|
889
|
+
if (content.includes("GitMem")) {
|
|
890
|
+
log(CHECK, `AGENTS.md already includes gitmem`);
|
|
891
|
+
return { done: false };
|
|
892
|
+
}
|
|
893
|
+
// Existing AGENTS.md without gitmem — ask before appending
|
|
894
|
+
if (interactive) {
|
|
895
|
+
if (!(await confirm("Add gitmem section to existing AGENTS.md?"))) {
|
|
896
|
+
log(SKIP, "AGENTS.md skipped");
|
|
897
|
+
return { done: false };
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
let template;
|
|
903
|
+
try {
|
|
904
|
+
template = readFileSync(templatePath, "utf-8");
|
|
905
|
+
} catch {
|
|
906
|
+
// Template not found — skip silently
|
|
907
|
+
return { done: false };
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (dryRun) {
|
|
911
|
+
log(CHECK, `Would ${existsSync(agentsPath) ? "update" : "create"} AGENTS.md`, "[dry-run]");
|
|
912
|
+
return { done: true };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (existsSync(agentsPath)) {
|
|
916
|
+
const existing = readFileSync(agentsPath, "utf-8");
|
|
917
|
+
writeFileSync(agentsPath, existing.trimEnd() + "\n\n" + template + "\n");
|
|
918
|
+
log(CHECK, "Updated AGENTS.md", "Added gitmem section (your existing content is preserved)");
|
|
919
|
+
} else {
|
|
920
|
+
writeFileSync(agentsPath, template + "\n");
|
|
921
|
+
log(CHECK, "Created AGENTS.md", "IDE-agnostic agent discovery file");
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return { done: true };
|
|
925
|
+
}
|
|
926
|
+
|
|
882
927
|
async function stepFeedbackOptIn() {
|
|
883
928
|
const configPath = join(gitmemDir, "config.json");
|
|
884
929
|
const config = readJson(configPath) || {};
|
|
@@ -980,6 +1025,9 @@ async function main() {
|
|
|
980
1025
|
const r6 = await stepGitignore();
|
|
981
1026
|
if (r6.done) configured++;
|
|
982
1027
|
|
|
1028
|
+
const r6b = await stepAgentsMd();
|
|
1029
|
+
if (r6b.done) configured++;
|
|
1030
|
+
|
|
983
1031
|
const r7 = await stepFeedbackOptIn();
|
|
984
1032
|
if (r7.done) configured++;
|
|
985
1033
|
|
|
@@ -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 —
|
|
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
|
|
296
|
-
|
|
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 =
|
|
599
|
+
context = `${scar.scar_title.slice(0, 60)} (${reflection.outcome})`;
|
|
603
600
|
}
|
|
604
601
|
else {
|
|
605
|
-
// Fall back to confirmation-based default
|
|
606
|
-
|
|
607
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1050
|
-
|
|
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 };
|