pi-hermes-memory 0.7.7 → 0.7.9

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 CHANGED
@@ -103,7 +103,7 @@ The extension stores memory at two levels:
103
103
 
104
104
  | Tier | Location | What goes here | Available when |
105
105
  |---|---|---|---|
106
- | **Global** | `~/.pi/agent/memory/` | Facts that apply everywhere — your name, preferences, OS, tools | Searchable via `memory_search` |
106
+ | **Global** | `~/.pi/agent/pi-hermes-memory/` | Facts that apply everywhere — your name, preferences, OS, tools | Searchable via `memory_search` |
107
107
  | **Project** | `~/.pi/agent/projects-memory/<project>/` | Facts scoped to one codebase — architecture decisions, API quirks, team norms | Searchable when cwd matches the project |
108
108
 
109
109
  By default, full Markdown memories are **not** injected into the system prompt. The system prompt gets a full-detail `<memory-policy>` that tells the agent when to call `memory_search` and how to treat memory results. This keeps first-turn token usage low while preserving access to user, project, failure, correction, insight, preference, convention, and tool-quirk memories.
@@ -185,7 +185,7 @@ The agent also gets a `skill` tool for saving reusable procedures:
185
185
 
186
186
  Skills are stored in Pi-native locations:
187
187
 
188
- - Global skills: `~/.pi/agent/skills/<slug>/SKILL.md`
188
+ - Global skills: `~/.pi/agent/pi-hermes-memory/skills/<slug>/SKILL.md`
189
189
  - Project skills: `~/.pi/agent/projects-memory/<project>/skills/<slug>/SKILL.md`
190
190
 
191
191
  The extension classifies new skills automatically:
@@ -241,13 +241,13 @@ This lets Pi discover project skills as native skills without copying them into
241
241
  |---|---|---|---|
242
242
  | **memory** | `MEMORY.md` | Agent's notes — env facts, project conventions, tool quirks, lessons learned | 5,000 chars |
243
243
  | **user** | `USER.md` | User profile — name, preferences, communication style, habits | 5,000 chars |
244
- | **skills** | `~/.pi/agent/skills/<slug>/SKILL.md` or `projects-memory/<project>/skills/<slug>/SKILL.md` | Procedures — *how* to debug, deploy, test, or fix something | Unlimited |
244
+ | **skills** | `~/.pi/agent/pi-hermes-memory/skills/<slug>/SKILL.md` or `projects-memory/<project>/skills/<slug>/SKILL.md` | Procedures — *how* to debug, deploy, test, or fix something | Unlimited |
245
245
  | **extended** | `sessions.db` | Searchable memories beyond the core limit | Unlimited |
246
246
  | **sessions** | `sessions.db` | Past conversation history (searchable via FTS5) | Unlimited |
247
247
 
248
248
  ### Session History Search
249
249
 
250
- The extension indexes your Pi session history into a SQLite database with FTS5 full-text search. The agent can search across all past conversations using the `session_search` tool:
250
+ By default, the extension indexes your Pi session history into a SQLite database with FTS5 full-text search. The agent can search across all past conversations using the `session_search` tool:
251
251
 
252
252
  | Tool | What it does |
253
253
  |---|---|---|
@@ -260,6 +260,8 @@ Session history is indexed automatically on session shutdown. To bulk-import exi
260
260
  /memory-index-sessions
