pi-hermes-memory 0.6.6 → 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.
@@ -41,7 +41,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
41
41
  lines.push(" User: Who you are — name, preferences, communication style");
42
42
  lines.push(" Failures: What didn't work — corrections, failures, insights");
43
43
  lines.push(" Skills: Procedures — how to debug, deploy, test");
44
- lines.push(" Extended: Searchable memories beyond the core limit");
44
+ lines.push(" Extended: SQLite search mirror for Markdown memory + backfill");
45
45
  lines.push("");
46
46
  lines.push(" Memory Categories:");
47
47
  lines.push(" ─────────────────");
@@ -70,7 +70,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
70
70
  lines.push(" Search past conversations across all sessions");
71
71
  lines.push("");
72
72
  lines.push(" memory_search");
73
- lines.push(" Search extended memory store (unlimited)");
73
+ lines.push(" Search the SQLite-backed memory mirror/store");
74
74
  lines.push(" Filters: project, target, category");
75
75
  lines.push(" Categories: failure, correction, insight, preference, convention, tool-quirk");
76
76
  }
@@ -87,6 +87,8 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
87
87
  lines.push(" /memory-interview Answer questions to pre-fill profile");
88
88
  lines.push(" /memory-switch-project List all project memories");
89
89
  lines.push(" /memory-index-sessions Import past sessions for search");
90
+ lines.push(" /memory-sync-markdown Backfill Markdown memories into SQLite");
91
+ lines.push(" /memory-preview-context Show injected memory/skill prompt blocks");
90
92
  }
91
93
 
92
94
  if (section.startsWith("✅")) {
@@ -115,12 +117,13 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
115
117
  lines.push(" ╚══════════════════════════════════════════════╝");
116
118
  lines.push("");
117
119
  lines.push(" 1. Session starts → Core memory + recent failures injected");
118
- lines.push(" 2. During conversation → Agent saves via memory tool");
119
- lines.push(" 3. Every 10 turns Background review saves items");
120
- lines.push(" 4. On correction Immediate save as [correction] category");
121
- lines.push(" 5. On failure Saves what failed + why");
122
- lines.push(" 6. When full Auto-consolidation merges");
123
- lines.push(" 7. Session ends Final flush");
120
+ lines.push(" 2. During conversation → Agent saves to Markdown memory");
121
+ lines.push(" 3. Successful saves Best-effort SQLite search sync");
122
+ lines.push(" 4. Every 10 turns Background review saves items");
123
+ lines.push(" 5. On correction Immediate save as [correction] category");
124
+ lines.push(" 6. On failure Saves what failed + why");
125
+ lines.push(" 7. When full Auto-consolidation merges");
126
+ lines.push(" 8. Session ends → Final flush");
124
127
  }
125
128
 
126
129
  if (section.startsWith("🏗️")) {
@@ -137,10 +140,11 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
137
140
  lines.push(" │ Project memory — When cwd matches │");
138
141
  lines.push(" └─────────────────────────────────────┘");
139
142
  lines.push("");
140
- lines.push(" Searchable on Demand (Unlimited)");
143
+ lines.push(" Searchable on Demand (SQLite mirror/store)");
141
144
  lines.push(" ┌─────────────────────────────────────┐");
142
145
  lines.push(" │ session_search(\"auth flow\") │");
143
146
  lines.push(" │ memory_search(\"testing patterns\") │");
147
+ lines.push(" │ /memory-sync-markdown (backfill old md)│");
144
148
  lines.push(" │ memory_search(\"auth\", cat:\"failure\")│");
145
149
  lines.push(" └─────────────────────────────────────┘");
146
150
  }
@@ -153,9 +157,11 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
153
157
  lines.push("");
154
158
  lines.push(" \"Memory is full\"");
155
159
  lines.push(" → /memory-consolidate to merge entries");
160
+ lines.push(" → If it still fails, the save does NOT silently become SQLite-only");
156
161
  lines.push("");
157
162
  lines.push(" \"Can't find something\"");
158
- lines.push(" → memory_search to search extended store");
163
+ lines.push(" → memory_search to search the SQLite mirror/store");
164
+ lines.push(" → /memory-sync-markdown to import older Markdown entries");
159
165
  lines.push("");
160
166
  lines.push(" \"Agent forgot something\"");
161
167
  lines.push(" → Check /memory-insights, tell agent \"remember X\"");
@@ -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);
@@ -19,6 +19,8 @@ import {
19
19
  ENTRY_DELIMITER,
20
20
  DEFAULT_MEMORY_CHAR_LIMIT,
21
21
  DEFAULT_USER_CHAR_LIMIT,
22
+ DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS,
23
+ DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
22
24
  MEMORY_FILE,
23
25
  USER_FILE,
24
26
  } from "../constants.js";
@@ -280,12 +282,17 @@ export class MemoryStore {
280
282
  if (this.snapshot.user) parts.push(this.fenceBlock(this.snapshot.user));
281
283
 
282
284
  // Add recent failure memories
283
- const recentFailures = this.getFailureEntries(7);
284
- if (recentFailures.length > 0) {
285
- const maxFailures = 5;
286
- const failures = recentFailures.slice(0, maxFailures);
287
- const failureBlock = this.renderFailureBlock(failures);
288
- parts.push(this.fenceBlock(failureBlock));
285
+ if (this.config.failureInjectionEnabled !== false) {
286
+ const maxAgeDays = this.config.failureInjectionMaxAgeDays ?? DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS;
287
+ const maxFailures = this.config.failureInjectionMaxEntries ?? DEFAULT_FAILURE_INJECTION_MAX_ENTRIES;
288
+ const recentFailures = this.getFailureEntries(maxAgeDays);
289
+ if (recentFailures.length > 0) {
290
+ const failures = recentFailures.slice(0, maxFailures);
291
+ if (failures.length > 0) {
292
+ const failureBlock = this.renderFailureBlock(failures);
293
+ parts.push(this.fenceBlock(failureBlock));
294
+ }
295
+ }
289
296
  }
290
297
 
291
298
  return parts.join("\n\n");