gitmem-mcp 1.1.2 → 1.1.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 +5 -0
- package/README.md +32 -16
- package/bin/init-wizard.js +147 -21
- package/copilot-instructions.template +82 -0
- package/dist/schemas/session-close.d.ts +26 -26
- package/dist/server.js +13 -3
- package/dist/services/analytics.js +1 -1
- package/dist/services/enforcement.d.ts +24 -0
- package/dist/services/enforcement.js +126 -0
- package/dist/tools/cleanup-threads.js +7 -3
- package/dist/tools/definitions.js +5 -5
- package/dist/tools/list-threads.js +6 -14
- package/dist/tools/log.js +1 -1
- package/dist/tools/record-scar-usage-batch.js +7 -2
- package/dist/tools/record-scar-usage.js +7 -2
- package/dist/tools/session-start.js +49 -5
- package/dist/types/index.d.ts +2 -0
- package/package.json +13 -3
- package/windsurfrules.template +82 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.1.3] - 2026-02-19
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Thread display output**: `list_threads` and `cleanup_threads` replaced ASCII box-drawing tables with markdown tables. Thread text truncation increased from 40-48 to 60 characters. Output now renders cleanly in all MCP clients instead of clipping on narrow terminals.
|
|
14
|
+
|
|
10
15
|
## [1.1.2] - 2026-02-17
|
|
11
16
|
|
|
12
17
|
### Changed
|
package/README.md
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
|
|
22
22
|
GitMem is an [MCP server](https://modelcontextprotocol.io/) that gives your AI coding agent **persistent memory across sessions**. It remembers mistakes (scars), successes (wins), and decisions — so your agent learns from experience instead of starting from scratch every time.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
> **What's MCP?** [Model Context Protocol](https://modelcontextprotocol.io/) is how AI coding tools connect to external capabilities. GitMem is an MCP server — install it once and your agent gains persistent memory.
|
|
25
|
+
|
|
26
|
+
Works with **Claude Code**, **Cursor**, **VS Code (Copilot)**, **Windsurf**, and any MCP-compatible client.
|
|
25
27
|
|
|
26
28
|
## Quick Start
|
|
27
29
|
|
|
@@ -29,19 +31,19 @@ Works with **Claude Code**, **Claude Desktop**, **Cursor**, and any MCP-compatib
|
|
|
29
31
|
npx gitmem-mcp init
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
One command. The wizard sets up everything:
|
|
33
|
-
- `.gitmem/` directory with
|
|
34
|
-
- `.mcp.json
|
|
35
|
-
- `CLAUDE.md
|
|
36
|
-
-
|
|
37
|
-
- Lifecycle hooks for automatic session management
|
|
34
|
+
One command. The wizard auto-detects your IDE and sets up everything:
|
|
35
|
+
- `.gitmem/` directory with starter scars
|
|
36
|
+
- MCP server config (`.mcp.json`, `.vscode/mcp.json`, `.cursor/mcp.json`, etc.)
|
|
37
|
+
- Instructions file (`CLAUDE.md`, `.cursorrules`, `.windsurfrules`, `.github/copilot-instructions.md`)
|
|
38
|
+
- Lifecycle hooks (where supported)
|
|
38
39
|
- `.gitignore` updated
|
|
39
40
|
|
|
40
41
|
Already have existing config? The wizard merges without destroying anything. Re-running is safe.
|
|
41
42
|
|
|
42
43
|
```bash
|
|
43
|
-
npx gitmem-mcp init --yes
|
|
44
|
-
npx gitmem-mcp init --dry-run
|
|
44
|
+
npx gitmem-mcp init --yes # Non-interactive
|
|
45
|
+
npx gitmem-mcp init --dry-run # Preview changes
|
|
46
|
+
npx gitmem-mcp init --client vscode # Force specific client
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
## How It Works
|
|
@@ -78,16 +80,22 @@ Every scar includes **counter-arguments** — reasons why someone might reasonab
|
|
|
78
80
|
|
|
79
81
|
## Supported Clients
|
|
80
82
|
|
|
81
|
-
| Client | Setup |
|
|
82
|
-
|
|
83
|
-
| **Claude Code** | `npx gitmem-mcp init` (
|
|
84
|
-
| **
|
|
85
|
-
| **
|
|
86
|
-
| **
|
|
83
|
+
| Client | Setup | Hooks |
|
|
84
|
+
|--------|-------|-------|
|
|
85
|
+
| **Claude Code** | `npx gitmem-mcp init` | Full (session, recall, credential guard) |
|
|
86
|
+
| **Cursor** | `npx gitmem-mcp init --client cursor` | Partial (session, recall) |
|
|
87
|
+
| **VS Code (Copilot)** | `npx gitmem-mcp init --client vscode` | Instructions-based |
|
|
88
|
+
| **Windsurf** | `npx gitmem-mcp init --client windsurf` | Instructions-based |
|
|
89
|
+
| **Claude Desktop** | Add to `claude_desktop_config.json` | Manual |
|
|
90
|
+
| **Any MCP client** | `npx gitmem-mcp init --client generic` | Instructions-based |
|
|
91
|
+
|
|
92
|
+
The wizard auto-detects your IDE. Use `--client` to override.
|
|
87
93
|
|
|
88
94
|
<details>
|
|
89
95
|
<summary><strong>Manual MCP configuration</strong></summary>
|
|
90
96
|
|
|
97
|
+
Add this to your MCP client's config file:
|
|
98
|
+
|
|
91
99
|
```json
|
|
92
100
|
{
|
|
93
101
|
"mcpServers": {
|
|
@@ -99,13 +107,21 @@ Every scar includes **counter-arguments** — reasons why someone might reasonab
|
|
|
99
107
|
}
|
|
100
108
|
```
|
|
101
109
|
|
|
110
|
+
| Client | Config file |
|
|
111
|
+
|--------|-------------|
|
|
112
|
+
| Claude Code | `.mcp.json` |
|
|
113
|
+
| Cursor | `.cursor/mcp.json` |
|
|
114
|
+
| VS Code | `.vscode/mcp.json` |
|
|
115
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
116
|
+
|
|
102
117
|
</details>
|
|
103
118
|
|
|
104
119
|
## CLI Commands
|
|
105
120
|
|
|
106
121
|
| Command | Description |
|
|
107
122
|
|---------|-------------|
|
|
108
|
-
| `npx gitmem-mcp init` | Interactive setup wizard |
|
|
123
|
+
| `npx gitmem-mcp init` | Interactive setup wizard (auto-detects IDE) |
|
|
124
|
+
| `npx gitmem-mcp init --client <name>` | Setup for specific client (`claude`, `cursor`, `vscode`, `windsurf`, `generic`) |
|
|
109
125
|
| `npx gitmem-mcp init --yes` | Non-interactive setup |
|
|
110
126
|
| `npx gitmem-mcp init --dry-run` | Preview changes |
|
|
111
127
|
| `npx gitmem-mcp uninstall` | Clean removal (preserves `.gitmem/` data) |
|
package/bin/init-wizard.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Interactive setup that detects existing config, prompts, and merges.
|
|
7
7
|
* Supports Claude Code and Cursor IDE.
|
|
8
8
|
*
|
|
9
|
-
* Usage: npx gitmem-mcp init [--yes] [--dry-run] [--project <name>] [--client <claude|cursor>]
|
|
9
|
+
* Usage: npx gitmem-mcp init [--yes] [--dry-run] [--project <name>] [--client <claude|cursor|vscode|windsurf|generic>]
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {
|
|
@@ -35,11 +35,15 @@ const clientFlag = clientIdx !== -1 ? args[clientIdx + 1]?.toLowerCase() : null;
|
|
|
35
35
|
|
|
36
36
|
// ── Client Configuration ──
|
|
37
37
|
|
|
38
|
+
// Resolve user home directory for clients that use user-level config
|
|
39
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
|
|
40
|
+
|
|
38
41
|
const CLIENT_CONFIGS = {
|
|
39
42
|
claude: {
|
|
40
43
|
name: "Claude Code",
|
|
41
44
|
mcpConfigPath: join(cwd, ".mcp.json"),
|
|
42
45
|
mcpConfigName: ".mcp.json",
|
|
46
|
+
mcpConfigScope: "project",
|
|
43
47
|
instructionsFile: join(cwd, "CLAUDE.md"),
|
|
44
48
|
instructionsName: "CLAUDE.md",
|
|
45
49
|
templateFile: join(__dirname, "..", "CLAUDE.md.template"),
|
|
@@ -50,12 +54,14 @@ const CLIENT_CONFIGS = {
|
|
|
50
54
|
settingsLocalFile: join(cwd, ".claude", "settings.local.json"),
|
|
51
55
|
hasPermissions: true,
|
|
52
56
|
hooksInSettings: true,
|
|
57
|
+
hasHooks: true,
|
|
53
58
|
completionMsg: "Setup complete! Start Claude Code \u2014 memory is active.",
|
|
54
59
|
},
|
|
55
60
|
cursor: {
|
|
56
61
|
name: "Cursor",
|
|
57
62
|
mcpConfigPath: join(cwd, ".cursor", "mcp.json"),
|
|
58
63
|
mcpConfigName: ".cursor/mcp.json",
|
|
64
|
+
mcpConfigScope: "project",
|
|
59
65
|
instructionsFile: join(cwd, ".cursorrules"),
|
|
60
66
|
instructionsName: ".cursorrules",
|
|
61
67
|
templateFile: join(__dirname, "..", "cursorrules.template"),
|
|
@@ -66,10 +72,66 @@ const CLIENT_CONFIGS = {
|
|
|
66
72
|
settingsLocalFile: null,
|
|
67
73
|
hasPermissions: false,
|
|
68
74
|
hooksInSettings: false,
|
|
75
|
+
hasHooks: true,
|
|
69
76
|
hooksFile: join(cwd, ".cursor", "hooks.json"),
|
|
70
77
|
hooksFileName: ".cursor/hooks.json",
|
|
71
78
|
completionMsg: "Setup complete! Open Cursor (Agent mode) \u2014 memory is active.",
|
|
72
79
|
},
|
|
80
|
+
vscode: {
|
|
81
|
+
name: "VS Code (Copilot)",
|
|
82
|
+
mcpConfigPath: join(cwd, ".vscode", "mcp.json"),
|
|
83
|
+
mcpConfigName: ".vscode/mcp.json",
|
|
84
|
+
mcpConfigScope: "project",
|
|
85
|
+
instructionsFile: join(cwd, ".github", "copilot-instructions.md"),
|
|
86
|
+
instructionsName: ".github/copilot-instructions.md",
|
|
87
|
+
templateFile: join(__dirname, "..", "copilot-instructions.template"),
|
|
88
|
+
startMarker: "<!-- gitmem:start -->",
|
|
89
|
+
endMarker: "<!-- gitmem:end -->",
|
|
90
|
+
configDir: join(cwd, ".vscode"),
|
|
91
|
+
settingsFile: null,
|
|
92
|
+
settingsLocalFile: null,
|
|
93
|
+
hasPermissions: false,
|
|
94
|
+
hooksInSettings: false,
|
|
95
|
+
hasHooks: false,
|
|
96
|
+
completionMsg: "Setup complete! Open VS Code \u2014 memory is active via Copilot.",
|
|
97
|
+
},
|
|
98
|
+
windsurf: {
|
|
99
|
+
name: "Windsurf",
|
|
100
|
+
mcpConfigPath: join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
|
|
101
|
+
mcpConfigName: "~/.codeium/windsurf/mcp_config.json",
|
|
102
|
+
mcpConfigScope: "user",
|
|
103
|
+
instructionsFile: join(cwd, ".windsurfrules"),
|
|
104
|
+
instructionsName: ".windsurfrules",
|
|
105
|
+
templateFile: join(__dirname, "..", "windsurfrules.template"),
|
|
106
|
+
startMarker: "# --- gitmem:start ---",
|
|
107
|
+
endMarker: "# --- gitmem:end ---",
|
|
108
|
+
configDir: null,
|
|
109
|
+
settingsFile: null,
|
|
110
|
+
settingsLocalFile: null,
|
|
111
|
+
hasPermissions: false,
|
|
112
|
+
hooksInSettings: false,
|
|
113
|
+
hasHooks: false,
|
|
114
|
+
completionMsg: "Setup complete! Open Windsurf \u2014 memory is active.",
|
|
115
|
+
},
|
|
116
|
+
generic: {
|
|
117
|
+
name: "Generic MCP Client",
|
|
118
|
+
mcpConfigPath: join(cwd, ".mcp.json"),
|
|
119
|
+
mcpConfigName: ".mcp.json",
|
|
120
|
+
mcpConfigScope: "project",
|
|
121
|
+
instructionsFile: join(cwd, "CLAUDE.md"),
|
|
122
|
+
instructionsName: "CLAUDE.md",
|
|
123
|
+
templateFile: join(__dirname, "..", "CLAUDE.md.template"),
|
|
124
|
+
startMarker: "<!-- gitmem:start -->",
|
|
125
|
+
endMarker: "<!-- gitmem:end -->",
|
|
126
|
+
configDir: null,
|
|
127
|
+
settingsFile: null,
|
|
128
|
+
settingsLocalFile: null,
|
|
129
|
+
hasPermissions: false,
|
|
130
|
+
hooksInSettings: false,
|
|
131
|
+
hasHooks: false,
|
|
132
|
+
completionMsg:
|
|
133
|
+
"Setup complete! Configure your MCP client to use the gitmem server from .mcp.json.",
|
|
134
|
+
},
|
|
73
135
|
};
|
|
74
136
|
|
|
75
137
|
// Shared paths (client-agnostic)
|
|
@@ -84,33 +146,49 @@ let cc; // shorthand for CLIENT_CONFIGS[client]
|
|
|
84
146
|
|
|
85
147
|
// ── Client Detection ──
|
|
86
148
|
|
|
149
|
+
const VALID_CLIENTS = Object.keys(CLIENT_CONFIGS);
|
|
150
|
+
|
|
87
151
|
function detectClient() {
|
|
88
152
|
// Explicit flag takes priority
|
|
89
153
|
if (clientFlag) {
|
|
90
|
-
if (clientFlag
|
|
91
|
-
console.error(` Error: Unknown client "${clientFlag}". Use --client
|
|
154
|
+
if (!VALID_CLIENTS.includes(clientFlag)) {
|
|
155
|
+
console.error(` Error: Unknown client "${clientFlag}". Use --client ${VALID_CLIENTS.join("|")}.`);
|
|
92
156
|
process.exit(1);
|
|
93
157
|
}
|
|
94
158
|
return clientFlag;
|
|
95
159
|
}
|
|
96
160
|
|
|
97
|
-
// Auto-detect based on directory presence
|
|
161
|
+
// Auto-detect based on directory/file presence
|
|
98
162
|
const hasCursorDir = existsSync(join(cwd, ".cursor"));
|
|
99
163
|
const hasClaudeDir = existsSync(join(cwd, ".claude"));
|
|
100
164
|
const hasMcpJson = existsSync(join(cwd, ".mcp.json"));
|
|
101
165
|
const hasClaudeMd = existsSync(join(cwd, "CLAUDE.md"));
|
|
102
166
|
const hasCursorRules = existsSync(join(cwd, ".cursorrules"));
|
|
103
167
|
const hasCursorMcp = existsSync(join(cwd, ".cursor", "mcp.json"));
|
|
168
|
+
const hasVscodeDir = existsSync(join(cwd, ".vscode"));
|
|
169
|
+
const hasVscodeMcp = existsSync(join(cwd, ".vscode", "mcp.json"));
|
|
170
|
+
const hasCopilotInstructions = existsSync(join(cwd, ".github", "copilot-instructions.md"));
|
|
171
|
+
const hasWindsurfRules = existsSync(join(cwd, ".windsurfrules"));
|
|
172
|
+
const hasWindsurfMcp = existsSync(
|
|
173
|
+
join(homeDir, ".codeium", "windsurf", "mcp_config.json")
|
|
174
|
+
);
|
|
104
175
|
|
|
105
176
|
// Strong Cursor signals
|
|
106
177
|
if (hasCursorDir && !hasClaudeDir && !hasMcpJson && !hasClaudeMd) return "cursor";
|
|
107
|
-
if (hasCursorRules && !hasClaudeMd) return "cursor";
|
|
108
|
-
if (hasCursorMcp && !hasMcpJson) return "cursor";
|
|
178
|
+
if (hasCursorRules && !hasClaudeMd && !hasCopilotInstructions) return "cursor";
|
|
179
|
+
if (hasCursorMcp && !hasMcpJson && !hasVscodeMcp) return "cursor";
|
|
109
180
|
|
|
110
181
|
// Strong Claude signals
|
|
111
|
-
if (hasClaudeDir && !hasCursorDir) return "claude";
|
|
112
|
-
if (hasMcpJson && !hasCursorMcp) return "claude";
|
|
113
|
-
if (hasClaudeMd && !hasCursorRules) return "claude";
|
|
182
|
+
if (hasClaudeDir && !hasCursorDir && !hasVscodeDir) return "claude";
|
|
183
|
+
if (hasMcpJson && !hasCursorMcp && !hasVscodeMcp) return "claude";
|
|
184
|
+
if (hasClaudeMd && !hasCursorRules && !hasCopilotInstructions) return "claude";
|
|
185
|
+
|
|
186
|
+
// VS Code signals
|
|
187
|
+
if (hasVscodeMcp && !hasMcpJson && !hasCursorMcp) return "vscode";
|
|
188
|
+
if (hasCopilotInstructions && !hasClaudeMd && !hasCursorRules) return "vscode";
|
|
189
|
+
|
|
190
|
+
// Windsurf signals
|
|
191
|
+
if (hasWindsurfRules && !hasClaudeMd && !hasCursorRules && !hasCopilotInstructions) return "windsurf";
|
|
114
192
|
|
|
115
193
|
// Default to Claude Code (most common)
|
|
116
194
|
return "claude";
|
|
@@ -428,6 +506,36 @@ async function stepMemoryStore() {
|
|
|
428
506
|
}
|
|
429
507
|
}
|
|
430
508
|
|
|
509
|
+
// Closing payload template — agents read this before writing closing-payload.json
|
|
510
|
+
const templatePath = join(gitmemDir, "closing-payload-template.json");
|
|
511
|
+
if (!existsSync(templatePath)) {
|
|
512
|
+
writeJson(templatePath, {
|
|
513
|
+
closing_reflection: {
|
|
514
|
+
what_broke: "",
|
|
515
|
+
what_took_longer: "",
|
|
516
|
+
do_differently: "",
|
|
517
|
+
what_worked: "",
|
|
518
|
+
wrong_assumption: "",
|
|
519
|
+
scars_applied: [],
|
|
520
|
+
institutional_memory_items: "",
|
|
521
|
+
collaborative_dynamic: "",
|
|
522
|
+
rapport_notes: ""
|
|
523
|
+
},
|
|
524
|
+
task_completion: {
|
|
525
|
+
questions_displayed_at: "ISO-8601 timestamp",
|
|
526
|
+
reflection_completed_at: "ISO-8601 timestamp",
|
|
527
|
+
human_asked_at: "ISO-8601 timestamp",
|
|
528
|
+
human_response_at: "ISO-8601 timestamp",
|
|
529
|
+
human_response: "no corrections | actual corrections text"
|
|
530
|
+
},
|
|
531
|
+
human_corrections: "",
|
|
532
|
+
scars_to_record: [],
|
|
533
|
+
learnings_created: [],
|
|
534
|
+
open_threads: [],
|
|
535
|
+
decisions: []
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
431
539
|
console.log(
|
|
432
540
|
` Created .gitmem/ with ${starterScars.length} starter scars` +
|
|
433
541
|
(added < starterScars.length
|
|
@@ -439,6 +547,7 @@ async function stepMemoryStore() {
|
|
|
439
547
|
async function stepMcpServer() {
|
|
440
548
|
const mcpPath = cc.mcpConfigPath;
|
|
441
549
|
const mcpName = cc.mcpConfigName;
|
|
550
|
+
const isUserLevel = cc.mcpConfigScope === "user";
|
|
442
551
|
|
|
443
552
|
const existing = readJson(mcpPath);
|
|
444
553
|
const hasGitmem =
|
|
@@ -453,9 +562,10 @@ async function stepMcpServer() {
|
|
|
453
562
|
? Object.keys(existing.mcpServers).length
|
|
454
563
|
: 0;
|
|
455
564
|
const tierLabel = process.env.SUPABASE_URL ? "pro" : "free";
|
|
565
|
+
const scopeNote = isUserLevel ? " (user-level config)" : "";
|
|
456
566
|
const prompt = existing
|
|
457
|
-
? `Add gitmem to ${mcpName}
|
|
458
|
-
: `Create ${mcpName} with gitmem server
|
|
567
|
+
? `Add gitmem to ${mcpName}?${scopeNote} (${serverCount} existing server${serverCount !== 1 ? "s" : ""} preserved)`
|
|
568
|
+
: `Create ${mcpName} with gitmem server?${scopeNote}`;
|
|
459
569
|
|
|
460
570
|
if (!(await confirm(prompt))) {
|
|
461
571
|
console.log(" Skipped.");
|
|
@@ -463,11 +573,11 @@ async function stepMcpServer() {
|
|
|
463
573
|
}
|
|
464
574
|
|
|
465
575
|
if (dryRun) {
|
|
466
|
-
console.log(` [dry-run] Would add gitmem entry to ${mcpName} (${tierLabel} tier)`);
|
|
576
|
+
console.log(` [dry-run] Would add gitmem entry to ${mcpName} (${tierLabel} tier${scopeNote})`);
|
|
467
577
|
return;
|
|
468
578
|
}
|
|
469
579
|
|
|
470
|
-
// Ensure parent directory exists (for .cursor/mcp.json)
|
|
580
|
+
// Ensure parent directory exists (for .cursor/mcp.json, .vscode/mcp.json, ~/.codeium/windsurf/)
|
|
471
581
|
const parentDir = dirname(mcpPath);
|
|
472
582
|
if (!existsSync(parentDir)) {
|
|
473
583
|
mkdirSync(parentDir, { recursive: true });
|
|
@@ -481,7 +591,8 @@ async function stepMcpServer() {
|
|
|
481
591
|
console.log(
|
|
482
592
|
` Added gitmem entry to ${mcpName} (${tierLabel} tier` +
|
|
483
593
|
(process.env.SUPABASE_URL ? " \u2014 Supabase detected" : " \u2014 local storage") +
|
|
484
|
-
")"
|
|
594
|
+
")" +
|
|
595
|
+
(isUserLevel ? " [user-level]" : "")
|
|
485
596
|
);
|
|
486
597
|
}
|
|
487
598
|
|
|
@@ -525,6 +636,12 @@ async function stepInstructions() {
|
|
|
525
636
|
block = `${cc.startMarker}\n${block}\n${cc.endMarker}`;
|
|
526
637
|
}
|
|
527
638
|
|
|
639
|
+
// Ensure parent directory exists (for .github/copilot-instructions.md)
|
|
640
|
+
const instrParentDir = dirname(instrPath);
|
|
641
|
+
if (!existsSync(instrParentDir)) {
|
|
642
|
+
mkdirSync(instrParentDir, { recursive: true });
|
|
643
|
+
}
|
|
644
|
+
|
|
528
645
|
if (exists) {
|
|
529
646
|
content = content.trimEnd() + "\n\n" + block + "\n";
|
|
530
647
|
} else {
|
|
@@ -598,6 +715,11 @@ function copyHookScripts() {
|
|
|
598
715
|
}
|
|
599
716
|
|
|
600
717
|
async function stepHooks() {
|
|
718
|
+
if (!cc.hasHooks) {
|
|
719
|
+
console.log(` ${cc.name} does not support lifecycle hooks. Skipping.`);
|
|
720
|
+
console.log(" Enforcement relies on system prompt instructions instead.");
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
601
723
|
if (cc.hooksInSettings) {
|
|
602
724
|
return stepHooksClaude();
|
|
603
725
|
}
|
|
@@ -821,7 +943,7 @@ async function main() {
|
|
|
821
943
|
);
|
|
822
944
|
}
|
|
823
945
|
|
|
824
|
-
if (!cc.hooksInSettings && cc.hooksFile && existsSync(cc.hooksFile)) {
|
|
946
|
+
if (!cc.hooksInSettings && cc.hasHooks && cc.hooksFile && existsSync(cc.hooksFile)) {
|
|
825
947
|
const hooks = readJson(cc.hooksFile);
|
|
826
948
|
const hookCount = hooks?.hooks
|
|
827
949
|
? Object.values(hooks.hooks).flat().length
|
|
@@ -850,8 +972,10 @@ async function main() {
|
|
|
850
972
|
);
|
|
851
973
|
console.log("");
|
|
852
974
|
|
|
853
|
-
// Run steps — step count depends on client
|
|
854
|
-
|
|
975
|
+
// Run steps — step count depends on client capabilities
|
|
976
|
+
let stepCount = 4; // memory store + mcp server + instructions + gitignore
|
|
977
|
+
if (cc.hasPermissions) stepCount++;
|
|
978
|
+
if (cc.hasHooks) stepCount++;
|
|
855
979
|
let step = 1;
|
|
856
980
|
|
|
857
981
|
console.log(` Step ${step}/${stepCount} \u2014 Memory Store`);
|
|
@@ -876,10 +1000,12 @@ async function main() {
|
|
|
876
1000
|
step++;
|
|
877
1001
|
}
|
|
878
1002
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1003
|
+
if (cc.hasHooks) {
|
|
1004
|
+
console.log(` Step ${step}/${stepCount} \u2014 Lifecycle Hooks`);
|
|
1005
|
+
await stepHooks();
|
|
1006
|
+
console.log("");
|
|
1007
|
+
step++;
|
|
1008
|
+
}
|
|
883
1009
|
|
|
884
1010
|
console.log(` Step ${step}/${stepCount} \u2014 Gitignore`);
|
|
885
1011
|
await stepGitignore();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!-- gitmem:start -->
|
|
2
|
+
# GitMem — Persistent Memory
|
|
3
|
+
|
|
4
|
+
You have a persistent memory layer called gitmem. Every session starts from
|
|
5
|
+
zero — but gitmem carries forward lessons, mistakes, wins, decisions, and
|
|
6
|
+
open threads across sessions and projects. Search it. Test it. You might be
|
|
7
|
+
amazed at what's already there. If convinced, make it your own.
|
|
8
|
+
|
|
9
|
+
## Red Lines
|
|
10
|
+
|
|
11
|
+
### Credential Protection
|
|
12
|
+
|
|
13
|
+
Credential exposure in conversation history is permanent and irreversible.
|
|
14
|
+
|
|
15
|
+
1. **NEVER read credential files in full.** Files like `mcp-config.json`, `.env`, `.credentials.json`, `.netrc`, `.npmrc`, SSH keys, or `.pem`/`.key` files must not be read or dumped.
|
|
16
|
+
2. **NEVER print environment variable values that contain secrets.** Commands like `env | grep KEY`, `echo $API_KEY`, or `printenv TOKEN` expose credentials in output.
|
|
17
|
+
3. **NEVER display API keys, tokens, or secrets in conversation output.**
|
|
18
|
+
|
|
19
|
+
Safe alternatives: `env | grep -c VARNAME` (count only), `[ -n "$VARNAME" ] && echo "set"` (existence check), `grep -c '"key"' config.json` (structure check).
|
|
20
|
+
|
|
21
|
+
### Recall Before Consequential Actions
|
|
22
|
+
|
|
23
|
+
1. **NEVER parallelize `recall()` with actions that expose, modify, or transmit sensitive data.** Recall must complete first.
|
|
24
|
+
2. **Confirm scars before acting.** Each recalled scar requires APPLYING (past-tense evidence), N_A (explanation), or REFUTED (risk acknowledgment).
|
|
25
|
+
3. **Parallel recall is only safe with benign reads** — source code, docs, non-sensitive config.
|
|
26
|
+
|
|
27
|
+
## Tools
|
|
28
|
+
|
|
29
|
+
| Tool | When to use |
|
|
30
|
+
|------|-------------|
|
|
31
|
+
| `recall` | Before any task — surfaces relevant warnings from past experience |
|
|
32
|
+
| `confirm_scars` | After recall — acknowledge each scar as APPLYING, N_A, or REFUTED |
|
|
33
|
+
| `search` | Explore institutional knowledge by topic |
|
|
34
|
+
| `log` | Browse recent learnings chronologically |
|
|
35
|
+
| `session_start` | Beginning of session — loads last session context and open threads |
|
|
36
|
+
| `session_close` | End of session — persists what you learned |
|
|
37
|
+
| `create_learning` | Capture a mistake (scar), success (win), or pattern |
|
|
38
|
+
| `create_decision` | Log an architectural or operational decision with rationale |
|
|
39
|
+
| `list_threads` | See unresolved work carrying over between sessions |
|
|
40
|
+
| `create_thread` | Track something that needs follow-up in a future session |
|
|
41
|
+
| `help` | Show all available commands |
|
|
42
|
+
|
|
43
|
+
## Session Lifecycle
|
|
44
|
+
|
|
45
|
+
**Start:** Call `session_start()` at the beginning of every session.
|
|
46
|
+
|
|
47
|
+
**End:** On "closing", "done for now", or "wrapping up":
|
|
48
|
+
|
|
49
|
+
1. **Answer these reflection questions** and display to the human:
|
|
50
|
+
- What broke that you didn't expect?
|
|
51
|
+
- What took longer than it should have?
|
|
52
|
+
- What would you do differently next time?
|
|
53
|
+
- What pattern or approach worked well?
|
|
54
|
+
- What assumption was wrong?
|
|
55
|
+
- Which scars did you apply?
|
|
56
|
+
- What should be captured as institutional memory?
|
|
57
|
+
|
|
58
|
+
2. **Ask the human**: "Any corrections or additions?" Wait for their response.
|
|
59
|
+
|
|
60
|
+
3. **Write payload** to `.gitmem/closing-payload.json`:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"closing_reflection": {
|
|
64
|
+
"what_broke": "...",
|
|
65
|
+
"what_took_longer": "...",
|
|
66
|
+
"do_differently": "...",
|
|
67
|
+
"what_worked": "...",
|
|
68
|
+
"wrong_assumption": "...",
|
|
69
|
+
"scars_applied": ["scar title 1", "scar title 2"],
|
|
70
|
+
"institutional_memory_items": "..."
|
|
71
|
+
},
|
|
72
|
+
"human_corrections": "",
|
|
73
|
+
"scars_to_record": [],
|
|
74
|
+
"open_threads": [],
|
|
75
|
+
"decisions": []
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
4. **Call `session_close`** with `session_id` and `close_type: "standard"`
|
|
80
|
+
|
|
81
|
+
For short exploratory sessions (< 30 min, no real work), use `close_type: "quick"` — no questions needed.
|
|
82
|
+
<!-- gitmem:end -->
|
|
@@ -279,16 +279,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
279
279
|
}[] | undefined;
|
|
280
280
|
transcript_path?: string | undefined;
|
|
281
281
|
linear_issue?: string | undefined;
|
|
282
|
-
open_threads?: (string | {
|
|
283
|
-
id: string;
|
|
284
|
-
created_at: string;
|
|
285
|
-
status: "open" | "resolved";
|
|
286
|
-
text: string;
|
|
287
|
-
resolved_at?: string | undefined;
|
|
288
|
-
resolution_note?: string | undefined;
|
|
289
|
-
source_session?: string | undefined;
|
|
290
|
-
resolved_by_session?: string | undefined;
|
|
291
|
-
})[] | undefined;
|
|
292
282
|
closing_reflection?: {
|
|
293
283
|
what_broke: string;
|
|
294
284
|
what_took_longer: string;
|
|
@@ -300,7 +290,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
300
290
|
collaborative_dynamic?: string | undefined;
|
|
301
291
|
rapport_notes?: string | undefined;
|
|
302
292
|
} | undefined;
|
|
303
|
-
project_state?: string | undefined;
|
|
304
293
|
task_completion?: {
|
|
305
294
|
questions_displayed_at: string;
|
|
306
295
|
reflection_completed_at: string;
|
|
@@ -309,8 +298,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
309
298
|
human_response: string;
|
|
310
299
|
} | undefined;
|
|
311
300
|
human_corrections?: string | undefined;
|
|
312
|
-
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
313
|
-
ceremony_duration_ms?: number | undefined;
|
|
314
301
|
scars_to_record?: {
|
|
315
302
|
surfaced_at: string;
|
|
316
303
|
reference_type: "explicit" | "implicit" | "acknowledged" | "refuted" | "none";
|
|
@@ -324,6 +311,19 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
324
311
|
execution_successful?: boolean | undefined;
|
|
325
312
|
variant_id?: string | undefined;
|
|
326
313
|
}[] | undefined;
|
|
314
|
+
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
315
|
+
open_threads?: (string | {
|
|
316
|
+
id: string;
|
|
317
|
+
created_at: string;
|
|
318
|
+
status: "open" | "resolved";
|
|
319
|
+
text: string;
|
|
320
|
+
resolved_at?: string | undefined;
|
|
321
|
+
resolution_note?: string | undefined;
|
|
322
|
+
source_session?: string | undefined;
|
|
323
|
+
resolved_by_session?: string | undefined;
|
|
324
|
+
})[] | undefined;
|
|
325
|
+
project_state?: string | undefined;
|
|
326
|
+
ceremony_duration_ms?: number | undefined;
|
|
327
327
|
capture_transcript?: boolean | undefined;
|
|
328
328
|
}, {
|
|
329
329
|
session_id: string;
|
|
@@ -336,16 +336,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
336
336
|
}[] | undefined;
|
|
337
337
|
transcript_path?: string | undefined;
|
|
338
338
|
linear_issue?: string | undefined;
|
|
339
|
-
open_threads?: (string | {
|
|
340
|
-
id: string;
|
|
341
|
-
created_at: string;
|
|
342
|
-
status: "open" | "resolved";
|
|
343
|
-
text: string;
|
|
344
|
-
resolved_at?: string | undefined;
|
|
345
|
-
resolution_note?: string | undefined;
|
|
346
|
-
source_session?: string | undefined;
|
|
347
|
-
resolved_by_session?: string | undefined;
|
|
348
|
-
})[] | undefined;
|
|
349
339
|
closing_reflection?: {
|
|
350
340
|
what_broke: string;
|
|
351
341
|
what_took_longer: string;
|
|
@@ -357,7 +347,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
357
347
|
collaborative_dynamic?: string | undefined;
|
|
358
348
|
rapport_notes?: string | undefined;
|
|
359
349
|
} | undefined;
|
|
360
|
-
project_state?: string | undefined;
|
|
361
350
|
task_completion?: {
|
|
362
351
|
questions_displayed_at: string;
|
|
363
352
|
reflection_completed_at: string;
|
|
@@ -366,8 +355,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
366
355
|
human_response: string;
|
|
367
356
|
} | undefined;
|
|
368
357
|
human_corrections?: string | undefined;
|
|
369
|
-
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
370
|
-
ceremony_duration_ms?: number | undefined;
|
|
371
358
|
scars_to_record?: {
|
|
372
359
|
surfaced_at: string;
|
|
373
360
|
reference_type: "explicit" | "implicit" | "acknowledged" | "refuted" | "none";
|
|
@@ -381,6 +368,19 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
381
368
|
execution_successful?: boolean | undefined;
|
|
382
369
|
variant_id?: string | undefined;
|
|
383
370
|
}[] | undefined;
|
|
371
|
+
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
372
|
+
open_threads?: (string | {
|
|
373
|
+
id: string;
|
|
374
|
+
created_at: string;
|
|
375
|
+
status: "open" | "resolved";
|
|
376
|
+
text: string;
|
|
377
|
+
resolved_at?: string | undefined;
|
|
378
|
+
resolution_note?: string | undefined;
|
|
379
|
+
source_session?: string | undefined;
|
|
380
|
+
resolved_by_session?: string | undefined;
|
|
381
|
+
})[] | undefined;
|
|
382
|
+
project_state?: string | undefined;
|
|
383
|
+
ceremony_duration_ms?: number | undefined;
|
|
384
384
|
capture_transcript?: boolean | undefined;
|
|
385
385
|
}>;
|
|
386
386
|
export type SessionCloseParams = z.infer<typeof SessionCloseParamsSchema>;
|
package/dist/server.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Registers all tools and handles MCP protocol communication.
|
|
5
5
|
* Tool definitions are in ./tools/definitions.ts
|
|
6
6
|
*/
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const pkg = require("../package.json");
|
|
7
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -34,6 +37,7 @@ import { archiveLearning } from "./tools/archive-learning.js";
|
|
|
34
37
|
import { getCacheStatus, checkCacheHealth, flushCache, startBackgroundInit, } from "./services/startup.js";
|
|
35
38
|
import { getEffectTracker } from "./services/effect-tracker.js";
|
|
36
39
|
import { getProject } from "./services/session-state.js";
|
|
40
|
+
import { checkEnforcement } from "./services/enforcement.js";
|
|
37
41
|
import { getTier, hasSupabase, hasCacheManagement, hasBatchOperations, hasTranscripts, } from "./services/tier.js";
|
|
38
42
|
import { getRegisteredTools } from "./tools/definitions.js";
|
|
39
43
|
import { validateToolArgs } from "./schemas/registry.js";
|
|
@@ -43,7 +47,7 @@ import { validateToolArgs } from "./schemas/registry.js";
|
|
|
43
47
|
export function createServer() {
|
|
44
48
|
const server = new Server({
|
|
45
49
|
name: "gitmem-mcp",
|
|
46
|
-
version:
|
|
50
|
+
version: pkg.version,
|
|
47
51
|
}, {
|
|
48
52
|
capabilities: {
|
|
49
53
|
tools: {},
|
|
@@ -84,6 +88,8 @@ export function createServer() {
|
|
|
84
88
|
isError: true,
|
|
85
89
|
};
|
|
86
90
|
}
|
|
91
|
+
// Server-side enforcement: advisory warnings for protocol violations
|
|
92
|
+
const enforcement = checkEnforcement(name);
|
|
87
93
|
try {
|
|
88
94
|
let result;
|
|
89
95
|
switch (name) {
|
|
@@ -240,7 +246,7 @@ export function createServer() {
|
|
|
240
246
|
// Build command table for display
|
|
241
247
|
const cmdLines = visibleCommands.map(c => ` ${c.alias.padEnd(22)} ${c.description}`).join("\n");
|
|
242
248
|
const display = [
|
|
243
|
-
`gitmem
|
|
249
|
+
`gitmem v${pkg.version} · ${tier} · ${registeredTools.length} tools · ${hasSupabase() ? "supabase" : "local (.gitmem/)"}`,
|
|
244
250
|
"Memory that compounds.",
|
|
245
251
|
"",
|
|
246
252
|
cmdLines,
|
|
@@ -252,7 +258,7 @@ export function createServer() {
|
|
|
252
258
|
"Tool results are collapsed in the CLI — the user cannot see them unless you echo them.",
|
|
253
259
|
].join("\n");
|
|
254
260
|
result = {
|
|
255
|
-
version:
|
|
261
|
+
version: pkg.version,
|
|
256
262
|
tier,
|
|
257
263
|
tools_registered: registeredTools.length,
|
|
258
264
|
storage: hasSupabase() ? "supabase" : "local (.gitmem/)",
|
|
@@ -313,6 +319,10 @@ export function createServer() {
|
|
|
313
319
|
else {
|
|
314
320
|
responseText = JSON.stringify(result, null, 2);
|
|
315
321
|
}
|
|
322
|
+
// Prepend enforcement warning if present (advisory, non-blocking)
|
|
323
|
+
if (enforcement.warning) {
|
|
324
|
+
responseText = enforcement.warning + "\n\n" + responseText;
|
|
325
|
+
}
|
|
316
326
|
return {
|
|
317
327
|
content: [
|
|
318
328
|
{
|
|
@@ -365,7 +365,7 @@ export function formatSummary(data) {
|
|
|
365
365
|
const lines = [
|
|
366
366
|
`## ${period.days}-Day Summary (${period.start} to ${period.end})`,
|
|
367
367
|
``,
|
|
368
|
-
`**Sessions:** ${total_sessions} | **Decisions:** ${total_decisions} | **
|
|
368
|
+
`**Sessions:** ${total_sessions} | **Decisions:** ${total_decisions} | **Threads Referenced:** ${total_open_threads}`,
|
|
369
369
|
`**With Reflections:** ${sessions_with_reflections} | **With Issues:** ${sessions_with_issues}`,
|
|
370
370
|
``,
|
|
371
371
|
`### Agents`,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Side Enforcement Layer
|
|
3
|
+
*
|
|
4
|
+
* Advisory warnings that surface in tool responses when the agent
|
|
5
|
+
* hasn't followed the recall → confirm → act protocol.
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Advisory, not blocking: warnings append to responses, never prevent execution
|
|
9
|
+
* - Zero overhead on compliant calls: only fires when state is missing
|
|
10
|
+
* - Universal: works in ALL MCP clients, no IDE hooks needed
|
|
11
|
+
* - Lightweight: pure in-memory checks, no I/O
|
|
12
|
+
*/
|
|
13
|
+
export interface EnforcementResult {
|
|
14
|
+
/** Warning text to prepend to tool response (null = clean, no warning) */
|
|
15
|
+
warning: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run pre-dispatch enforcement checks for a tool call.
|
|
19
|
+
*
|
|
20
|
+
* Returns a warning string to prepend to the response, or null if clean.
|
|
21
|
+
* Never blocks execution — always advisory.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkEnforcement(toolName: string): EnforcementResult;
|
|
24
|
+
//# sourceMappingURL=enforcement.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Side Enforcement Layer
|
|
3
|
+
*
|
|
4
|
+
* Advisory warnings that surface in tool responses when the agent
|
|
5
|
+
* hasn't followed the recall → confirm → act protocol.
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Advisory, not blocking: warnings append to responses, never prevent execution
|
|
9
|
+
* - Zero overhead on compliant calls: only fires when state is missing
|
|
10
|
+
* - Universal: works in ALL MCP clients, no IDE hooks needed
|
|
11
|
+
* - Lightweight: pure in-memory checks, no I/O
|
|
12
|
+
*/
|
|
13
|
+
import { getCurrentSession, hasUnconfirmedScars, getSurfacedScars } from "./session-state.js";
|
|
14
|
+
/**
|
|
15
|
+
* Tools that require an active session to function properly.
|
|
16
|
+
* Read-only/administrative tools are excluded.
|
|
17
|
+
*/
|
|
18
|
+
const SESSION_REQUIRED_TOOLS = new Set([
|
|
19
|
+
"recall", "gitmem-r",
|
|
20
|
+
"confirm_scars", "gitmem-cs", "gm-confirm",
|
|
21
|
+
"session_close", "gitmem-sc", "gm-close",
|
|
22
|
+
"session_refresh", "gitmem-sr", "gm-refresh",
|
|
23
|
+
"create_learning", "gitmem-cl", "gm-scar",
|
|
24
|
+
"create_decision", "gitmem-cd",
|
|
25
|
+
"record_scar_usage", "gitmem-rs",
|
|
26
|
+
"record_scar_usage_batch", "gitmem-rsb",
|
|
27
|
+
"prepare_context", "gitmem-pc", "gm-pc",
|
|
28
|
+
"absorb_observations", "gitmem-ao", "gm-absorb",
|
|
29
|
+
"create_thread", "gitmem-ct", "gm-thread-new",
|
|
30
|
+
"resolve_thread", "gitmem-rt", "gm-resolve",
|
|
31
|
+
"save_transcript", "gitmem-st",
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Tools that represent "consequential actions" — the agent is creating
|
|
35
|
+
* or modifying state. These should ideally happen after recall + confirm.
|
|
36
|
+
*/
|
|
37
|
+
const CONSEQUENTIAL_TOOLS = new Set([
|
|
38
|
+
"create_learning", "gitmem-cl", "gm-scar",
|
|
39
|
+
"create_decision", "gitmem-cd",
|
|
40
|
+
"create_thread", "gitmem-ct", "gm-thread-new",
|
|
41
|
+
"session_close", "gitmem-sc", "gm-close",
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Tools that are always safe — no enforcement checks needed.
|
|
45
|
+
* Includes session_start (which creates the session), read-only tools,
|
|
46
|
+
* and administrative tools.
|
|
47
|
+
*/
|
|
48
|
+
const EXEMPT_TOOLS = new Set([
|
|
49
|
+
"session_start", "gitmem-ss", "gm-open",
|
|
50
|
+
"search", "gitmem-search", "gm-search",
|
|
51
|
+
"log", "gitmem-log", "gm-log",
|
|
52
|
+
"analyze", "gitmem-analyze", "gm-analyze",
|
|
53
|
+
"graph_traverse", "gitmem-graph", "gm-graph",
|
|
54
|
+
"list_threads", "gitmem-lt", "gm-threads",
|
|
55
|
+
"cleanup_threads", "gitmem-cleanup", "gm-cleanup",
|
|
56
|
+
"promote_suggestion", "gitmem-ps", "gm-promote",
|
|
57
|
+
"dismiss_suggestion", "gitmem-ds", "gm-dismiss",
|
|
58
|
+
"archive_learning", "gitmem-al", "gm-archive",
|
|
59
|
+
"get_transcript", "gitmem-gt",
|
|
60
|
+
"search_transcripts", "gitmem-stx", "gm-stx",
|
|
61
|
+
"gitmem-help",
|
|
62
|
+
"health", "gitmem-health", "gm-health",
|
|
63
|
+
"gitmem-cache-status", "gm-cache-s",
|
|
64
|
+
"gitmem-cache-health", "gm-cache-h",
|
|
65
|
+
"gitmem-cache-flush", "gm-cache-f",
|
|
66
|
+
]);
|
|
67
|
+
/**
|
|
68
|
+
* Run pre-dispatch enforcement checks for a tool call.
|
|
69
|
+
*
|
|
70
|
+
* Returns a warning string to prepend to the response, or null if clean.
|
|
71
|
+
* Never blocks execution — always advisory.
|
|
72
|
+
*/
|
|
73
|
+
export function checkEnforcement(toolName) {
|
|
74
|
+
// Exempt tools skip all checks
|
|
75
|
+
if (EXEMPT_TOOLS.has(toolName)) {
|
|
76
|
+
return { warning: null };
|
|
77
|
+
}
|
|
78
|
+
const session = getCurrentSession();
|
|
79
|
+
// Check 1: No active session
|
|
80
|
+
if (!session && SESSION_REQUIRED_TOOLS.has(toolName)) {
|
|
81
|
+
return {
|
|
82
|
+
warning: [
|
|
83
|
+
"--- gitmem enforcement ---",
|
|
84
|
+
"No active session. Call session_start() first to initialize memory context.",
|
|
85
|
+
"Without a session, scars won't be tracked and the closing ceremony can't run.",
|
|
86
|
+
"---",
|
|
87
|
+
].join("\n"),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// If no session and not session-required, skip remaining checks
|
|
91
|
+
if (!session) {
|
|
92
|
+
return { warning: null };
|
|
93
|
+
}
|
|
94
|
+
// Check 2: Unconfirmed scars before consequential action
|
|
95
|
+
if (CONSEQUENTIAL_TOOLS.has(toolName) && hasUnconfirmedScars()) {
|
|
96
|
+
const recallScars = getSurfacedScars().filter(s => s.source === "recall");
|
|
97
|
+
const confirmedCount = session.confirmations?.length || 0;
|
|
98
|
+
const pendingCount = recallScars.length - confirmedCount;
|
|
99
|
+
return {
|
|
100
|
+
warning: [
|
|
101
|
+
"--- gitmem enforcement ---",
|
|
102
|
+
`${pendingCount} recalled scar(s) await confirmation.`,
|
|
103
|
+
"Call confirm_scars() with APPLYING/N_A/REFUTED for each before proceeding.",
|
|
104
|
+
"Unconfirmed scars may contain warnings relevant to what you're about to do.",
|
|
105
|
+
"---",
|
|
106
|
+
].join("\n"),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Check 3: No recall before consequential action
|
|
110
|
+
if (CONSEQUENTIAL_TOOLS.has(toolName)) {
|
|
111
|
+
const recallScars = getSurfacedScars().filter(s => s.source === "recall");
|
|
112
|
+
if (recallScars.length === 0) {
|
|
113
|
+
return {
|
|
114
|
+
warning: [
|
|
115
|
+
"--- gitmem enforcement ---",
|
|
116
|
+
"No recall() was run this session before this action.",
|
|
117
|
+
"Consider calling recall() first to check for relevant institutional memory.",
|
|
118
|
+
"Past mistakes and patterns may prevent repeating known issues.",
|
|
119
|
+
"---",
|
|
120
|
+
].join("\n"),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { warning: null };
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=enforcement.js.map
|
|
@@ -44,12 +44,16 @@ function buildCleanupDisplay(summary, groups, archivedCount) {
|
|
|
44
44
|
for (const [label, items] of sections) {
|
|
45
45
|
if (items.length === 0)
|
|
46
46
|
continue;
|
|
47
|
-
lines.push(
|
|
47
|
+
lines.push(`**${label}** (${items.length}):`);
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push("| ID | Thread | Last Touch |");
|
|
50
|
+
lines.push("|----|--------|------------|");
|
|
48
51
|
for (const t of items) {
|
|
49
|
-
const text = truncate(t.text,
|
|
52
|
+
const text = truncate(t.text, 60);
|
|
50
53
|
const time = formatDaysAgo(t.days_since_touch);
|
|
51
|
-
lines.push(
|
|
54
|
+
lines.push(`| ${t.thread_id} | ${text} | ${time} |`);
|
|
52
55
|
}
|
|
56
|
+
lines.push("");
|
|
53
57
|
}
|
|
54
58
|
lines.push("");
|
|
55
59
|
lines.push(`Archived: ${archivedCount}`);
|
|
@@ -158,7 +158,7 @@ export const TOOLS = [
|
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
name: "create_learning",
|
|
161
|
-
description: "Create scar, win, or pattern entry in institutional memory",
|
|
161
|
+
description: "Create scar, win, or pattern entry in institutional memory. Frame as 'what we now know' — lead with the factual/architectural discovery, not what went wrong. Good: 'Fine-grained PATs are scoped to one resource owner'. Bad: 'Should have checked PAT type first'.",
|
|
162
162
|
inputSchema: {
|
|
163
163
|
type: "object",
|
|
164
164
|
properties: {
|
|
@@ -169,11 +169,11 @@ export const TOOLS = [
|
|
|
169
169
|
},
|
|
170
170
|
title: {
|
|
171
171
|
type: "string",
|
|
172
|
-
description: "
|
|
172
|
+
description: "Frame as a knowledge discovery — what we now know. Lead with the factual insight, not self-criticism.",
|
|
173
173
|
},
|
|
174
174
|
description: {
|
|
175
175
|
type: "string",
|
|
176
|
-
description: "Detailed description",
|
|
176
|
+
description: "Detailed description. Include the architectural/behavioral fact that makes this retrievable by domain.",
|
|
177
177
|
},
|
|
178
178
|
severity: {
|
|
179
179
|
type: "string",
|
|
@@ -849,7 +849,7 @@ export const TOOLS = [
|
|
|
849
849
|
},
|
|
850
850
|
{
|
|
851
851
|
name: "gitmem-cl",
|
|
852
|
-
description: "gitmem-cl (create_learning) - Create scar/win/pattern
|
|
852
|
+
description: "gitmem-cl (create_learning) - Create scar/win/pattern. Frame as 'what we now know' — factual discovery, not self-criticism.",
|
|
853
853
|
inputSchema: {
|
|
854
854
|
type: "object",
|
|
855
855
|
properties: {
|
|
@@ -1496,7 +1496,7 @@ export const TOOLS = [
|
|
|
1496
1496
|
},
|
|
1497
1497
|
{
|
|
1498
1498
|
name: "gm-scar",
|
|
1499
|
-
description: "gm-scar (create_learning) - Create a scar/win/pattern
|
|
1499
|
+
description: "gm-scar (create_learning) - Create a scar/win/pattern. Frame as 'what we now know' — factual discovery, not self-criticism.",
|
|
1500
1500
|
inputSchema: {
|
|
1501
1501
|
type: "object",
|
|
1502
1502
|
properties: {
|
|
@@ -39,24 +39,16 @@ function buildThreadsDisplay(threads, totalOpen, totalResolved) {
|
|
|
39
39
|
}
|
|
40
40
|
// Deterministic sort: oldest first by created_at
|
|
41
41
|
const sorted = [...threads].sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const DATE_W = 8;
|
|
46
|
-
const hr = (l, j, r) => `${l}${"─".repeat(NUM_W + 2)}${j}${"─".repeat(ID_W + 2)}${j}${"─".repeat(TEXT_W + 2)}${j}${"─".repeat(DATE_W + 2)}${r}`;
|
|
47
|
-
const hdr = (l, j, r) => `${l}${"═".repeat(NUM_W + 2)}${j}${"═".repeat(ID_W + 2)}${j}${"═".repeat(TEXT_W + 2)}${j}${"═".repeat(DATE_W + 2)}${r}`;
|
|
48
|
-
const row = (n, id, t, d) => `│ ${n.padEnd(NUM_W)} │ ${id.padEnd(ID_W)} │ ${t.padEnd(TEXT_W)} │ ${d.padStart(DATE_W)} │`;
|
|
49
|
-
lines.push(hr("┌", "┬", "┐"));
|
|
50
|
-
lines.push(row("#", "ID", "Thread", "Active"));
|
|
51
|
-
lines.push(hdr("╞", "╪", "╡"));
|
|
42
|
+
// Markdown table — renders cleanly in all MCP clients
|
|
43
|
+
lines.push("| # | ID | Thread | Active |");
|
|
44
|
+
lines.push("|---|-----|--------|--------|");
|
|
52
45
|
for (let i = 0; i < sorted.length; i++) {
|
|
53
46
|
const t = sorted[i];
|
|
54
|
-
const shortId =
|
|
55
|
-
const text = truncate(t.text,
|
|
47
|
+
const shortId = t.id;
|
|
48
|
+
const text = truncate(t.text, 60);
|
|
56
49
|
const date = shortDate(t.last_touched_at || t.created_at);
|
|
57
|
-
lines.push(
|
|
50
|
+
lines.push(`| ${i + 1} | ${shortId} | ${text} | ${date} |`);
|
|
58
51
|
}
|
|
59
|
-
lines.push(hr("└", "┴", "┘"));
|
|
60
52
|
return wrapDisplay(lines.join("\n"));
|
|
61
53
|
}
|
|
62
54
|
export async function listThreads(params) {
|
package/dist/tools/log.js
CHANGED
|
@@ -20,7 +20,7 @@ import { wrapDisplay, relativeTime, truncate, SEV, TYPE } from "../services/disp
|
|
|
20
20
|
// --- Display Formatting ---
|
|
21
21
|
function buildLogDisplay(entries, total, filters) {
|
|
22
22
|
const lines = [];
|
|
23
|
-
lines.push(`gitmem log · ${total}
|
|
23
|
+
lines.push(`gitmem log · ${total} most recent learnings · ${filters.project}`);
|
|
24
24
|
const fp = [];
|
|
25
25
|
if (filters.learning_type)
|
|
26
26
|
fp.push(`type=${filters.learning_type}`);
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { v4 as uuidv4 } from "uuid";
|
|
6
6
|
import * as supabase from "../services/supabase-client.js";
|
|
7
7
|
import { hasSupabase, getTableName } from "../services/tier.js";
|
|
8
|
+
import { detectAgent } from "../services/agent-detection.js";
|
|
9
|
+
import { getCurrentSession } from "../services/session-state.js";
|
|
8
10
|
import { Timer, recordMetrics, buildPerformanceData } from "../services/metrics.js";
|
|
9
11
|
const TARGET_LATENCY_MS = 2000; // Target for batch operation
|
|
10
12
|
/**
|
|
@@ -84,6 +86,9 @@ export async function recordScarUsageBatch(params) {
|
|
|
84
86
|
return { entry, scarId };
|
|
85
87
|
});
|
|
86
88
|
const resolvedScars = await Promise.all(resolutionPromises);
|
|
89
|
+
// Auto-detect agent and session as fallbacks for entries missing them
|
|
90
|
+
const fallbackAgent = detectAgent().agent || null;
|
|
91
|
+
const fallbackSessionId = getCurrentSession()?.sessionId || null;
|
|
87
92
|
// Build usage records for all successfully resolved scars
|
|
88
93
|
const usageRecords = resolvedScars
|
|
89
94
|
.filter(({ scarId }) => scarId !== null)
|
|
@@ -94,8 +99,8 @@ export async function recordScarUsageBatch(params) {
|
|
|
94
99
|
scar_id: scarId,
|
|
95
100
|
issue_id: entry.issue_id || null,
|
|
96
101
|
issue_identifier: entry.issue_identifier || null,
|
|
97
|
-
session_id: entry.session_id ||
|
|
98
|
-
agent: entry.agent ||
|
|
102
|
+
session_id: entry.session_id || fallbackSessionId,
|
|
103
|
+
agent: entry.agent || fallbackAgent,
|
|
99
104
|
surfaced_at: entry.surfaced_at,
|
|
100
105
|
acknowledged_at: entry.acknowledged_at || null,
|
|
101
106
|
referenced: entry.reference_type !== "none",
|
|
@@ -11,6 +11,8 @@ import { wrapDisplay } from "../services/display-protocol.js";
|
|
|
11
11
|
import * as supabase from "../services/supabase-client.js";
|
|
12
12
|
import { hasSupabase } from "../services/tier.js";
|
|
13
13
|
import { getStorage } from "../services/storage.js";
|
|
14
|
+
import { detectAgent } from "../services/agent-detection.js";
|
|
15
|
+
import { getCurrentSession } from "../services/session-state.js";
|
|
14
16
|
import { Timer, recordMetrics, buildPerformanceData, } from "../services/metrics.js";
|
|
15
17
|
/**
|
|
16
18
|
* Execute record_scar_usage tool
|
|
@@ -19,13 +21,16 @@ export async function recordScarUsage(params) {
|
|
|
19
21
|
const timer = new Timer();
|
|
20
22
|
const metricsId = uuidv4();
|
|
21
23
|
const usageId = uuidv4();
|
|
24
|
+
// Auto-detect agent and session if not provided by caller
|
|
25
|
+
const resolvedAgent = params.agent || detectAgent().agent || null;
|
|
26
|
+
const resolvedSessionId = params.session_id || getCurrentSession()?.sessionId || null;
|
|
22
27
|
const usageData = {
|
|
23
28
|
id: usageId,
|
|
24
29
|
scar_id: params.scar_id,
|
|
25
30
|
issue_id: params.issue_id || null,
|
|
26
31
|
issue_identifier: params.issue_identifier || null,
|
|
27
|
-
session_id:
|
|
28
|
-
agent:
|
|
32
|
+
session_id: resolvedSessionId,
|
|
33
|
+
agent: resolvedAgent,
|
|
29
34
|
surfaced_at: params.surfaced_at,
|
|
30
35
|
acknowledged_at: params.acknowledged_at || null,
|
|
31
36
|
referenced: params.reference_type !== "none",
|
|
@@ -28,6 +28,33 @@ import { setGitmemDir, getGitmemDir, getSessionPath, getConfigProject } from "..
|
|
|
28
28
|
import { registerSession, findSessionByHostPid, pruneStale, migrateFromLegacy } from "../services/active-sessions.js";
|
|
29
29
|
import * as os from "os";
|
|
30
30
|
import { formatDate } from "../services/timezone.js";
|
|
31
|
+
/**
|
|
32
|
+
* Closing payload schema — returned in session_start/refresh so agents
|
|
33
|
+
* know the exact field names for closing-payload.json without guessing.
|
|
34
|
+
*/
|
|
35
|
+
const CLOSING_PAYLOAD_SCHEMA = {
|
|
36
|
+
closing_reflection: {
|
|
37
|
+
what_broke: "Q1: What broke that you didn't expect?",
|
|
38
|
+
what_took_longer: "Q2: What took longer than it should have?",
|
|
39
|
+
do_differently: "Q3: What would you do differently next time?",
|
|
40
|
+
what_worked: "Q4: What pattern or approach worked well?",
|
|
41
|
+
wrong_assumption: "Q5: What assumption was wrong?",
|
|
42
|
+
scars_applied: ["Q6: scar titles applied"],
|
|
43
|
+
institutional_memory_items: "Q7: What to capture as institutional memory?",
|
|
44
|
+
},
|
|
45
|
+
task_completion: {
|
|
46
|
+
questions_displayed_at: "ISO-8601",
|
|
47
|
+
reflection_completed_at: "ISO-8601",
|
|
48
|
+
human_asked_at: "ISO-8601",
|
|
49
|
+
human_response_at: "ISO-8601",
|
|
50
|
+
human_response: "human's corrections or 'no corrections'",
|
|
51
|
+
},
|
|
52
|
+
human_corrections: "",
|
|
53
|
+
scars_to_record: [],
|
|
54
|
+
learnings_created: [],
|
|
55
|
+
open_threads: [],
|
|
56
|
+
decisions: [],
|
|
57
|
+
};
|
|
31
58
|
/**
|
|
32
59
|
* Normalize decisions from mixed formats (strings or objects) to string[].
|
|
33
60
|
* Historical sessions (pre-2026) stored {title, decision} objects.
|
|
@@ -413,6 +440,7 @@ async function sessionStartFree(params, env, agent, project, timer, metricsId, e
|
|
|
413
440
|
...(freeMergedThreads.length > 0 && { open_threads: freeMergedThreads }),
|
|
414
441
|
recent_decisions: decisions,
|
|
415
442
|
gitmem_dir: getGitmemDir(),
|
|
443
|
+
closing_payload_schema: CLOSING_PAYLOAD_SCHEMA,
|
|
416
444
|
project,
|
|
417
445
|
performance,
|
|
418
446
|
};
|
|
@@ -573,13 +601,27 @@ function writeSessionFiles(sessionId, agent, project, surfacedScars, threads, re
|
|
|
573
601
|
const existingFileThreads = loadThreadsFile();
|
|
574
602
|
let merged;
|
|
575
603
|
if (supabaseAuthoritative) {
|
|
576
|
-
// Supabase is source of truth — use its threads, but preserve
|
|
577
|
-
//
|
|
578
|
-
// mid-session
|
|
604
|
+
// Supabase is source of truth — use its threads, but preserve local-only threads
|
|
605
|
+
// that were created recently (within 24h) and have a valid ID. These are likely
|
|
606
|
+
// mid-session threads not yet synced by session_close. Older local-only threads
|
|
607
|
+
// are stale — they were resolved/archived in Supabase but linger in the file
|
|
608
|
+
// because the NOT-IN query excludes them, making them look "local-only".
|
|
579
609
|
const supabaseIds = new Set(threads.map(t => t.id));
|
|
580
|
-
const
|
|
610
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
611
|
+
const localOnlyThreads = existingFileThreads.filter(t => {
|
|
612
|
+
if (supabaseIds.has(t.id))
|
|
613
|
+
return false; // exists in Supabase active set
|
|
614
|
+
if (!t.id)
|
|
615
|
+
return false; // no ID = malformed, drop
|
|
616
|
+
const created = t.created_at ? new Date(t.created_at).getTime() : 0;
|
|
617
|
+
return created > cutoff; // only keep if created within last 24h
|
|
618
|
+
});
|
|
619
|
+
const dropped = existingFileThreads.filter(t => !supabaseIds.has(t.id)).length - localOnlyThreads.length;
|
|
581
620
|
if (localOnlyThreads.length > 0) {
|
|
582
|
-
console.error(`[session_start] Preserving ${localOnlyThreads.length} local-only threads not yet in Supabase`);
|
|
621
|
+
console.error(`[session_start] Preserving ${localOnlyThreads.length} recent local-only threads not yet in Supabase`);
|
|
622
|
+
}
|
|
623
|
+
if (dropped > 0) {
|
|
624
|
+
console.error(`[session_start] Dropped ${dropped} stale local-only threads (resolved/archived in Supabase)`);
|
|
583
625
|
}
|
|
584
626
|
merged = deduplicateThreadList([...threads, ...localOnlyThreads]);
|
|
585
627
|
}
|
|
@@ -809,6 +851,7 @@ export async function sessionStart(params) {
|
|
|
809
851
|
recent_decisions: decisions,
|
|
810
852
|
...(recordingPath && { recording_path: recordingPath }),
|
|
811
853
|
gitmem_dir: getGitmemDir(),
|
|
854
|
+
closing_payload_schema: CLOSING_PAYLOAD_SCHEMA,
|
|
812
855
|
project,
|
|
813
856
|
performance,
|
|
814
857
|
};
|
|
@@ -931,6 +974,7 @@ export async function sessionRefresh(params) {
|
|
|
931
974
|
recent_decisions: decisions,
|
|
932
975
|
// Rapport disabled — not injected into session context
|
|
933
976
|
...(recordingPath && { recording_path: recordingPath }),
|
|
977
|
+
closing_payload_schema: CLOSING_PAYLOAD_SCHEMA,
|
|
934
978
|
project,
|
|
935
979
|
performance,
|
|
936
980
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -163,6 +163,8 @@ export interface SessionStartResult {
|
|
|
163
163
|
recording_path?: string;
|
|
164
164
|
/** Resolved .gitmem directory path — use for closing-payload.json writes */
|
|
165
165
|
gitmem_dir?: string;
|
|
166
|
+
/** Closing payload schema — exact field names for closing-payload.json */
|
|
167
|
+
closing_payload_schema?: Record<string, unknown>;
|
|
166
168
|
/** Project namespace for this session */
|
|
167
169
|
project?: string;
|
|
168
170
|
/** Pre-formatted display string for consistent CLI output */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitmem-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Institutional memory for AI coding agents. Memory that compounds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@types/uuid": "^9.0.0",
|
|
41
41
|
"pg": "^8.11.0",
|
|
42
42
|
"typescript": "^5.0.0",
|
|
43
|
-
"vitest": "^
|
|
43
|
+
"vitest": "^4.0.18"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=18.0.0"
|
|
@@ -55,16 +55,26 @@
|
|
|
55
55
|
"schema",
|
|
56
56
|
"CLAUDE.md.template",
|
|
57
57
|
"cursorrules.template",
|
|
58
|
+
"copilot-instructions.template",
|
|
59
|
+
"windsurfrules.template",
|
|
58
60
|
"README.md",
|
|
59
61
|
"CHANGELOG.md"
|
|
60
62
|
],
|
|
61
63
|
"keywords": [
|
|
62
64
|
"mcp",
|
|
65
|
+
"mcp-server",
|
|
63
66
|
"gitmem",
|
|
64
67
|
"claude",
|
|
68
|
+
"claude-code",
|
|
65
69
|
"cursor",
|
|
66
|
-
"
|
|
70
|
+
"vscode",
|
|
71
|
+
"copilot",
|
|
72
|
+
"windsurf",
|
|
73
|
+
"openclaw",
|
|
74
|
+
"ai-memory",
|
|
75
|
+
"ai-agent",
|
|
67
76
|
"ai-coding",
|
|
77
|
+
"institutional-memory",
|
|
68
78
|
"scars",
|
|
69
79
|
"semantic-search"
|
|
70
80
|
],
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# --- gitmem:start ---
|
|
2
|
+
# GitMem — Persistent Memory
|
|
3
|
+
|
|
4
|
+
You have a persistent memory layer called gitmem. Every session starts from
|
|
5
|
+
zero — but gitmem carries forward lessons, mistakes, wins, decisions, and
|
|
6
|
+
open threads across sessions and projects. Search it. Test it. You might be
|
|
7
|
+
amazed at what's already there. If convinced, make it your own.
|
|
8
|
+
|
|
9
|
+
## Red Lines
|
|
10
|
+
|
|
11
|
+
### Credential Protection
|
|
12
|
+
|
|
13
|
+
Credential exposure in conversation history is permanent and irreversible.
|
|
14
|
+
|
|
15
|
+
1. **NEVER read credential files in full.** Files like `mcp-config.json`, `.env`, `.credentials.json`, `.netrc`, `.npmrc`, SSH keys, or `.pem`/`.key` files must not be read or dumped.
|
|
16
|
+
2. **NEVER print environment variable values that contain secrets.** Commands like `env | grep KEY`, `echo $API_KEY`, or `printenv TOKEN` expose credentials in output.
|
|
17
|
+
3. **NEVER display API keys, tokens, or secrets in conversation output.**
|
|
18
|
+
|
|
19
|
+
Safe alternatives: `env | grep -c VARNAME` (count only), `[ -n "$VARNAME" ] && echo "set"` (existence check), `grep -c '"key"' config.json` (structure check).
|
|
20
|
+
|
|
21
|
+
### Recall Before Consequential Actions
|
|
22
|
+
|
|
23
|
+
1. **NEVER parallelize `recall()` with actions that expose, modify, or transmit sensitive data.** Recall must complete first.
|
|
24
|
+
2. **Confirm scars before acting.** Each recalled scar requires APPLYING (past-tense evidence), N_A (explanation), or REFUTED (risk acknowledgment).
|
|
25
|
+
3. **Parallel recall is only safe with benign reads** — source code, docs, non-sensitive config.
|
|
26
|
+
|
|
27
|
+
## Tools
|
|
28
|
+
|
|
29
|
+
| Tool | When to use |
|
|
30
|
+
|------|-------------|
|
|
31
|
+
| `recall` | Before any task — surfaces relevant warnings from past experience |
|
|
32
|
+
| `confirm_scars` | After recall — acknowledge each scar as APPLYING, N_A, or REFUTED |
|
|
33
|
+
| `search` | Explore institutional knowledge by topic |
|
|
34
|
+
| `log` | Browse recent learnings chronologically |
|
|
35
|
+
| `session_start` | Beginning of session — loads last session context and open threads |
|
|
36
|
+
| `session_close` | End of session — persists what you learned |
|
|
37
|
+
| `create_learning` | Capture a mistake (scar), success (win), or pattern |
|
|
38
|
+
| `create_decision` | Log an architectural or operational decision with rationale |
|
|
39
|
+
| `list_threads` | See unresolved work carrying over between sessions |
|
|
40
|
+
| `create_thread` | Track something that needs follow-up in a future session |
|
|
41
|
+
| `help` | Show all available commands |
|
|
42
|
+
|
|
43
|
+
## Session Lifecycle
|
|
44
|
+
|
|
45
|
+
**Start:** Call `session_start()` at the beginning of every session.
|
|
46
|
+
|
|
47
|
+
**End:** On "closing", "done for now", or "wrapping up":
|
|
48
|
+
|
|
49
|
+
1. **Answer these reflection questions** and display to the human:
|
|
50
|
+
- What broke that you didn't expect?
|
|
51
|
+
- What took longer than it should have?
|
|
52
|
+
- What would you do differently next time?
|
|
53
|
+
- What pattern or approach worked well?
|
|
54
|
+
- What assumption was wrong?
|
|
55
|
+
- Which scars did you apply?
|
|
56
|
+
- What should be captured as institutional memory?
|
|
57
|
+
|
|
58
|
+
2. **Ask the human**: "Any corrections or additions?" Wait for their response.
|
|
59
|
+
|
|
60
|
+
3. **Write payload** to `.gitmem/closing-payload.json`:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"closing_reflection": {
|
|
64
|
+
"what_broke": "...",
|
|
65
|
+
"what_took_longer": "...",
|
|
66
|
+
"do_differently": "...",
|
|
67
|
+
"what_worked": "...",
|
|
68
|
+
"wrong_assumption": "...",
|
|
69
|
+
"scars_applied": ["scar title 1", "scar title 2"],
|
|
70
|
+
"institutional_memory_items": "..."
|
|
71
|
+
},
|
|
72
|
+
"human_corrections": "",
|
|
73
|
+
"scars_to_record": [],
|
|
74
|
+
"open_threads": [],
|
|
75
|
+
"decisions": []
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
4. **Call `session_close`** with `session_id` and `close_type: "standard"`
|
|
80
|
+
|
|
81
|
+
For short exploratory sessions (< 30 min, no real work), use `close_type: "quick"` — no questions needed.
|
|
82
|
+
# --- gitmem:end ---
|