261
261
  ```
262
262
 
263
+ For users who prefer source anchors over snippets, `sessionSearch.variant` can be set to `anchors`. In that opt-in mode, the same `session_search` tool reads session JSONL files directly and accepts a Markdown request with fields such as `from`, `to`, `cwd`, and `limit`, plus `all`, `any`, and `exclude` lists. It returns plain text with `count`, an optional `message`, and compact `path:startLine-endLine` style anchors with short reasons instead of summaries or previews.
264
+
263
265
  ### Extended Memory Store
264
266
 
265
267
  The extension keeps Markdown memory as the human-readable source of truth, and mirrors successful writes into the SQLite-backed search store used by `memory_search`.
@@ -321,7 +323,7 @@ This means skills build up naturally over time without you having to ask.
321
323
  | Command | What it does |
322
324
  |---|---|
323
325
  | `/memory-insights` | Shows everything stored in memory and user profile |
324
- | `/memory-skills` | Lists global and active-project skills with their ids |
326
+ | `/memory-skills` | Opens an interactive skills manager for search, multi-select, move, and delete |
325
327
  | `/memory-consolidate` | Manually trigger memory consolidation to free space |
326
328
  | `/memory-interview` | Answer a few questions to pre-fill your user profile |
327
329
  | `/memory-switch-project` | List all project memories and their entry counts |
@@ -350,25 +352,34 @@ This means skills build up naturally over time without you having to ask.
350
352
  3. codes primarily in TypeScript
351
353
  ```
352
354
 
353
- ### `/memory-skills` Output
354
-
355
- ```
356
- ╔══════════════════════════════════════════════╗
357
- ║ 🧠 Procedural Skills ║
358
- ╚══════════════════════════════════════════════╝
359
-
360
- Global Skills
361
- ─────────────
362
- 📄 debug-typescript-errors
363
- Step-by-step approach to debugging TS errors in monorepos
364
- id: global:debug-typescript-errors
365
-
366
- Project Skills (pi-hermes-memory)
367
- ────────────────────────────────
368
- 📄 deploy-checklist
369
- Pre-deploy verification steps for this project
370
- id: project:pi-hermes-memory:deploy-checklist
371
- ```
355
+ ### `/memory-skills` Manager
356
+
357
+ `/memory-skills` now opens an interactive TUI modal for skill management.
358
+
359
+ Features:
360
+ - fuzzy search by skill name
361
+ - single-list view with scope badges (`[G]` global, `[P]` project)
362
+ - multi-select with spacebar
363
+ - batch move to global or current project
364
+ - batch delete with one confirmation
365
+ - inline action summaries for partial success/conflicts
366
+
367
+ Keybindings:
368
+ - `↑` / `↓` — move focus
369
+ - `space` — toggle selection
370
+ - `/` — focus search
371
+ - `tab` switch between search and list
372
+ - `g` — move selected skills to global
373
+ - `p` — move selected skills to project
374
+ - `d` — delete selected skills
375
+ - `a` — select all filtered skills
376
+ - `n` — clear selection
377
+ - `esc` — close the modal
378
+
379
+ Move behavior:
380
+ - moves are **conflict-safe**
381
+ - if the destination already contains the same slug, the conflicting skill stays in place
382
+ - batch moves use partial-success semantics: non-conflicting skills move, blocked skills are reported in the summary
372
383
 
373
384
  ## Configuration
374
385
 
@@ -381,8 +392,9 @@ Create `~/.pi/agent/hermes-memory-config.json`:
381
392
  "memoryCharLimit": 5000,
382
393
  "userCharLimit": 5000,
383
394
  "projectCharLimit": 5000,
384
- "memoryDir": "~/.pi/agent/memory",
395
+ "memoryDir": "~/.pi/agent/pi-hermes-memory",
385
396
  "projectsMemoryDir": "projects-memory",
397
+ "sessionSearch": { "variant": "legacy" },
386
398
  "nudgeInterval": 10,
387
399
  "nudgeToolCalls": 15,
388
400
  "reviewRecentMessages": 0,
@@ -409,8 +421,9 @@ Create `~/.pi/agent/hermes-memory-config.json`:
409
421
  | `memoryCharLimit` | `5000` | Max characters in MEMORY.md |
410
422
  | `userCharLimit` | `5000` | Max characters in USER.md |
411
423
  | `projectCharLimit` | `5000` | Max characters in project-scoped MEMORY.md |
