claude-launchpad 1.3.0 → 1.4.0

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.
Files changed (44) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-UJP5PJTA.js → chunk-ADZ45KHX.js} +3 -3
  3. package/dist/{chunk-V4NXT4KB.js → chunk-DXDOVWOA.js} +64 -8
  4. package/dist/chunk-DXDOVWOA.js.map +1 -0
  5. package/dist/{chunk-N6X3E5AX.js → chunk-F6SLV2FR.js} +24 -5
  6. package/dist/chunk-F6SLV2FR.js.map +1 -0
  7. package/dist/{chunk-AR64LWGW.js → chunk-RJEPJ4JE.js} +25 -4
  8. package/dist/chunk-RJEPJ4JE.js.map +1 -0
  9. package/dist/{chunk-YXPJDIMK.js → chunk-UQOBOHKN.js} +33 -2
  10. package/dist/chunk-UQOBOHKN.js.map +1 -0
  11. package/dist/{chunk-J765H3HZ.js → chunk-WOC2PKT2.js} +2 -2
  12. package/dist/{chunk-F5PNKQKW.js → chunk-YZ53W47Z.js} +7 -2
  13. package/dist/chunk-YZ53W47Z.js.map +1 -0
  14. package/dist/cli.js +24 -13
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/memory/server.js +5 -5
  17. package/dist/{context-VAXF3EW3.js → context-NCV46PTY.js} +6 -6
  18. package/dist/{install-M3JWBGMK.js → install-DORMJYCU.js} +6 -6
  19. package/dist/install-DORMJYCU.js.map +1 -0
  20. package/dist/{pull-ZQFCMK46.js → pull-UYLUGILU.js} +9 -9
  21. package/dist/{push-5ZJNWAE7.js → push-HWXJGSTL.js} +40 -17
  22. package/dist/push-HWXJGSTL.js.map +1 -0
  23. package/dist/{require-deps-QW2IU6I3.js → require-deps-RUXTMQUV.js} +3 -3
  24. package/dist/{stats-NPXPJNBO.js → stats-DFV24AJW.js} +7 -7
  25. package/dist/{sync-clean-JQLVE4WU.js → sync-clean-GFX5F55E.js} +2 -2
  26. package/dist/{sync-status-IYG7ZYC5.js → sync-status-VXMDWDRG.js} +9 -9
  27. package/dist/{tui-74FMIMUM.js → tui-ELOJ37ZA.js} +5 -5
  28. package/package.json +1 -1
  29. package/dist/chunk-AR64LWGW.js.map +0 -1
  30. package/dist/chunk-F5PNKQKW.js.map +0 -1
  31. package/dist/chunk-N6X3E5AX.js.map +0 -1
  32. package/dist/chunk-V4NXT4KB.js.map +0 -1
  33. package/dist/chunk-YXPJDIMK.js.map +0 -1
  34. package/dist/install-M3JWBGMK.js.map +0 -1
  35. package/dist/push-5ZJNWAE7.js.map +0 -1
  36. /package/dist/{chunk-UJP5PJTA.js.map → chunk-ADZ45KHX.js.map} +0 -0
  37. /package/dist/{chunk-J765H3HZ.js.map → chunk-WOC2PKT2.js.map} +0 -0
  38. /package/dist/{context-VAXF3EW3.js.map → context-NCV46PTY.js.map} +0 -0
  39. /package/dist/{pull-ZQFCMK46.js.map → pull-UYLUGILU.js.map} +0 -0
  40. /package/dist/{require-deps-QW2IU6I3.js.map → require-deps-RUXTMQUV.js.map} +0 -0
  41. /package/dist/{stats-NPXPJNBO.js.map → stats-DFV24AJW.js.map} +0 -0
  42. /package/dist/{sync-clean-JQLVE4WU.js.map → sync-clean-GFX5F55E.js.map} +0 -0
  43. /package/dist/{sync-status-IYG7ZYC5.js.map → sync-status-VXMDWDRG.js.map} +0 -0
  44. /package/dist/{tui-74FMIMUM.js.map → tui-ELOJ37ZA.js.map} +0 -0
package/README.md CHANGED
@@ -229,12 +229,12 @@ Data stays in `~/.agentic-memory/memory.db`. Sync requires the [GitHub CLI](http
229
229
  | `push` | Push current project's memories to a private GitHub Gist |
230
230
  | `pull` | Pull current project's memories from a private GitHub Gist |
231
231
  | `push --all` | Push all projects |
232
- | `pull --all` | Pull all projects |
232
+ | `pull --all` | Pull every project already set up on this machine (skips new ones) |
233
233
  | `push -y` | Skip confirmation prompt |
234
234
  | `sync status` | Show local vs remote memory counts |
235
235
  | `sync clean <project>` | Remove a project from the sync gist |
236
236
 
237
- Sync stores one file per project inside a single private gist. Push/pull auto-detects the current project from your working directory. On a new device, the gist is auto-discovered from your GitHub account (no config to copy).
237
+ Sync stores one file per project inside a single private gist. Push/pull auto-detects the current project from your working directory. On a new device, the gist is auto-discovered from your GitHub account (no config to copy). Deletions propagate. Delete on one machine; the next machine to pull drops it too.
238
238
 
239
239
  ## Hooks
240
240
 
@@ -3,14 +3,14 @@ import {
3
3
  MemoryRepo,
4
4
  RelationRepo,
5
5
  SearchRepo
6
- } from "./chunk-V4NXT4KB.js";
6
+ } from "./chunk-DXDOVWOA.js";
7
7
  import {
8
8
  closeDatabase,
9
9
  createDatabase,
10
10
  loadConfig,
11
11
  migrate,
12
12
  resolveDataDir
13
- } from "./chunk-AR64LWGW.js";
13
+ } from "./chunk-RJEPJ4JE.js";
14
14
 
15
15
  // src/commands/memory/subcommands/init-storage.ts
