pi-hermes-memory 0.6.7 → 0.6.8
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/README.md +18 -3
- package/docs/images/memory-architecture.svg +1 -1
- package/docs/images/session-lifecycle.svg +1 -1
- package/docs/images/source-architecture.svg +1 -1
- package/docs/mermaid/memory-architecture.mmd +22 -4
- package/docs/mermaid/session-lifecycle.mmd +34 -31
- package/docs/mermaid/source-architecture.mmd +17 -1
- package/package.json +1 -1
- package/src/handlers/correction-detector.ts +28 -3
- package/src/handlers/learn-memory.ts +16 -10
- package/src/handlers/preview-context.ts +67 -0
- package/src/handlers/sync-markdown-memories.ts +136 -0
- package/src/index.ts +7 -3
- package/src/store/sqlite-memory-store.ts +472 -46
- package/src/tools/memory-tool.ts +155 -4
- package/src/types.ts +3 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview context command — /memory-preview-context shows the memory/skill blocks
|
|
3
|
+
* that are injected into the system prompt.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { MemoryStore } from "../store/memory-store.js";
|
|
8
|
+
import { SkillStore } from "../store/skill-store.js";
|
|
9
|
+
|
|
10
|
+
export function registerPreviewContextCommand(
|
|
11
|
+
pi: ExtensionAPI,
|
|
12
|
+
store: MemoryStore,
|
|
13
|
+
projectStore: MemoryStore | null,
|
|
14
|
+
skillStore: SkillStore,
|
|
15
|
+
projectName: string,
|
|
16
|
+
): void {
|
|
17
|
+
pi.registerCommand("memory-preview-context", {
|
|
18
|
+
description: "Preview the memory/skill context blocks injected into the system prompt",
|
|
19
|
+
handler: async (_args, ctx) => {
|
|
20
|
+
const memoryBlock = store.formatForSystemPrompt();
|
|
21
|
+
const projectBlock = projectStore ? projectStore.formatProjectBlock(projectName) : "";
|
|
22
|
+
const skillIndex = await skillStore.formatIndexForSystemPrompt();
|
|
23
|
+
|
|
24
|
+
const lines: string[] = [];
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push(" ╔══════════════════════════════════════════════╗");
|
|
27
|
+
lines.push(" ║ 👀 Injected Context Preview ║");
|
|
28
|
+
lines.push(" ╚══════════════════════════════════════════════╝");
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push(" This is the memory/skill context appended to the system prompt.");
|
|
31
|
+
lines.push(" (Core hidden system instructions are NOT shown.)");
|
|
32
|
+
lines.push("");
|
|
33
|
+
|
|
34
|
+
let blockCount = 0;
|
|
35
|
+
|
|
36
|
+
if (memoryBlock) {
|
|
37
|
+
blockCount++;
|
|
38
|
+
lines.push(" ── MEMORY + USER + RECENT FAILURES ─────────────────────────");
|
|
39
|
+
lines.push(memoryBlock);
|
|
40
|
+
lines.push("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (projectBlock) {
|
|
44
|
+
blockCount++;
|
|
45
|
+
lines.push(` ── PROJECT MEMORY (${projectName}) ─────────────────────────`);
|
|
46
|
+
lines.push(projectBlock);
|
|
47
|
+
lines.push("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (skillIndex) {
|
|
51
|
+
blockCount++;
|
|
52
|
+
lines.push(" ── SKILL INDEX ─────────────────────────────────────────────");
|
|
53
|
+
lines.push(skillIndex);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (blockCount === 0) {
|
|
58
|
+
lines.push(" No memory context blocks are currently injected.");
|
|
59
|
+
lines.push(" Add memory entries or skills, then run this command again.");
|
|
60
|
+
lines.push("");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
lines.push(` Blocks shown: ${blockCount}`);
|
|
64
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown memory sync command — /memory-sync-markdown imports existing
|
|
3
|
+
* Markdown-backed memories into the SQLite search store.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import { DatabaseManager } from '../store/db.js';
|
|
10
|
+
import {
|
|
11
|
+
parseMarkdownMemoryEntry,
|
|
12
|
+
syncMemoryEntry,
|
|
13
|
+
} from '../store/sqlite-memory-store.js';
|
|
14
|
+
import { ENTRY_DELIMITER, MEMORY_FILE, USER_FILE } from '../constants.js';
|
|
15
|
+
|
|
16
|
+
interface BackfillCounters {
|
|
17
|
+
filesScanned: number;
|
|
18
|
+
entriesScanned: number;
|
|
19
|
+
imported: number;
|
|
20
|
+
skipped: number;
|
|
21
|
+
warnings: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readEntries(filePath: string): string[] {
|
|
25
|
+
if (!fs.existsSync(filePath)) return [];
|
|
26
|
+
const raw = fs.readFileSync(filePath, 'utf-8').trim();
|
|
27
|
+
if (!raw) return [];
|
|
28
|
+
return raw.split(ENTRY_DELIMITER).map((entry) => entry.trim()).filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function importEntries(
|
|
32
|
+
dbManager: DatabaseManager,
|
|
33
|
+
counters: BackfillCounters,
|
|
34
|
+
entries: string[],
|
|
35
|
+
target: 'memory' | 'user' | 'failure',
|
|
36
|
+
project: string | null = null,
|
|
37
|
+
): void {
|
|
38
|
+
for (const rawEntry of entries) {
|
|
39
|
+
counters.entriesScanned++;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = parseMarkdownMemoryEntry(rawEntry, target, project);
|
|
42
|
+
const result = syncMemoryEntry(dbManager, parsed);
|
|
43
|
+
if (result.action === 'inserted') counters.imported++;
|
|
44
|
+
else counters.skipped++;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
counters.warnings.push(
|
|
47
|
+
`${path.basename(project ?? 'global')}/${target}: ${err instanceof Error ? err.message : String(err)}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scanProjectDirs(agentRoot: string, globalDir: string): Array<{ name: string; memoryFile: string }> {
|
|
54
|
+
if (!fs.existsSync(agentRoot)) return [];
|
|
55
|
+
const globalDirName = path.basename(globalDir);
|
|
56
|
+
|
|
57
|
+
return fs.readdirSync(agentRoot)
|
|
58
|
+
.map((name) => ({ name, dir: path.join(agentRoot, name) }))
|
|
59
|
+
.filter(({ name, dir }) => name !== globalDirName && fs.existsSync(dir) && fs.statSync(dir).isDirectory())
|
|
60
|
+
.map(({ name, dir }) => ({ name, memoryFile: path.join(dir, MEMORY_FILE) }))
|
|
61
|
+
.filter(({ memoryFile }) => fs.existsSync(memoryFile));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function registerSyncMarkdownMemoriesCommand(
|
|
65
|
+
pi: ExtensionAPI,
|
|
66
|
+
dbManager: DatabaseManager,
|
|
67
|
+
globalDir: string,
|
|
68
|
+
): void {
|
|
69
|
+
pi.registerCommand('memory-sync-markdown', {
|
|
70
|
+
description: 'Backfill Markdown memories into the SQLite search store',
|
|
71
|
+
handler: async (_args, ctx: ExtensionCommandContext) => {
|
|
72
|
+
const counters: BackfillCounters = {
|
|
73
|
+
filesScanned: 0,
|
|
74
|
+
entriesScanned: 0,
|
|
75
|
+
imported: 0,
|
|
76
|
+
skipped: 0,
|
|
77
|
+
warnings: [],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
ctx.ui.notify('🔄 Scanning Markdown memory files for SQLite backfill...', 'info');
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const globalMemoryFile = path.join(globalDir, MEMORY_FILE);
|
|
84
|
+
const globalUserFile = path.join(globalDir, USER_FILE);
|
|
85
|
+
const globalFailureFile = path.join(globalDir, 'failures.md');
|
|
86
|
+
|
|
87
|
+
const importFile = (
|
|
88
|
+
filePath: string,
|
|
89
|
+
target: 'memory' | 'user' | 'failure',
|
|
90
|
+
project: string | null = null,
|
|
91
|
+
) => {
|
|
92
|
+
if (!fs.existsSync(filePath)) return;
|
|
93
|
+
counters.filesScanned++;
|
|
94
|
+
const entries = readEntries(filePath);
|
|
95
|
+
importEntries(dbManager, counters, entries, target, project);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
importFile(globalMemoryFile, 'memory');
|
|
99
|
+
importFile(globalUserFile, 'user');
|
|
100
|
+
importFile(globalFailureFile, 'failure');
|
|
101
|
+
|
|
102
|
+
const agentRoot = path.dirname(globalDir);
|
|
103
|
+
const projects = scanProjectDirs(agentRoot, globalDir);
|
|
104
|
+
for (const project of projects) {
|
|
105
|
+
importFile(project.memoryFile, 'memory', project.name);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let output = `\n✅ Markdown → SQLite sync complete!\n\n`;
|
|
109
|
+
output += `📊 Results:\n`;
|
|
110
|
+
output += `├─ Files scanned: ${counters.filesScanned}\n`;
|
|
111
|
+
output += `├─ Entries scanned: ${counters.entriesScanned}\n`;
|
|
112
|
+
output += `├─ Imported into SQLite: ${counters.imported}\n`;
|
|
113
|
+
output += `└─ Skipped as duplicates: ${counters.skipped}\n`;
|
|
114
|
+
|
|
115
|
+
if (projects.length > 0) {
|
|
116
|
+
output += `\n📁 Project memories scanned: ${projects.length}\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (counters.warnings.length > 0) {
|
|
120
|
+
output += `\n⚠️ Warnings (${counters.warnings.length}):\n`;
|
|
121
|
+
for (const warning of counters.warnings.slice(0, 5)) {
|
|
122
|
+
output += `├─ ${warning}\n`;
|
|
123
|
+
}
|
|
124
|
+
if (counters.warnings.length > 5) {
|
|
125
|
+
output += `└─ ... and ${counters.warnings.length - 5} more\n`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
output += `\n💡 Re-running this command is safe — existing SQLite rows are de-duplicated.`;
|
|
130
|
+
ctx.ui.notify(output, 'info');
|
|
131
|
+
} catch (err) {
|
|
132
|
+
ctx.ui.notify(`❌ Markdown sync failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -45,6 +45,8 @@ import { registerInterviewCommand } from "./handlers/interview.js";
|
|
|
45
45
|
import { registerSwitchProjectCommand } from "./handlers/switch-project.js";
|
|
46
46
|
import { registerIndexSessionsCommand } from "./handlers/index-sessions.js";
|
|
47
47
|
import { registerLearnMemoryCommand } from "./handlers/learn-memory.js";
|
|
48
|
+
import { registerSyncMarkdownMemoriesCommand } from "./handlers/sync-markdown-memories.js";
|
|
49
|
+
import { registerPreviewContextCommand } from "./handlers/preview-context.js";
|
|
48
50
|
import { loadConfig } from "./config.js";
|
|
49
51
|
import { detectProject } from "./project.js";
|
|
50
52
|
|
|
@@ -90,8 +92,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
92
|
}
|
|
91
93
|
});
|
|
92
94
|
|
|
93
|
-
// ── 3. Register the memory tool (with project store) ──
|
|
94
|
-
registerMemoryTool(pi, store, projectStore);
|
|
95
|
+
// ── 3. Register the memory tool (with project store + SQLite sync) ──
|
|
96
|
+
registerMemoryTool(pi, store, projectStore, dbManager, projectName);
|
|
95
97
|
|
|
96
98
|
// ── 4. Register the skill tool ──
|
|
97
99
|
registerSkillTool(pi, skillStore);
|
|
@@ -109,7 +111,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
109
111
|
registerConsolidateCommand(pi, store);
|
|
110
112
|
|
|
111
113
|
// ── 8. Setup correction detection ──
|
|
112
|
-
setupCorrectionDetector(pi, store, projectStore, config);
|
|
114
|
+
setupCorrectionDetector(pi, store, projectStore, config, dbManager);
|
|
113
115
|
|
|
114
116
|
// ── 9. Setup skill auto-trigger ──
|
|
115
117
|
setupSkillAutoTrigger(pi, store, skillStore, config);
|
|
@@ -120,6 +122,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
120
122
|
registerInterviewCommand(pi, store);
|
|
121
123
|
registerSwitchProjectCommand(pi);
|
|
122
124
|
registerLearnMemoryCommand(pi);
|
|
125
|
+
registerSyncMarkdownMemoriesCommand(pi, dbManager, globalDir);
|
|
126
|
+
registerPreviewContextCommand(pi, store, projectStore, skillStore, projectName);
|
|
123
127
|
|
|
124
128
|
// ── 11. SQLite session search + extended memory ──
|
|
125
129
|
registerSessionSearchTool(pi, dbManager);
|