412
- | `memoryDir` | `~/.pi/agent/memory` | Custom directory for memory files |
424
+ | `memoryDir` | `~/.pi/agent/pi-hermes-memory` | Custom directory for extension storage files |
413
425
  | `projectsMemoryDir` | `projects-memory` | Subdirectory under `~/.pi/agent/` for project-scoped memory |
426
+ | `sessionSearch` | `{ "variant": "legacy" }` | Session search implementation: `legacy` keeps the existing SQLite/FTS snippet search; `anchors` uses the opt-in Markdown request surface and returns compact JSONL line-range anchors from `~/.pi/agent/sessions/` |
414
427
  | `nudgeInterval` | `10` | Turns between auto-reviews |
415
428
  | `nudgeToolCalls` | `15` | Tool calls between auto-reviews (OR with turns) |
416
429
  | `reviewRecentMessages` | `0` | Recent messages included in background review (`0` = all) |
@@ -435,11 +448,16 @@ Create `~/.pi/agent/hermes-memory-config.json`:
435
448
 
436
449
  ```
437
450
  ~/.pi/agent/
438
- ├── skills/ ← Global Pi-native skills
439
- │ ├── debug-typescript-errors/
440
- │ └── SKILL.md
441
- └── testing-checklist/
442
- └── SKILL.md
451
+ ├── pi-hermes-memory/ ← Global extension storage root
452
+ │ ├── MEMORY.md ← Agent's personal notes (env facts, patterns, lessons)
453
+ ├── USER.md ← User profile (name, preferences, habits)
454
+ ├── sessions.db ← SQLite database (session history + extended memory)
455
+ ├── skills/ ← Global extension-managed skills
456
+ │ │ ├── debug-typescript-errors/
457
+ │ │ │ └── SKILL.md
458
+ │ │ └── testing-checklist/
459
+ │ │ └── SKILL.md
460
+ │ └── .skills-migrated-to-extension-storage
443
461
  ├── projects-memory/ ← ALL project-scoped memories (one subfolder per project)
444
462
  │ ├── my-project/
445
463
  │ │ ├── MEMORY.md
@@ -448,11 +466,6 @@ Create `~/.pi/agent/hermes-memory-config.json`:
448
466
  │ │ └── SKILL.md
449
467
  │ └── another-project/
450
468
  │ └── MEMORY.md
451
- ├── memory/ ← Global memory
452
- │ ├── MEMORY.md ← Agent's personal notes (env facts, patterns, lessons)
453
- │ ├── USER.md ← User profile (name, preferences, habits)
454
- │ ├── sessions.db ← SQLite database (session history + extended memory)
455
- │ └── .skills-migrated-to-pi-native
456
469
  ├── hermes-memory-config.json
457
470
  └── ...
458
471
  ```
@@ -471,8 +484,9 @@ The `sessions.db` SQLite database stores session history and extended memory ent
471
484
  - **Older Markdown memories may need backfill**: If you saved memories before the SQLite mirror existed or search looks stale, run `/memory-sync-markdown`.
472
485
  - **Core memory limits still apply**: SQLite search mirroring does not bypass the 5,000-char core Markdown limit. If consolidation cannot free space, the write fails instead of becoming SQLite-only memory invisibly.
473
486
  - **System prompts are invisible**: Pi's TUI does not display the system prompt. Use `/memory-preview-context` to inspect whether policy-only or legacy memory injection is active.
474
- - **Project skill visibility depends on Pi discovery cycles**: project skills are exposed through `resources_discover` using the active project's `skills/` path. If a new skill doesn't show up immediately in a running session, trigger a reload/new session so Pi refreshes discovered resources.
475
- - **Skills are agent-generated**: Skills are created by the agent based on its experience. They may not always be perfectly structured. You can edit or delete them in `~/.pi/agent/skills/` or the active project's `skills/` folder.
487
+ - **Project skill visibility depends on Pi discovery cycles**: project skills are exposed through `resources_discover` using the active project's `skills/` path. If a moved or newly created project skill doesn't show up immediately in a running session, trigger a reload/new session so Pi refreshes discovered resources.
488
+ - **Project move requires active project context**: in `/memory-skills`, the `p` hotkey is disabled when Pi is not currently in a detected project directory.
489
+ - **Skills are agent-generated**: Skills are created by the agent based on its experience. They may not always be perfectly structured. You can move, delete, or still edit them directly in `~/.pi/agent/pi-hermes-memory/skills/` or the active project's `skills/` folder.
476
490
 