16
16
  function initStorage(dbPath) {
@@ -32,4 +32,4 @@ function initStorage(dbPath) {
32
32
  export {
33
33
  initStorage
34
34
  };
35
- //# sourceMappingURL=chunk-UJP5PJTA.js.map
35
+ //# sourceMappingURL=chunk-ADZ45KHX.js.map
@@ -2,6 +2,9 @@
2
2
 
3
3
  // src/commands/memory/storage/memory-repo.ts
4
4
  import { randomUUID, createHash } from "crypto";
5
+ function rowToTombstone(row) {
6
+ return { id: row.id, project: row.project, deletedAt: row.deleted_at };
7
+ }
5
8
  function safeParseTags(raw) {
6
9
  try {
7
10
  const parsed = JSON.parse(raw);
@@ -92,7 +95,23 @@ var MemoryRepo = class {
92
95
  `),
93
96
  getAllStrictProject: db.prepare(
94
97
  "SELECT * FROM memories WHERE project = ? ORDER BY created_at DESC"
95
- )
98
+ ),
99
+ getIdProjectByType: db.prepare("SELECT id, project FROM memories WHERE type = ?"),
100
+ getIdProjectByProject: db.prepare("SELECT id, project FROM memories WHERE project = ?"),
101
+ tombstoneUpsert: db.prepare(`
102
+ INSERT INTO memory_tombstones (id, project, deleted_at)
103
+ VALUES (?, ?, ?)
104
+ ON CONFLICT(id) DO UPDATE SET
105
+ project = excluded.project,
106
+ deleted_at = excluded.deleted_at
107
+ WHERE excluded.deleted_at > memory_tombstones.deleted_at
108
+ `),
109
+ tombstoneGetById: db.prepare("SELECT * FROM memory_tombstones WHERE id = ?"),
110
+ tombstoneGetAll: db.prepare("SELECT * FROM memory_tombstones ORDER BY deleted_at DESC"),
111
+ tombstoneGetByProjectStrict: db.prepare(
112
+ "SELECT * FROM memory_tombstones WHERE project = ? ORDER BY deleted_at DESC"
113
+ ),
114
+ tombstoneDelete: db.prepare("DELETE FROM memory_tombstones WHERE id = ?")
96
115
  };
97
116
  }
98
117
  create(input, _embedding = null) {
@@ -207,16 +226,53 @@ var MemoryRepo = class {
207
226
  return result.changes > 0;
208
227
  }
209
228
  hardDelete(id) {
210
- const result = this.#stmts.hardDelete.run(id);
211
- return result.changes > 0;
229
+ const deleteAndTombstone = this.db.transaction((memoryId) => {
230
+ const memory = this.getById(memoryId);
231
+ if (!memory) return 0;
232
+ this.#stmts.tombstoneUpsert.run(memoryId, memory.project, (/* @__PURE__ */ new Date()).toISOString());
233
+ return this.#stmts.hardDelete.run(memoryId).changes;
234
+ });
235
+ return deleteAndTombstone(id) > 0;
212
236
  }
213
237
  deleteByType(type) {
214
- const result = this.#stmts.deleteByType.run(type);
215
- return result.changes;
238
+ const deleteAndTombstone = this.db.transaction((t) => {
239
+ const rows = this.#stmts.getIdProjectByType.all(t);
240
+ const now = (/* @__PURE__ */ new Date()).toISOString();
241
+ for (const row of rows) {
242
+ this.#stmts.tombstoneUpsert.run(row.id, row.project, now);
243
+ }
244
+ return this.#stmts.deleteByType.run(t).changes;
245
+ });
246
+ return deleteAndTombstone(type);
216
247
  }
217
248
  deleteByProject(project) {
218
- const result = this.#stmts.deleteByProject.run(project);
219
- return result.changes;
249
+ const deleteAndTombstone = this.db.transaction((p) => {
250
+ const rows = this.#stmts.getIdProjectByProject.all(p);
251
+ const now = (/* @__PURE__ */ new Date()).toISOString();
252
+ for (const row of rows) {
253
+ this.#stmts.tombstoneUpsert.run(row.id, row.project, now);
254
+ }
255
+ return this.#stmts.deleteByProject.run(p).changes;
256
+ });
257
+ return deleteAndTombstone(project);
258
+ }
259
+ getTombstone(id) {
260
+ const row = this.#stmts.tombstoneGetById.get(id);
261
+ return row ? rowToTombstone(row) : null;
262
+ }
263
+ getAllTombstones() {
264
+ const rows = this.#stmts.tombstoneGetAll.all();
265
+ return rows.map(rowToTombstone);
266
+ }
267
+ getTombstonesByProject(project) {
268
+ const rows = this.#stmts.tombstoneGetByProjectStrict.all(project);
269
+ return rows.map(rowToTombstone);
270
+ }
271
+ upsertTombstone(id, project, deletedAt) {
272
+ this.#stmts.tombstoneUpsert.run(id, project, deletedAt);
273
+ }
274
+ deleteTombstone(id) {
275
+ this.#stmts.tombstoneDelete.run(id);
220
276
  }
221
277
  count(project) {
222
278
  if (project) {
@@ -486,4 +542,4 @@ export {
486
542
  RelationRepo,
487
543
  SearchRepo
488
544
  };
489
- //# sourceMappingURL=chunk-V4NXT4KB.js.map
545
+ //# sourceMappingURL=chunk-DXDOVWOA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/memory/storage/memory-repo.ts","../src/commands/memory/storage/relation-repo.ts","../src/commands/memory/storage/search-repo.ts"],"sourcesContent":["import type Database from 'better-sqlite3';\nimport type { Memory, MemoryType, MemorySource, StoreInput, SyncMemoryRow, Tombstone } from '../types.js';\nimport { randomUUID, createHash } from 'node:crypto';\n\ninterface TombstoneRow {\n id: string;\n project: string | null;\n deleted_at: string;\n}\n\nfunction rowToTombstone(row: TombstoneRow): Tombstone {\n return { id: row.id, project: row.project, deletedAt: row.deleted_at };\n}\n\nfunction safeParseTags(raw: string): string[] {\n try {\n const parsed = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed.filter(t => typeof t === 'string') : [];\n } catch {\n return [];\n }\n}\n\n// ── Row shape from SQLite ─────────────────────────────────────\n\ninterface MemoryRow {\n id: string;\n type: string;\n title: string | null;\n content: string;\n context: string | null;\n source: string | null;\n project: string | null;\n tags: string;\n importance: number;\n created_at: string;\n updated_at: string;\n access_count: number;\n last_accessed: string | null;\n injection_count: number;\n embedding: Buffer | null;\n}\n\nfunction rowToMemory(row: MemoryRow): Memory {\n return {\n id: row.id,\n type: row.type as MemoryType,\n title: row.title,\n content: row.content,\n context: row.context,\n source: row.source as MemorySource | null,\n project: row.project,\n tags: safeParseTags(row.tags),\n importance: row.importance,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n accessCount: row.access_count,\n lastAccessed: row.last_accessed,\n injectionCount: row.injection_count,\n };\n}\n\n// ── Repository ────────────────────────────────────────────────\n\nexport class MemoryRepo {\n readonly #stmts;\n readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n this.#stmts = {\n insert: db.prepare(`\n INSERT OR IGNORE INTO memories (id, type, title, content, context, source, project, tags, importance, created_at, updated_at, embedding, content_hash)\n VALUES (@id, @type, @title, @content, @context, @source, @project, @tags, @importance, @createdAt, @updatedAt, @embedding, @contentHash)\n `),\n getById: db.prepare('SELECT * FROM memories WHERE id = ?'),\n getAll: db.prepare('SELECT * FROM memories ORDER BY created_at DESC'),\n getAllByProject: db.prepare('SELECT * FROM memories WHERE project = ? OR project IS NULL ORDER BY created_at DESC'),\n getByType: db.prepare('SELECT * FROM memories WHERE type = ? ORDER BY created_at DESC'),\n getByTypeAndProject: db.prepare('SELECT * FROM memories WHERE type = ? AND (project = ? OR project IS NULL) ORDER BY created_at DESC'),\n getRecent: db.prepare('SELECT * FROM memories ORDER BY created_at DESC LIMIT ?'),\n getRecentByProject: db.prepare('SELECT * FROM memories WHERE project = ? OR project IS NULL ORDER BY created_at DESC LIMIT ?'),\n getRecentByTypeAndProject: db.prepare('SELECT * FROM memories WHERE type = ? AND (project = ? OR project IS NULL) ORDER BY created_at DESC LIMIT ?'),\n update: db.prepare(`\n UPDATE memories\n SET title = @title, content = @content, context = @context, tags = @tags,\n importance = @importance, updated_at = @updatedAt, embedding = @embedding,\n content_hash = @contentHash\n WHERE id = @id\n `),\n updateImportance: db.prepare('UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?'),\n updateImportanceOnly: db.prepare('UPDATE memories SET importance = ? WHERE id = ?'),\n incrementAccess: db.prepare(`\n UPDATE memories SET access_count = access_count + 1, last_accessed = ? WHERE id = ?\n `),\n incrementInjection: db.prepare(`\n UPDATE memories SET injection_count = injection_count + 1 WHERE id = ?\n `),\n softDelete: db.prepare('UPDATE memories SET importance = 0, updated_at = ? WHERE id = ?'),\n hardDelete: db.prepare('DELETE FROM memories WHERE id = ?'),\n deleteByType: db.prepare('DELETE FROM memories WHERE type = ?'),\n deleteByProject: db.prepare('DELETE FROM memories WHERE project = ?'),\n count: db.prepare('SELECT COUNT(*) as count FROM memories'),\n countByProject: db.prepare('SELECT COUNT(*) as count FROM memories WHERE project = ?'),\n countByType: db.prepare('SELECT type, COUNT(*) as count FROM memories GROUP BY type'),\n projectCounts: db.prepare('SELECT COALESCE(project, \\'_global\\') as project, COUNT(*) as count FROM memories GROUP BY project'),\n dateRange: db.prepare('SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories'),\n topInjected: db.prepare(`\n SELECT id, title, injection_count FROM memories\n WHERE injection_count > 0 ORDER BY injection_count DESC LIMIT ?\n `),\n insertSync: db.prepare(`\n INSERT OR IGNORE INTO memories\n (id, type, title, content, context, source, project, tags, importance,\n access_count, injection_count, created_at, updated_at, last_accessed, embedding, content_hash)\n VALUES (@id, @type, @title, @content, @context, @source, @project, @tags, @importance,\n @accessCount, @injectionCount, @createdAt, @updatedAt, @lastAccessed, NULL, @contentHash)\n `),\n updateSync: db.prepare(`\n UPDATE memories SET\n title = @title, content = @content, context = @context,\n source = @source, project = @project, tags = @tags,\n importance = @importance, access_count = @accessCount,\n injection_count = @injectionCount, updated_at = @updatedAt,\n last_accessed = @lastAccessed, content_hash = @contentHash\n WHERE id = @id\n `),\n getAllStrictProject: db.prepare(\n 'SELECT * FROM memories WHERE project = ? ORDER BY created_at DESC'\n ),\n getIdProjectByType: db.prepare('SELECT id, project FROM memories WHERE type = ?'),\n getIdProjectByProject: db.prepare('SELECT id, project FROM memories WHERE project = ?'),\n tombstoneUpsert: db.prepare(`\n INSERT INTO memory_tombstones (id, project, deleted_at)\n VALUES (?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n project = excluded.project,\n deleted_at = excluded.deleted_at\n WHERE excluded.deleted_at > memory_tombstones.deleted_at\n `),\n tombstoneGetById: db.prepare('SELECT * FROM memory_tombstones WHERE id = ?'),\n tombstoneGetAll: db.prepare('SELECT * FROM memory_tombstones ORDER BY deleted_at DESC'),\n tombstoneGetByProjectStrict: db.prepare(\n 'SELECT * FROM memory_tombstones WHERE project = ? ORDER BY deleted_at DESC'\n ),\n tombstoneDelete: db.prepare('DELETE FROM memory_tombstones WHERE id = ?'),\n };\n }\n\n create(input: StoreInput, _embedding: Buffer | null = null): Memory | null {\n const now = new Date().toISOString();\n const id = randomUUID();\n const contentHash = createHash('sha256').update(input.content).digest('hex');\n\n const params = {\n id,\n type: input.type,\n title: input.title ?? null,\n content: input.content,\n context: input.context ?? null,\n source: input.source,\n project: input.project ?? null,\n tags: JSON.stringify(input.tags),\n importance: input.importance,\n createdAt: now,\n updatedAt: now,\n embedding: null,\n contentHash,\n };\n\n const result = this.#stmts.insert.run(params);\n if (result.changes === 0) return null;\n\n const row: MemoryRow = {\n id,\n type: input.type,\n title: input.title ?? null,\n content: input.content,\n context: input.context ?? null,\n source: input.source,\n project: input.project ?? null,\n tags: JSON.stringify(input.tags),\n importance: input.importance,\n created_at: now,\n updated_at: now,\n access_count: 0,\n last_accessed: null,\n injection_count: 0,\n embedding: null,\n };\n\n return rowToMemory(row);\n }\n\n getById(id: string): Memory | undefined {\n const row = this.#stmts.getById.get(id) as MemoryRow | undefined;\n return row ? rowToMemory(row) : undefined;\n }\n\n getAll(project?: string): readonly Memory[] {\n if (project) {\n const rows = this.#stmts.getAllByProject.all(project) as MemoryRow[];\n return rows.map(rowToMemory);\n }\n const rows = this.#stmts.getAll.all() as MemoryRow[];\n return rows.map(rowToMemory);\n }\n\n getRecent(limit: number, project?: string, type?: MemoryType): readonly Memory[] {\n if (type && project) {\n const rows = this.#stmts.getRecentByTypeAndProject.all(type, project, limit) as MemoryRow[];\n return rows.map(rowToMemory);\n }\n if (project) {\n const rows = this.#stmts.getRecentByProject.all(project, limit) as MemoryRow[];\n return rows.map(rowToMemory);\n }\n const rows = this.#stmts.getRecent.all(limit) as MemoryRow[];\n return rows.map(rowToMemory);\n }\n\n getByType(type: MemoryType, project?: string): readonly Memory[] {\n if (project) {\n const rows = this.#stmts.getByTypeAndProject.all(type, project) as MemoryRow[];\n return rows.map(rowToMemory);\n }\n const rows = this.#stmts.getByType.all(type) as MemoryRow[];\n return rows.map(rowToMemory);\n }\n\n updateContent(id: string, updates: {\n readonly title?: string | null;\n readonly content?: string;\n readonly context?: string | null;\n readonly tags?: readonly string[];\n readonly importance?: number;\n }): boolean {\n const existing = this.getById(id);\n if (!existing) return false;\n\n const now = new Date().toISOString();\n\n const finalContent = updates.content ?? existing.content;\n const params = {\n id,\n title: updates.title !== undefined ? updates.title : existing.title,\n content: finalContent,\n context: updates.context !== undefined ? updates.context : existing.context,\n tags: JSON.stringify(updates.tags ?? existing.tags),\n importance: updates.importance ?? existing.importance,\n updatedAt: now,\n embedding: null,\n contentHash: createHash('sha256').update(finalContent).digest('hex'),\n };\n\n this.#stmts.update.run(params);\n return true;\n }\n\n updateImportance(id: string, importance: number): boolean {\n const now = new Date().toISOString();\n const result = this.#stmts.updateImportance.run(importance, now, id);\n return result.changes > 0;\n }\n\n /** Update importance without touching updated_at - used by decay to avoid resetting the clock. */\n updateImportanceOnly(id: string, importance: number): boolean {\n const result = this.#stmts.updateImportanceOnly.run(importance, id);\n return result.changes > 0;\n }\n\n incrementAccess(id: string): void {\n this.#stmts.incrementAccess.run(new Date().toISOString(), id);\n }\n\n incrementInjection(id: string): void {\n this.#stmts.incrementInjection.run(id);\n }\n\n softDelete(id: string): boolean {\n const result = this.#stmts.softDelete.run(new Date().toISOString(), id);\n return result.changes > 0;\n }\n\n hardDelete(id: string): boolean {\n const deleteAndTombstone = this.db.transaction((memoryId: string) => {\n const memory = this.getById(memoryId);\n if (!memory) return 0;\n this.#stmts.tombstoneUpsert.run(memoryId, memory.project, new Date().toISOString());\n return this.#stmts.hardDelete.run(memoryId).changes;\n });\n return deleteAndTombstone(id) > 0;\n }\n\n deleteByType(type: MemoryType): number {\n const deleteAndTombstone = this.db.transaction((t: MemoryType) => {\n const rows = this.#stmts.getIdProjectByType.all(t) as { id: string; project: string | null }[];\n const now = new Date().toISOString();\n for (const row of rows) {\n this.#stmts.tombstoneUpsert.run(row.id, row.project, now);\n }\n return this.#stmts.deleteByType.run(t).changes;\n });\n return deleteAndTombstone(type);\n }\n\n deleteByProject(project: string): number {\n const deleteAndTombstone = this.db.transaction((p: string) => {\n const rows = this.#stmts.getIdProjectByProject.all(p) as { id: string; project: string | null }[];\n const now = new Date().toISOString();\n for (const row of rows) {\n this.#stmts.tombstoneUpsert.run(row.id, row.project, now);\n }\n return this.#stmts.deleteByProject.run(p).changes;\n });\n return deleteAndTombstone(project);\n }\n\n getTombstone(id: string): Tombstone | null {\n const row = this.#stmts.tombstoneGetById.get(id) as TombstoneRow | undefined;\n return row ? rowToTombstone(row) : null;\n }\n\n getAllTombstones(): readonly Tombstone[] {\n const rows = this.#stmts.tombstoneGetAll.all() as TombstoneRow[];\n return rows.map(rowToTombstone);\n }\n\n getTombstonesByProject(project: string): readonly Tombstone[] {\n const rows = this.#stmts.tombstoneGetByProjectStrict.all(project) as TombstoneRow[];\n return rows.map(rowToTombstone);\n }\n\n upsertTombstone(id: string, project: string | null, deletedAt: string): void {\n this.#stmts.tombstoneUpsert.run(id, project, deletedAt);\n }\n\n deleteTombstone(id: string): void {\n this.#stmts.tombstoneDelete.run(id);\n }\n\n count(project?: string): number {\n if (project) {\n const row = this.#stmts.countByProject.get(project) as { count: number };\n return row.count;\n }\n const row = this.#stmts.count.get() as { count: number };\n return row.count;\n }\n\n countByType(): Record<string, number> {\n const rows = this.#stmts.countByType.all() as { type: string; count: number }[];\n return Object.fromEntries(rows.map(r => [r.type, r.count]));\n }\n\n projectCounts(): ReadonlyMap<string, number> {\n const rows = this.#stmts.projectCounts.all() as { project: string; count: number }[];\n return new Map(rows.map(r => [r.project, r.count]));\n }\n\n dateRange(): { oldest: string | null; newest: string | null } {\n const row = this.#stmts.dateRange.get() as { oldest: string | null; newest: string | null };\n return { oldest: row.oldest, newest: row.newest };\n }\n\n topInjected(limit: number = 5): readonly { id: string; title: string | null; injectionCount: number }[] {\n const rows = this.#stmts.topInjected.all(limit) as { id: string; title: string | null; injection_count: number }[];\n return rows.map(r => ({ id: r.id, title: r.title, injectionCount: r.injection_count }));\n }\n\n upsertFromSync(row: SyncMemoryRow): void {\n const params = {\n id: row.id,\n type: row.type,\n title: row.title,\n content: row.content,\n context: row.context,\n source: row.source,\n project: row.project,\n tags: JSON.stringify(row.tags),\n importance: row.importance,\n accessCount: row.access_count,\n injectionCount: row.injection_count,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n lastAccessed: row.last_accessed,\n contentHash: createHash('sha256').update(row.content).digest('hex'),\n };\n\n const existing = this.getById(row.id);\n if (existing) {\n this.#stmts.updateSync.run(params);\n } else {\n this.#stmts.insertSync.run(params);\n }\n }\n\n getAllForSync(project?: string): readonly Memory[] {\n if (project) {\n const rows = this.#stmts.getAllStrictProject.all(project) as MemoryRow[];\n return rows.map(rowToMemory);\n }\n return this.getAll();\n }\n}\n","import type Database from 'better-sqlite3';\nimport type { Relation, RelationType } from '../types.js';\n\ninterface RelationRow {\n source_id: string;\n target_id: string;\n relation_type: string;\n created_at: string;\n}\n\nfunction rowToRelation(row: RelationRow): Relation {\n return {\n sourceId: row.source_id,\n targetId: row.target_id,\n relationType: row.relation_type as RelationType,\n createdAt: row.created_at,\n };\n}\n\nexport class RelationRepo {\n readonly #stmts;\n\n constructor(db: Database.Database) {\n this.#stmts = {\n insert: db.prepare(`\n INSERT OR IGNORE INTO relations (source_id, target_id, relation_type)\n VALUES (?, ?, ?)\n `),\n getBySource: db.prepare('SELECT * FROM relations WHERE source_id = ?'),\n getByTarget: db.prepare('SELECT * FROM relations WHERE target_id = ?'),\n getByMemory: db.prepare(`\n SELECT * FROM relations WHERE source_id = ? OR target_id = ?\n `),\n delete: db.prepare(`\n DELETE FROM relations WHERE source_id = ? AND target_id = ? AND relation_type = ?\n `),\n countByMemory: db.prepare(`\n SELECT COUNT(*) as count FROM relations WHERE source_id = ? OR target_id = ?\n `),\n count: db.prepare('SELECT COUNT(*) as count FROM relations'),\n getAll: db.prepare('SELECT * FROM relations'),\n deleteOrphaned: db.prepare(`\n DELETE FROM relations\n WHERE source_id NOT IN (SELECT id FROM memories)\n OR target_id NOT IN (SELECT id FROM memories)\n `),\n };\n }\n\n create(sourceId: string, targetId: string, relationType: RelationType): boolean {\n const result = this.#stmts.insert.run(sourceId, targetId, relationType);\n return result.changes > 0;\n }\n\n getBySource(sourceId: string): readonly Relation[] {\n const rows = this.#stmts.getBySource.all(sourceId) as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n getByTarget(targetId: string): readonly Relation[] {\n const rows = this.#stmts.getByTarget.all(targetId) as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n getByMemory(memoryId: string): readonly Relation[] {\n const rows = this.#stmts.getByMemory.all(memoryId, memoryId) as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n delete(sourceId: string, targetId: string, relationType: RelationType): boolean {\n const result = this.#stmts.delete.run(sourceId, targetId, relationType);\n return result.changes > 0;\n }\n\n countByMemory(memoryId: string): number {\n const row = this.#stmts.countByMemory.get(memoryId, memoryId) as { count: number };\n return row.count;\n }\n\n count(): number {\n const row = this.#stmts.count.get() as { count: number };\n return row.count;\n }\n\n getAll(): readonly Relation[] {\n const rows = this.#stmts.getAll.all() as RelationRow[];\n return rows.map(rowToRelation);\n }\n\n deleteOrphaned(): number {\n const result = this.#stmts.deleteOrphaned.run();\n return result.changes;\n }\n}\n","import type Database from 'better-sqlite3';\nimport type { FtsMatch, MemoryType } from '../types.js';\n\n// ── FTS5 Search ───────────────────────────────────────────────\n\nexport interface FtsSearchOptions {\n readonly query: string;\n readonly limit: number;\n readonly type?: MemoryType;\n readonly minImportance?: number;\n readonly project?: string;\n}\n\n\nexport class SearchRepo {\n readonly #stmts;\n\n constructor(db: Database.Database) {\n // FTS5 search with BM25 ranking (weights: title=5.0, content=1.0, tags=2.0)\n this.#stmts = {\n ftsSearch: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n ORDER BY rank\n LIMIT @limit\n `),\n ftsSearchByProject: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n AND (m.project = @project OR m.project IS NULL)\n ORDER BY rank\n LIMIT @limit\n `),\n ftsSearchFiltered: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n AND m.type = @type\n AND m.importance >= @minImportance\n ORDER BY rank\n LIMIT @limit\n `),\n ftsSearchFilteredByProject: db.prepare(`\n SELECT\n m.rowid,\n m.id as memory_id,\n bm25(memories_fts, 5.0, 1.0, 2.0) as rank\n FROM memories_fts f\n JOIN memories m ON m.rowid = f.rowid\n WHERE memories_fts MATCH @query\n AND m.type = @type\n AND m.importance >= @minImportance\n AND (m.project = @project OR m.project IS NULL)\n ORDER BY rank\n LIMIT @limit\n `),\n };\n }\n\n /**\n * Full-text search using BM25 ranking.\n * Returns matches sorted by relevance (most relevant first).\n */\n searchFts(options: FtsSearchOptions): readonly FtsMatch[] {\n const ftsQuery = toFtsQuery(options.query);\n if (!ftsQuery) return [];\n\n try {\n const hasType = !!options.type;\n const hasProject = !!options.project;\n\n let rows: FtsRow[];\n if (hasType && hasProject) {\n rows = this.#stmts.ftsSearchFilteredByProject.all({\n query: ftsQuery, limit: options.limit,\n type: options.type, minImportance: options.minImportance ?? 0,\n project: options.project,\n }) as FtsRow[];\n } else if (hasType) {\n rows = this.#stmts.ftsSearchFiltered.all({\n query: ftsQuery, limit: options.limit,\n type: options.type, minImportance: options.minImportance ?? 0,\n }) as FtsRow[];\n } else if (hasProject) {\n rows = this.#stmts.ftsSearchByProject.all({\n query: ftsQuery, limit: options.limit,\n project: options.project,\n }) as FtsRow[];\n } else {\n rows = this.#stmts.ftsSearch.all({\n query: ftsQuery, limit: options.limit,\n }) as FtsRow[];\n }\n\n return rows.map(r => ({\n rowid: r.rowid,\n memoryId: r.memory_id,\n rank: r.rank,\n }));\n } catch (err) {\n // FTS5 MATCH throws on invalid query syntax - degrade gracefully\n console.error('[agentic-memory] FTS5 search error:', err instanceof Error ? err.message : err);\n return [];\n }\n }\n\n}\n\n// ── Internal helpers ──────────────────────────────────────────\n\ninterface FtsRow {\n rowid: number;\n memory_id: string;\n rank: number;\n}\n\n// Synonym expansion for common dev terms\nconst SYNONYMS: Record<string, readonly string[]> = {\n auth: ['authentication', 'login', 'oauth', 'jwt'],\n authentication: ['auth', 'login', 'oauth'],\n login: ['auth', 'authentication', 'signin'],\n db: ['database', 'sql', 'sqlite', 'postgres'],\n database: ['db', 'sql', 'sqlite', 'postgres'],\n api: ['endpoint', 'route', 'rest', 'graphql'],\n deploy: ['deployment', 'release', 'ship', 'publish'],\n test: ['testing', 'spec', 'jest', 'vitest'],\n config: ['configuration', 'settings', 'setup'],\n err: ['error', 'exception', 'crash', 'bug'],\n error: ['err', 'exception', 'crash', 'bug'],\n};\n\n/**\n * Convert a natural language query to FTS5 query syntax.\n * Expands synonyms and wraps words in quotes for safe matching.\n */\nfunction toFtsQuery(input: string): string | null {\n const words = input\n .replace(/[^\\w\\s]/g, ' ')\n .split(/\\s+/)\n .filter(w => w.length > 0);\n\n if (words.length === 0) return null;\n\n const expanded = words.flatMap((w) => {\n const lower = w.toLowerCase();\n const syns = SYNONYMS[lower];\n return syns ? [w, ...syns] : [w];\n });\n\n return [...new Set(expanded)].map(w => `\"${w}\"`).join(' OR ');\n}\n"],"mappings":"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","names":["rows","row"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SyncPayloadSchema
4
- } from "./chunk-F5PNKQKW.js";
4
+ } from "./chunk-YZ53W47Z.js";
5
5
 
6
6
  // src/commands/memory/utils/sync-merge.ts
7
7
  function memoryToSyncRow(m) {
@@ -22,6 +22,9 @@ function memoryToSyncRow(m) {
22
22
  last_accessed: m.lastAccessed
23
23
  };
24
24
  }
25
+ function tombstoneToSyncRow(t) {
26
+ return { id: t.id, project: t.project, deleted_at: t.deletedAt };
27
+ }
25
28
  function parsePayload(raw) {
26
29
  if (!raw || raw === "null") return null;
27
30
  try {
@@ -33,9 +36,24 @@ function parsePayload(raw) {
33
36
  function mergeFromRemote(memoryRepo, relationRepo, payload) {
34
37
  let inserted = 0;
35
38
  let updated = 0;
39
+ let deleted = 0;
36
40
  let relationsAdded = 0;
37
- const memories = payload.memories;
38
- for (const remote of memories) {
41
+ for (const t of payload.tombstones) {
42
+ const local = memoryRepo.getById(t.id);
43
+ if (local && local.updatedAt <= t.deleted_at) {
44
+ memoryRepo.hardDelete(t.id);
45
+ deleted++;
46
+ }
47
+ memoryRepo.upsertTombstone(t.id, t.project, t.deleted_at);
48
+ }
49
+ for (const remote of payload.memories) {
50
+ const tombstone = memoryRepo.getTombstone(remote.id);
51
+ if (tombstone && tombstone.deletedAt >= remote.updated_at) {
52
+ continue;
53
+ }
54
+ if (tombstone && tombstone.deletedAt < remote.updated_at) {
55
+ memoryRepo.deleteTombstone(remote.id);
56
+ }
39
57
  const local = memoryRepo.getById(remote.id);
40
58
  if (!local) {
41
59
  memoryRepo.upsertFromSync(remote);
@@ -57,12 +75,13 @@ function mergeFromRemote(memoryRepo, relationRepo, payload) {
57
75
  );
58
76
  if (added) relationsAdded++;
59
77
  }
60
- return { inserted, updated, relationsAdded };
78
+ return { inserted, updated, deleted, relationsAdded };
61
79
  }
62
80
 
63
81
  export {
64
82
  memoryToSyncRow,
83
+ tombstoneToSyncRow,
65
84
  parsePayload,
66
85
  mergeFromRemote
67
86
  };
68
- //# sourceMappingURL=chunk-N6X3E5AX.js.map
87
+ //# sourceMappingURL=chunk-F6SLV2FR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/memory/utils/sync-merge.ts"],"sourcesContent":["import { SyncPayloadSchema } from '../types.js';\nimport type { Memory, SyncPayload, SyncMemoryRow, SyncTombstone, MergeResult, RelationType, Tombstone } from '../types.js';\nimport type { MemoryRepo } from '../storage/memory-repo.js';\nimport type { RelationRepo } from '../storage/relation-repo.js';\n\nfunction memoryToSyncRow(m: Memory): SyncMemoryRow {\n return {\n id: m.id,\n type: m.type,\n title: m.title,\n content: m.content,\n context: m.context,\n source: m.source,\n project: m.project,\n tags: [...m.tags],\n importance: m.importance,\n access_count: m.accessCount,\n injection_count: m.injectionCount,\n created_at: m.createdAt,\n updated_at: m.updatedAt,\n last_accessed: m.lastAccessed,\n };\n}\n\nfunction tombstoneToSyncRow(t: Tombstone): SyncTombstone {\n return { id: t.id, project: t.project, deleted_at: t.deletedAt };\n}\n\nexport { memoryToSyncRow, tombstoneToSyncRow };\n\nexport function parsePayload(raw: string | null): SyncPayload | null {\n if (!raw || raw === 'null') return null;\n try { return SyncPayloadSchema.parse(JSON.parse(raw)); }\n catch { return null; }\n}\n\nexport function mergeFromRemote(\n memoryRepo: MemoryRepo,\n relationRepo: RelationRepo,\n payload: SyncPayload,\n): MergeResult {\n let inserted = 0;\n let updated = 0;\n let deleted = 0;\n let relationsAdded = 0;\n\n // Phase 1: apply remote tombstones — delete locally or persist the tombstone\n // so future remote memory rows with older updated_at don't resurrect them.\n // Tie-break: delete wins when timestamps are equal (matches Phase 2 semantics).\n // upsertTombstone is safe to call unconditionally — ON CONFLICT keeps the newer row.\n for (const t of payload.tombstones) {\n const local = memoryRepo.getById(t.id);\n if (local && local.updatedAt <= t.deleted_at) {\n memoryRepo.hardDelete(t.id);\n deleted++;\n }\n memoryRepo.upsertTombstone(t.id, t.project, t.deleted_at);\n }\n\n // Phase 2: merge remote memories, honoring local tombstones\n for (const remote of payload.memories) {\n const tombstone = memoryRepo.getTombstone(remote.id);\n if (tombstone && tombstone.deletedAt >= remote.updated_at) {\n // Delete wins; skip this memory.\n continue;\n }\n if (tombstone && tombstone.deletedAt < remote.updated_at) {\n // Remote update is newer than the delete — resurrect and drop the stale tombstone.\n memoryRepo.deleteTombstone(remote.id);\n }\n\n const local = memoryRepo.getById(remote.id);\n if (!local) {\n memoryRepo.upsertFromSync(remote);\n inserted++;\n } else if (remote.updated_at > local.updatedAt) {\n memoryRepo.upsertFromSync(remote);\n updated++;\n }\n }\n\n // Phase 3: relations — only link memories that survived both phases.\n const localIds = new Set(memoryRepo.getAll().map((m) => m.id));\n const relations = payload.relations.filter(\n (r) => localIds.has(r.source_id) && localIds.has(r.target_id),\n );\n\n for (const rel of relations) {\n const added = relationRepo.create(\n rel.source_id,\n rel.target_id,\n rel.relation_type as RelationType,\n );\n if (added) relationsAdded++;\n }\n\n return { inserted, updated, deleted, relationsAdded };\n}\n"],"mappings":";;;;;;AAKA,SAAS,gBAAgB,GAA0B;AACjD,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,OAAO,EAAE;AAAA,IACT,SAAS,EAAE;AAAA,IACX,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,SAAS,EAAE;AAAA,IACX,MAAM,CAAC,GAAG,EAAE,IAAI;AAAA,IAChB,YAAY,EAAE;AAAA,IACd,cAAc,EAAE;AAAA,IAChB,iBAAiB,EAAE;AAAA,IACnB,YAAY,EAAE;AAAA,IACd,YAAY,EAAE;AAAA,IACd,eAAe,EAAE;AAAA,EACnB;AACF;AAEA,SAAS,mBAAmB,GAA6B;AACvD,SAAO,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE,SAAS,YAAY,EAAE,UAAU;AACjE;AAIO,SAAS,aAAa,KAAwC;AACnE,MAAI,CAAC,OAAO,QAAQ,OAAQ,QAAO;AACnC,MAAI;AAAE,WAAO,kBAAkB,MAAM,KAAK,MAAM,GAAG,CAAC;AAAA,EAAG,QACjD;AAAE,WAAO;AAAA,EAAM;AACvB;AAEO,SAAS,gBACd,YACA,cACA,SACa;AACb,MAAI,WAAW;AACf,MAAI,UAAU;AACd,MAAI,UAAU;AACd,MAAI,iBAAiB;AAMrB,aAAW,KAAK,QAAQ,YAAY;AAClC,UAAM,QAAQ,WAAW,QAAQ,EAAE,EAAE;AACrC,QAAI,SAAS,MAAM,aAAa,EAAE,YAAY;AAC5C,iBAAW,WAAW,EAAE,EAAE;AAC1B;AAAA,IACF;AACA,eAAW,gBAAgB,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU;AAAA,EAC1D;AAGA,aAAW,UAAU,QAAQ,UAAU;AACrC,UAAM,YAAY,WAAW,aAAa,OAAO,EAAE;AACnD,QAAI,aAAa,UAAU,aAAa,OAAO,YAAY;AAEzD;AAAA,IACF;AACA,QAAI,aAAa,UAAU,YAAY,OAAO,YAAY;AAExD,iBAAW,gBAAgB,OAAO,EAAE;AAAA,IACtC;AAEA,UAAM,QAAQ,WAAW,QAAQ,OAAO,EAAE;AAC1C,QAAI,CAAC,OAAO;AACV,iBAAW,eAAe,MAAM;AAChC;AAAA,IACF,WAAW,OAAO,aAAa,MAAM,WAAW;AAC9C,iBAAW,eAAe,MAAM;AAChC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,IAAI,IAAI,WAAW,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC7D,QAAM,YAAY,QAAQ,UAAU;AAAA,IAClC,CAAC,MAAM,SAAS,IAAI,EAAE,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS;AAAA,EAC9D;AAEA,aAAW,OAAO,WAAW;AAC3B,UAAM,QAAQ,aAAa;AAAA,MACzB,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AACA,QAAI,MAAO;AAAA,EACb;AAEA,SAAO,EAAE,UAAU,SAAS,SAAS,eAAe;AACtD;","names":[]}
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  cwdRequire
4
- } from "./chunk-J765H3HZ.js";
4
+ } from "./chunk-WOC2PKT2.js";
5
5
  import {
6
6
  __export
7
- } from "./chunk-YXPJDIMK.js";
7
+ } from "./chunk-UQOBOHKN.js";
8
8
 
9
9
  // src/commands/memory/config.ts
10
10
  import { z } from "zod";
@@ -285,11 +285,32 @@ function up3(db) {
285
285
  db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash)");
286
286
  }
287
287
 
288
+ // src/commands/memory/storage/migrations/004-add-tombstones.ts
289
+ var add_tombstones_exports = {};
290
+ __export(add_tombstones_exports, {
291
+ up: () => up4,
292
+ version: () => version4
293
+ });
294
+ var version4 = 4;
295
+ function up4(db) {
296
+ db.exec(`
297
+ CREATE TABLE IF NOT EXISTS memory_tombstones (
298
+ id TEXT PRIMARY KEY,
299
+ project TEXT,
300
+ deleted_at TEXT NOT NULL
301
+ );
302
+
303
+ CREATE INDEX IF NOT EXISTS idx_tombstones_project ON memory_tombstones(project);
304
+ CREATE INDEX IF NOT EXISTS idx_tombstones_deleted_at ON memory_tombstones(deleted_at);
305
+ `);
306
+ }
307
+
288
308
  // src/commands/memory/storage/migrator.ts
289
309
  var migrations = [
290
310
  initial_exports,
291
311
  add_project_exports,
292
- add_content_hash_exports
312
+ add_content_hash_exports,
313
+ add_tombstones_exports
293
314
  ];
294
315
  function getSchemaVersion(db) {
295
316
  try {
@@ -331,4 +352,4 @@ export {
331
352
  closeDatabase,
332
353
  migrate
333
354
  };
334
- //# sourceMappingURL=chunk-AR64LWGW.js.map
355
+ //# sourceMappingURL=chunk-RJEPJ4JE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/memory/config.ts","../src/commands/memory/storage/database.ts","../src/commands/memory/storage/migrations/001-initial.ts","../src/commands/memory/storage/migrations/002-add-project.ts","../src/commands/memory/storage/migrations/003-add-content-hash.ts","../src/commands/memory/storage/migrations/004-add-tombstones.ts","../src/commands/memory/storage/migrator.ts"],"sourcesContent":["import { z } from 'zod';\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { DecayParams } from './types.js';\n\n// ── Config Schema ─────────────────────────────────────────────\n\nconst ConfigSchema = z.object({\n dataDir: z.string().default('~/.agentic-memory'),\n injectionBudget: z.number().int().min(100).max(20000).default(3000),\n consolidationInterval: z.number().int().min(1).default(10),\n enableReranker: z.boolean().default(true),\n logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('warn'),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\n// ── Defaults ──────────────────────────────────────────────────\n\nexport const DEFAULT_CONFIG: Config = {\n dataDir: '~/.agentic-memory',\n injectionBudget: 3000,\n consolidationInterval: 10,\n enableReranker: true,\n logLevel: 'warn',\n};\n\nexport const DEFAULT_DECAY_PARAMS: DecayParams = {\n tauByType: {\n working: 0, // cleared each session, tau irrelevant\n episodic: 30, // fast decay (was 60, cognitive science: unrehearsed episodes fade in ~30d)\n semantic: 540, // slow decay (was 365, extracted facts are stable for years)\n procedural: 730, // near-permanent\n pattern: 180, // medium decay\n },\n accessModifiers: [\n { maxCount: 3, multiplier: 1.0 },\n { maxCount: 10, multiplier: 2.0 },\n { maxCount: Infinity, multiplier: 4.0 },\n ],\n relationModifier: {\n connectedThreshold: 3,\n connectedMultiplier: 1.8, // higher tau = slower decay for connected memories\n isolatedMultiplier: 0.8, // lower tau = slightly faster decay for isolated memories\n highlyConnectedThreshold: 6,\n highlyConnectedMultiplier: 2.5, // near-immune: ~2.5x longer effective half-life\n },\n importanceFloor: 0.05,\n pruneThreshold: 0.1,\n pruneMinAgeDays: 90,\n};\n\nexport const SCORING_WEIGHTS = {\n text: 0.35,\n importance: 0.20,\n recency: 0.20,\n access: 0.10,\n context: 0.15,\n} as const;\n\n// ── Injection Algorithm Constants ──────────────────────────────\n\nexport const INJECTION_WEIGHTS = {\n context: 0.30,\n value: 0.25,\n importance: 0.20,\n recency: 0.15,\n typeBonus: 0.05,\n noise: 0.05,\n} as const;\n\nexport const TYPE_INJECTION_BONUS: Record<string, number> = {\n procedural: 1.0,\n pattern: 0.8,\n semantic: 0.6,\n episodic: 0.3,\n working: 0.0,\n};\n\nexport const RECENCY_HALF_LIFE: Record<string, number> = {\n working: 1,\n episodic: 7,\n pattern: 14,\n semantic: 30,\n procedural: 90,\n};\n\nexport const INJECTION_MIN_SCORE = 0.25;\nexport const INJECTION_COLD_START_THRESHOLD = 5;\nexport const INJECTION_COLD_START_RAMP_END = 20;\nexport const INJECTION_HEADER_TOKENS = 50;\nexport const INJECTION_MAX_SAME_TYPE_FULL = 2;\nexport const INJECTION_PINNED_BUDGET_PCT = 0.10;\n\n// ── Config Loader ─────────────────────────────────────────────\n\nexport function resolveDataDir(dataDir: string): string {\n if (dataDir.startsWith('~')) {\n return join(homedir(), dataDir.slice(1));\n }\n return dataDir;\n}\n\nexport function loadConfig(overrides?: Partial<Config>): Config {\n const envOverrides: Record<string, unknown> = {};\n\n const envBudget = process.env['AGENTIC_MEMORY_INJECTION_BUDGET'];\n if (envBudget !== undefined) {\n envOverrides['injectionBudget'] = parseInt(envBudget, 10);\n }\n\n const envLogLevel = process.env['AGENTIC_MEMORY_LOG_LEVEL'];\n if (envLogLevel !== undefined) {\n envOverrides['logLevel'] = envLogLevel;\n }\n\n const envDataDir = process.env['AGENTIC_MEMORY_DATA_DIR'];\n if (envDataDir !== undefined) {\n envOverrides['dataDir'] = envDataDir;\n }\n\n // Try loading config.json from data dir\n let fileConfig: Record<string, unknown> = {};\n const baseDir = resolveDataDir(overrides?.dataDir ?? envOverrides['dataDir'] as string ?? DEFAULT_CONFIG.dataDir);\n try {\n const raw = readFileSync(join(baseDir, 'config.json'), 'utf-8');\n fileConfig = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n const isNotFound = err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT';\n if (!isNotFound) {\n // Malformed JSON or permissions error - warn, don't silently ignore\n console.error('[agentic-memory] Failed to load config.json:', err instanceof Error ? err.message : err);\n }\n }\n\n const merged = { ...DEFAULT_CONFIG, ...fileConfig, ...envOverrides, ...overrides };\n return ConfigSchema.parse(merged);\n}\n\n// ── Token Estimation ──────────────────────────────────────────\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n","import type DatabaseConstructor from 'better-sqlite3';\nimport { resolveDataDir } from '../config.js';\nimport { mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { cwdRequire } from '../utils/require-deps.js';\n\nexport interface DatabaseOptions {\n readonly dbPath?: string; // full path override (e.g. ':memory:' for tests)\n readonly dataDir?: string; // resolved data dir (default ~/.agentic-memory)\n}\n\nexport function createDatabase(options: DatabaseOptions = {}): DatabaseConstructor.Database {\n const dbPath = options.dbPath ?? resolveDbPath(options.dataDir);\n\n if (dbPath !== ':memory:') {\n mkdirSync(dirname(dbPath), { recursive: true });\n }\n\n const Database = cwdRequire('better-sqlite3') as typeof DatabaseConstructor;\n const sqliteVec = cwdRequire('sqlite-vec') as { load: (db: DatabaseConstructor.Database) => void };\n\n const db = new Database(dbPath);\n\n // Load sqlite-vec extension\n sqliteVec.load(db);\n\n // Configure PRAGMAs (order matters: foreign_keys before any ops, journal_mode is persistent)\n db.pragma('journal_mode = WAL');\n db.pragma('busy_timeout = 5000');\n db.pragma('foreign_keys = ON');\n db.pragma('cache_size = -64000');\n db.pragma('mmap_size = 268435456');\n db.pragma('synchronous = NORMAL');\n db.pragma('temp_store = MEMORY');\n db.pragma('journal_size_limit = 33554432');\n\n return db;\n}\n\nexport function closeDatabase(db: DatabaseConstructor.Database): void {\n try {\n db.pragma('wal_checkpoint(TRUNCATE)');\n } catch {\n // Checkpoint may fail on :memory: - that's fine\n }\n db.close();\n}\n\nfunction resolveDbPath(dataDir?: string): string {\n const dir = resolveDataDir(dataDir ?? '~/.agentic-memory');\n return join(dir, 'memory.db');\n}\n","import type Database from 'better-sqlite3';\n\nexport const version = 1;\n\nexport function up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n );\n\n CREATE TABLE IF NOT EXISTS memories (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL CHECK(type IN ('episodic','semantic','procedural','working','pattern')),\n title TEXT,\n content TEXT NOT NULL,\n context TEXT,\n source TEXT CHECK(source IN ('manual','session_end','consolidation','hook','import')),\n tags TEXT NOT NULL DEFAULT '[]',\n importance REAL NOT NULL DEFAULT 0.5 CHECK(importance >= 0.0 AND importance <= 1.0),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n access_count INTEGER NOT NULL DEFAULT 0 CHECK(access_count >= 0),\n last_accessed TEXT,\n injection_count INTEGER NOT NULL DEFAULT 0 CHECK(injection_count >= 0),\n embedding BLOB\n );\n\n CREATE TABLE IF NOT EXISTS relations (\n source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,\n target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,\n relation_type TEXT NOT NULL CHECK(relation_type IN (\n 'relates_to','depends_on','contradicts','extends','implements','derived_from'\n )),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (source_id, target_id, relation_type)\n );\n\n -- FTS5 external content (no data duplication)\n CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(\n title, content, tags,\n content=memories,\n content_rowid=rowid,\n tokenize='porter unicode61'\n );\n\n -- FTS5 sync triggers\n CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN\n INSERT INTO memories_fts(rowid, title, content, tags)\n VALUES (new.rowid, new.title, new.content, new.tags);\n END;\n\n CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, title, content, tags)\n VALUES ('delete', old.rowid, old.title, old.content, old.tags);\n END;\n\n CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, title, content, tags)\n VALUES ('delete', old.rowid, old.title, old.content, old.tags);\n INSERT INTO memories_fts(rowid, title, content, tags)\n VALUES (new.rowid, new.title, new.content, new.tags);\n END;\n\n -- Vector search (synced manually in application code)\n CREATE VIRTUAL TABLE IF NOT EXISTS memories_vec USING vec0(\n memory_id TEXT PRIMARY KEY,\n embedding float[384] distance_metric=cosine\n );\n\n -- Indexes\n CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);\n CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance);\n CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);\n CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_id);\n `);\n}\n","import type Database from 'better-sqlite3';\n\nexport const version = 2;\n\nexport function up(db: Database.Database): void {\n db.exec(`\n ALTER TABLE memories ADD COLUMN project TEXT;\n CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project);\n `);\n}\n","import { createHash } from 'node:crypto';\nimport type Database from 'better-sqlite3';\n\nexport const version = 3;\n\nexport function up(db: Database.Database): void {\n db.exec('ALTER TABLE memories ADD COLUMN content_hash TEXT');\n\n // Backfill existing rows with SHA-256 hashes\n const rows = db.prepare('SELECT id, content FROM memories ORDER BY updated_at DESC').all() as { id: string; content: string }[];\n const update = db.prepare('UPDATE memories SET content_hash = ? WHERE id = ?');\n const remove = db.prepare('DELETE FROM memories WHERE id = ?');\n\n // Track seen hashes — keep the most recently updated, remove older duplicates\n const seen = new Set<string>();\n for (const row of rows) {\n const hash = createHash('sha256').update(row.content).digest('hex');\n if (seen.has(hash)) {\n remove.run(row.id);\n } else {\n seen.add(hash);\n update.run(hash, row.id);\n }\n }\n\n db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash)');\n}\n","import type Database from 'better-sqlite3';\n\nexport const version = 4;\n\nexport function up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS memory_tombstones (\n id TEXT PRIMARY KEY,\n project TEXT,\n deleted_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_tombstones_project ON memory_tombstones(project);\n CREATE INDEX IF NOT EXISTS idx_tombstones_deleted_at ON memory_tombstones(deleted_at);\n `);\n}\n","import type Database from 'better-sqlite3';\nimport * as migration001 from './migrations/001-initial.js';\nimport * as migration002 from './migrations/002-add-project.js';\nimport * as migration003 from './migrations/003-add-content-hash.js';\nimport * as migration004 from './migrations/004-add-tombstones.js';\n\ninterface Migration {\n readonly version: number;\n readonly up: (db: Database.Database) => void;\n}\n\nconst migrations: readonly Migration[] = [\n migration001,\n migration002,\n migration003,\n migration004,\n];\n\nexport function getSchemaVersion(db: Database.Database): number {\n try {\n const row = db.prepare(\"SELECT value FROM meta WHERE key = 'schema_version'\").get() as\n { value: string } | undefined;\n return row ? parseInt(row.value, 10) : 0;\n } catch {\n return 0;\n }\n}\n\nexport function migrate(db: Database.Database): void {\n const current = getSchemaVersion(db);\n const pending = migrations.filter(m => m.version > current);\n\n if (pending.length === 0) return;\n\n const runMigrations = db.transaction(() => {\n for (const m of pending) {\n m.up(db);\n db.prepare(\"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)\")\n .run(String(m.version));\n }\n });\n\n runMigrations();\n}\n"],"mappings":"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","names":["join","join","up","version","up","version","up","version"]}
@@ -684,9 +684,39 @@ async function addSessionStartPullHook(root, placement) {
684
684
  async function addSessionEndPushHook(root, placement) {
685
685
  const target = placement === "local" ? "settings.local.json" : "settings.json";
686
686
  return addPlacementHook(root, placement, "SessionEnd", "memory push", {
687
- hooks: [{ type: "command", command: "claude-launchpad memory push -y >/dev/null 2>&1 & exit 0" }]
687
+ hooks: [{ type: "command", command: "claude-launchpad memory push -y >/dev/null 2>&1; exit 0" }]
688
688
  }, false, `Added SessionEnd hook for memory sync to ${target}`);
689
689
  }
690
+ async function upgradeStaleSessionEndPushHook(root) {
691
+ let changedAny = false;
692
+ for (const placement of ["shared", "local"]) {
693
+ const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
694
+ const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
695
+ const settings = await read(root);
696
+ const hooks = settings.hooks;
697
+ const sessionEnd = hooks?.SessionEnd;
698
+ if (!sessionEnd) continue;
699
+ let changed = false;
700
+ const upgraded = sessionEnd.map((group) => {
701
+ const inner = group.hooks;
702
+ if (!inner) return group;
703
+ const rewritten = inner.map((h) => {
704
+ const cmd = typeof h.command === "string" ? h.command : "";
705
+ if (!cmd.includes("memory push") || !/&\s*exit\s+0\s*$/.test(cmd)) return h;
706
+ changed = true;
707
+ return { ...h, command: cmd.replace(/&\s*exit\s+0\s*$/, "; exit 0") };
708
+ });
709
+ return { ...group, hooks: rewritten };
710
+ });
711
+ if (!changed) continue;
712
+ const updated = { ...settings, hooks: { ...hooks, SessionEnd: upgraded } };
713
+ await write(root, updated);
714
+ const target = placement === "local" ? "settings.local.json" : "settings.json";
715
+ log.success(`Upgraded SessionEnd push hook in ${target} (now synchronous)`);
716
+ changedAny = true;
717
+ }
718
+ return changedAny;
719
+ }
690
720
  async function removeStaleStopHook(root) {
691
721
  const settings = await readSettingsJson(root);
692
722
  const hooks = settings.hooks;
@@ -797,6 +827,7 @@ var FIX_TABLE = [
797
827
  { analyzer: "MCP", match: "no allowedMcpServers", fix: (root, _det, placement) => addAllowedMcpServers(root, placement) },
798
828
  { analyzer: "Memory", match: "SessionStart hook to auto-pull", fix: (root, _det, placement) => addSessionStartPullHook(root, placement) },
799
829
  { analyzer: "Memory", match: "SessionEnd hook to auto-push", fix: (root, _det, placement) => addSessionEndPushHook(root, placement) },
830
+ { analyzer: "Memory", match: "SessionEnd push hook is backgrounded", fix: (root) => upgradeStaleSessionEndPushHook(root) },
800
831
  { analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root, _det, placement) => {
801
832
  const content = "Use agentic-memory to persist knowledge across sessions:\n- Memories are automatically injected at session start\n- STORE IMMEDIATELY when: a dependency strategy changes, an architecture decision is made, a convention is established, a bug pattern is discovered, or a feature is killed/added\n- Use memory_search before memory_store to check for duplicates\n- NEVER store credentials, API keys, tokens, or secrets in memories";
802
833
  const target = placement === "local" ? join4(root, ".claude", "CLAUDE.md") : void 0;
@@ -1172,4 +1203,4 @@ export {
1172
1203
  printScoreCard,
1173
1204
  renderDoctorReport
1174
1205
  };
1175
- //# sourceMappingURL=chunk-YXPJDIMK.js.map
1206
+ //# sourceMappingURL=chunk-UQOBOHKN.js.map