477
491
  ## Architecture
478
492
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hermes-memory",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "🧠 Persistent memory + 🔍 session search + 🛡️ secret scanning for Pi. Token-aware policy-only memory by default, SQLite FTS5 search, auto-consolidation, procedural skills. 368 tests. Ported from Hermes agent.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -56,6 +56,7 @@
56
56
  "typescript": "^6.0.3"
57
57
  },
58
58
  "dependencies": {
59
+ "@earendil-works/pi-tui": "^0.74.0",
59
60
  "better-sqlite3": "^12.9.0"
60
61
  }
61
62
  }
package/src/config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import * as os from "node:os";
4
- import type { MemoryConfig, MemoryOverflowStrategy } from "./types.js";
4
+ import type { MemoryConfig, MemoryOverflowStrategy, SessionSearchVariant } from "./types.js";
5
5
  import {
6
6
  DEFAULT_MEMORY_CHAR_LIMIT,
7
7
  DEFAULT_USER_CHAR_LIMIT,
@@ -18,11 +18,16 @@ import {
18
18
  } from "./constants.js";
19
19
 
20
20
  const MEMORY_OVERFLOW_STRATEGIES: readonly MemoryOverflowStrategy[] = ["auto-consolidate", "reject", "fifo-evict"];
21
+ const SESSION_SEARCH_VARIANTS: readonly SessionSearchVariant[] = ["legacy", "anchors"];
21
22
 
22
23
  function isMemoryOverflowStrategy(value: unknown): value is MemoryOverflowStrategy {
23
24
  return typeof value === "string" && MEMORY_OVERFLOW_STRATEGIES.includes(value as MemoryOverflowStrategy);
24
25
  }
25
26
 
27
+ function isSessionSearchVariant(value: unknown): value is SessionSearchVariant {
28
+ return typeof value === "string" && SESSION_SEARCH_VARIANTS.includes(value as SessionSearchVariant);
29
+ }
30
+
26
31
  const DEFAULT_CONFIG: MemoryConfig = {
27
32
  memoryMode: "policy-only",
28
33
  memoryPolicyStyle: "full",
@@ -45,6 +50,7 @@ const DEFAULT_CONFIG: MemoryConfig = {
45
50
  consolidationTimeoutMs: DEFAULT_CONSOLIDATION_TIMEOUT_MS,
46
51
  nudgeToolCalls: DEFAULT_NUDGE_TOOL_CALLS,
47
52
  projectsMemoryDir: DEFAULT_PROJECTS_MEMORY_DIR,
53
+ sessionSearch: { variant: "legacy" },
48
54
  };
49
55
 
50
56
  export const DEFAULT_CONFIG_PATH = path.join(
@@ -107,6 +113,13 @@ export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
107
113
  if (typeof parsed.projectCharLimit === "number") config.projectCharLimit = parsed.projectCharLimit;
108
114
  if (typeof parsed.memoryDir === "string") config.memoryDir = parsed.memoryDir;
109
115
  if (typeof parsed.projectsMemoryDir === "string") config.projectsMemoryDir = parsed.projectsMemoryDir;
116
+ if (
117
+ typeof parsed.sessionSearch === "object" &&
118
+ parsed.sessionSearch !== null &&
119
+ isSessionSearchVariant(parsed.sessionSearch.variant)
120
+ ) {
121
+ config.sessionSearch = { variant: parsed.sessionSearch.variant };
122
+ }
110
123
  if (hasMemoryOverflowStrategy) {
111
124
  config.autoConsolidate = config.memoryOverflowStrategy === "auto-consolidate";
112
125
  } else if (hasLegacyAutoConsolidate) {
@@ -0,0 +1,101 @@
1
+ import fs from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export interface ExtensionRootMigrationResult {
6
+ moved: number;
7
+ merged: number;
8
+ skipped: number;
9
+ warnings: string[];
10
+ }
11
+
12
+ async function pathExists(filePath: string): Promise<boolean> {
13
+ try {
14
+ await fs.access(filePath);
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ async function moveFileSafe(source: string, target: string): Promise<void> {
22
+ await fs.mkdir(path.dirname(target), { recursive: true });
23
+
24
+ try {
25
+ await fs.rename(source, target);
26
+ return;
27
+ } catch (error) {
28
+ const code = (error as NodeJS.ErrnoException)?.code;
29
+ if (code !== "EXDEV") throw error;
30
+ }
31
+
32
+ await fs.copyFile(source, target);
33
+ await fs.unlink(source);
34
+ }
35
+
36
+ async function moveDirContents(sourceDir: string, targetDir: string, result: ExtensionRootMigrationResult): Promise<void> {
37
+ await fs.mkdir(targetDir, { recursive: true });
38
+
39
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true });
40
+ for (const entry of entries) {
41
+ const sourcePath = path.join(sourceDir, entry.name);
42
+ const targetPath = path.join(targetDir, entry.name);
43
+
44
+ if (!await pathExists(targetPath)) {
45
+ try {
46
+ await moveFileSafe(sourcePath, targetPath);
47
+ result.moved++;
48
+ } catch (error) {
49
+ result.warnings.push(`${sourcePath}: ${error instanceof Error ? error.message : String(error)}`);
50
+ }
51
+ continue;
52
+ }
53
+
54
+ if (entry.isDirectory()) {
55
+ await moveDirContents(sourcePath, targetPath, result);
56
+ result.merged++;
57
+ try {
58
+ const remaining = await fs.readdir(sourcePath);
59
+ if (remaining.length === 0) await fs.rmdir(sourcePath);
60
+ } catch {
61
+ // best effort
62
+ }
63
+ continue;
64
+ }
65
+
66
+ result.skipped++;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Move legacy extension assets from ~/.pi/agent/memory into
72
+ * ~/.pi/agent/pi-hermes-memory. Existing destination files win.
73
+ */
74
+ export async function migrateExtensionRoot(
75
+ legacyRoot: string,
76
+ targetRoot: string,
77
+ ): Promise<ExtensionRootMigrationResult> {
78
+ const result: ExtensionRootMigrationResult = {
79
+ moved: 0,
80
+ merged: 0,
81
+ skipped: 0,
82
+ warnings: [],
83
+ };
84
+
85
+ if (path.resolve(legacyRoot) === path.resolve(targetRoot)) return result;
86
+ if (!existsSync(legacyRoot)) return result;
87
+
88
+ await fs.mkdir(targetRoot, { recursive: true });
89
+ await moveDirContents(legacyRoot, targetRoot, result);
90
+
91
+ try {
92
+ const remaining = await fs.readdir(legacyRoot);
93
+ if (remaining.length === 0) {
94
+ await fs.rmdir(legacyRoot);
95
+ }
96
+ } catch {
97
+ // best effort
98
+ }
99
+
100
+ return result;
101
+ }
@@ -34,7 +34,7 @@ export function registerIndexSessionsCommand(pi: ExtensionAPI): void {
34
34
 
35
35
  ctx.ui.notify(`📁 Found ${totalFiles} session files across ${projectDirs.length} projects\n⏳ Indexing...`, 'info');
36
36
 
37
- const memoryDir = path.join(os.homedir(), '.pi', 'agent', 'memory');
37
+ const memoryDir = path.join(os.homedir(), '.pi', 'agent', 'pi-hermes-memory');
38
38
  const dbManager = new DatabaseManager(memoryDir);
39
39
 
40
40
  try {