claude-launchpad 1.3.0 → 1.5.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.
- package/README.md +2 -2
- package/dist/{chunk-UJP5PJTA.js → chunk-5YUKTNBM.js} +3 -3
- package/dist/{chunk-V4NXT4KB.js → chunk-DXDOVWOA.js} +64 -8
- package/dist/chunk-DXDOVWOA.js.map +1 -0
- package/dist/{chunk-N6X3E5AX.js → chunk-F6SLV2FR.js} +24 -5
- package/dist/chunk-F6SLV2FR.js.map +1 -0
- package/dist/{chunk-YXPJDIMK.js → chunk-GLFJ2B43.js} +56 -12
- package/dist/chunk-GLFJ2B43.js.map +1 -0
- package/dist/{chunk-AR64LWGW.js → chunk-WLD2PA3B.js} +25 -4
- package/dist/chunk-WLD2PA3B.js.map +1 -0
- package/dist/{chunk-J765H3HZ.js → chunk-YF6HCPVY.js} +2 -2
- package/dist/{chunk-F5PNKQKW.js → chunk-YZ53W47Z.js} +7 -2
- package/dist/chunk-YZ53W47Z.js.map +1 -0
- package/dist/cli.js +313 -58
- package/dist/cli.js.map +1 -1
- package/dist/commands/memory/server.js +5 -5
- package/dist/{context-VAXF3EW3.js → context-4X4CLMU3.js} +6 -6
- package/dist/{install-M3JWBGMK.js → install-P4TFYUJT.js} +6 -6
- package/dist/install-P4TFYUJT.js.map +1 -0
- package/dist/{pull-ZQFCMK46.js → pull-7SR7P3US.js} +9 -9
- package/dist/{push-5ZJNWAE7.js → push-SCTO5TZQ.js} +40 -17
- package/dist/push-SCTO5TZQ.js.map +1 -0
- package/dist/{require-deps-QW2IU6I3.js → require-deps-MCFEZOIF.js} +3 -3
- package/dist/{stats-NPXPJNBO.js → stats-MLWRNOHU.js} +7 -7
- package/dist/{sync-clean-JQLVE4WU.js → sync-clean-2BMOFDV7.js} +2 -2
- package/dist/{sync-status-IYG7ZYC5.js → sync-status-J7BVY6KF.js} +9 -9
- package/dist/{tui-74FMIMUM.js → tui-JE5L7SXC.js} +5 -5
- package/package.json +3 -1
- package/dist/chunk-AR64LWGW.js.map +0 -1
- package/dist/chunk-F5PNKQKW.js.map +0 -1
- package/dist/chunk-N6X3E5AX.js.map +0 -1
- package/dist/chunk-V4NXT4KB.js.map +0 -1
- package/dist/chunk-YXPJDIMK.js.map +0 -1
- package/dist/install-M3JWBGMK.js.map +0 -1
- package/dist/push-5ZJNWAE7.js.map +0 -1
- /package/dist/{chunk-UJP5PJTA.js.map → chunk-5YUKTNBM.js.map} +0 -0
- /package/dist/{chunk-J765H3HZ.js.map → chunk-YF6HCPVY.js.map} +0 -0
- /package/dist/{context-VAXF3EW3.js.map → context-4X4CLMU3.js.map} +0 -0
- /package/dist/{pull-ZQFCMK46.js.map → pull-7SR7P3US.js.map} +0 -0
- /package/dist/{require-deps-QW2IU6I3.js.map → require-deps-MCFEZOIF.js.map} +0 -0
- /package/dist/{stats-NPXPJNBO.js.map → stats-MLWRNOHU.js.map} +0 -0
- /package/dist/{sync-clean-JQLVE4WU.js.map → sync-clean-2BMOFDV7.js.map} +0 -0
- /package/dist/{sync-status-IYG7ZYC5.js.map → sync-status-J7BVY6KF.js.map} +0 -0
- /package/dist/{tui-74FMIMUM.js.map → tui-JE5L7SXC.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
|
|
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-
|
|
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-
|
|
13
|
+
} from "./chunk-WLD2PA3B.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-
|
|
35
|
+
//# sourceMappingURL=chunk-5YUKTNBM.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
|
|
211
|
-
|
|
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
|
|
215
|
-
|
|
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
|
|
219
|
-
|
|
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-
|
|
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":";;;AAEA,SAAS,YAAY,kBAAkB;AAQvC,SAAS,eAAe,KAA8B;AACpD,SAAO,EAAE,IAAI,IAAI,IAAI,SAAS,IAAI,SAAS,WAAW,IAAI,WAAW;AACvE;AAEA,SAAS,cAAc,KAAuB;AAC5C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,OAAO,OAAK,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,EAC9E,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAsBA,SAAS,YAAY,KAAwB;AAC3C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,OAAO,IAAI;AAAA,IACX,SAAS,IAAI;AAAA,IACb,SAAS,IAAI;AAAA,IACb,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI;AAAA,IACb,MAAM,cAAc,IAAI,IAAI;AAAA,IAC5B,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,IACjB,cAAc,IAAI;AAAA,IAClB,gBAAgB,IAAI;AAAA,EACtB;AACF;AAIO,IAAM,aAAN,MAAiB;AAAA,EACb;AAAA,EACA;AAAA,EAET,YAAY,IAAuB;AACjC,SAAK,KAAK;AACV,SAAK,SAAS;AAAA,MACZ,QAAQ,GAAG,QAAQ;AAAA;AAAA;AAAA,OAGlB;AAAA,MACD,SAAS,GAAG,QAAQ,qCAAqC;AAAA,MACzD,QAAQ,GAAG,QAAQ,iDAAiD;AAAA,MACpE,iBAAiB,GAAG,QAAQ,sFAAsF;AAAA,MAClH,WAAW,GAAG,QAAQ,gEAAgE;AAAA,MACtF,qBAAqB,GAAG,QAAQ,qGAAqG;AAAA,MACrI,WAAW,GAAG,QAAQ,yDAAyD;AAAA,MAC/E,oBAAoB,GAAG,QAAQ,8FAA8F;AAAA,MAC7H,2BAA2B,GAAG,QAAQ,6GAA6G;AAAA,MACnJ,QAAQ,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAMlB;AAAA,MACD,kBAAkB,GAAG,QAAQ,iEAAiE;AAAA,MAC9F,sBAAsB,GAAG,QAAQ,iDAAiD;AAAA,MAClF,iBAAiB,GAAG,QAAQ;AAAA;AAAA,OAE3B;AAAA,MACD,oBAAoB,GAAG,QAAQ;AAAA;AAAA,OAE9B;AAAA,MACD,YAAY,GAAG,QAAQ,iEAAiE;AAAA,MACxF,YAAY,GAAG,QAAQ,mCAAmC;AAAA,MAC1D,cAAc,GAAG,QAAQ,qCAAqC;AAAA,MAC9D,iBAAiB,GAAG,QAAQ,wCAAwC;AAAA,MACpE,OAAO,GAAG,QAAQ,wCAAwC;AAAA,MAC1D,gBAAgB,GAAG,QAAQ,0DAA0D;AAAA,MACrF,aAAa,GAAG,QAAQ,4DAA4D;AAAA,MACpF,eAAe,GAAG,QAAQ,kGAAoG;AAAA,MAC9H,WAAW,GAAG,QAAQ,2EAA2E;AAAA,MACjG,aAAa,GAAG,QAAQ;AAAA;AAAA;AAAA,OAGvB;AAAA,MACD,YAAY,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAMtB;AAAA,MACD,YAAY,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAQtB;AAAA,MACD,qBAAqB,GAAG;AAAA,QACtB;AAAA,MACF;AAAA,MACA,oBAAoB,GAAG,QAAQ,iDAAiD;AAAA,MAChF,uBAAuB,GAAG,QAAQ,oDAAoD;AAAA,MACtF,iBAAiB,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAO3B;AAAA,MACD,kBAAkB,GAAG,QAAQ,8CAA8C;AAAA,MAC3E,iBAAiB,GAAG,QAAQ,0DAA0D;AAAA,MACtF,6BAA6B,GAAG;AAAA,QAC9B;AAAA,MACF;AAAA,MACA,iBAAiB,GAAG,QAAQ,4CAA4C;AAAA,IAC1E;AAAA,EACF;AAAA,EAEA,OAAO,OAAmB,aAA4B,MAAqB;AACzE,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,KAAK,WAAW;AACtB,UAAM,cAAc,WAAW,QAAQ,EAAE,OAAO,MAAM,OAAO,EAAE,OAAO,KAAK;AAE3E,UAAM,SAAS;AAAA,MACb;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM,SAAS;AAAA,MACtB,SAAS,MAAM;AAAA,MACf,SAAS,MAAM,WAAW;AAAA,MAC1B,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM,WAAW;AAAA,MAC1B,MAAM,KAAK,UAAU,MAAM,IAAI;AAAA,MAC/B,YAAY,MAAM;AAAA,MAClB,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,MACX;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,OAAO,OAAO,IAAI,MAAM;AAC5C,QAAI,OAAO,YAAY,EAAG,QAAO;AAEjC,UAAM,MAAiB;AAAA,MACrB;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM,SAAS;AAAA,MACtB,SAAS,MAAM;AAAA,MACf,SAAS,MAAM,WAAW;AAAA,MAC1B,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM,WAAW;AAAA,MAC1B,MAAM,KAAK,UAAU,MAAM,IAAI;AAAA,MAC/B,YAAY,MAAM;AAAA,MAClB,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,WAAW;AAAA,IACb;AAEA,WAAO,YAAY,GAAG;AAAA,EACxB;AAAA,EAEA,QAAQ,IAAgC;AACtC,UAAM,MAAM,KAAK,OAAO,QAAQ,IAAI,EAAE;AACtC,WAAO,MAAM,YAAY,GAAG,IAAI;AAAA,EAClC;AAAA,EAEA,OAAO,SAAqC;AAC1C,QAAI,SAAS;AACX,YAAMA,QAAO,KAAK,OAAO,gBAAgB,IAAI,OAAO;AACpD,aAAOA,MAAK,IAAI,WAAW;AAAA,IAC7B;AACA,UAAM,OAAO,KAAK,OAAO,OAAO,IAAI;AACpC,WAAO,KAAK,IAAI,WAAW;AAAA,EAC7B;AAAA,EAEA,UAAU,OAAe,SAAkB,MAAsC;AAC/E,QAAI,QAAQ,SAAS;AACnB,YAAMA,QAAO,KAAK,OAAO,0BAA0B,IAAI,MAAM,SAAS,KAAK;AAC3E,aAAOA,MAAK,IAAI,WAAW;AAAA,IAC7B;AACA,QAAI,SAAS;AACX,YAAMA,QAAO,KAAK,OAAO,mBAAmB,IAAI,SAAS,KAAK;AAC9D,aAAOA,MAAK,IAAI,WAAW;AAAA,IAC7B;AACA,UAAM,OAAO,KAAK,OAAO,UAAU,IAAI,KAAK;AAC5C,WAAO,KAAK,IAAI,WAAW;AAAA,EAC7B;AAAA,EAEA,UAAU,MAAkB,SAAqC;AAC/D,QAAI,SAAS;AACX,YAAMA,QAAO,KAAK,OAAO,oBAAoB,IAAI,MAAM,OAAO;AAC9D,aAAOA,MAAK,IAAI,WAAW;AAAA,IAC7B;AACA,UAAM,OAAO,KAAK,OAAO,UAAU,IAAI,IAAI;AAC3C,WAAO,KAAK,IAAI,WAAW;AAAA,EAC7B;AAAA,EAEA,cAAc,IAAY,SAMd;AACV,UAAM,WAAW,KAAK,QAAQ,EAAE;AAChC,QAAI,CAAC,SAAU,QAAO;AAEtB,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAM,eAAe,QAAQ,WAAW,SAAS;AACjD,UAAM,SAAS;AAAA,MACb;AAAA,MACA,OAAO,QAAQ,UAAU,SAAY,QAAQ,QAAQ,SAAS;AAAA,MAC9D,SAAS;AAAA,MACT,SAAS,QAAQ,YAAY,SAAY,QAAQ,UAAU,SAAS;AAAA,MACpE,MAAM,KAAK,UAAU,QAAQ,QAAQ,SAAS,IAAI;AAAA,MAClD,YAAY,QAAQ,cAAc,SAAS;AAAA,MAC3C,WAAW;AAAA,MACX,WAAW;AAAA,MACX,aAAa,WAAW,QAAQ,EAAE,OAAO,YAAY,EAAE,OAAO,KAAK;AAAA,IACrE;AAEA,SAAK,OAAO,OAAO,IAAI,MAAM;AAC7B,WAAO;AAAA,EACT;AAAA,EAEA,iBAAiB,IAAY,YAA6B;AACxD,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,SAAS,KAAK,OAAO,iBAAiB,IAAI,YAAY,KAAK,EAAE;AACnE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA;AAAA,EAGA,qBAAqB,IAAY,YAA6B;AAC5D,UAAM,SAAS,KAAK,OAAO,qBAAqB,IAAI,YAAY,EAAE;AAClE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,gBAAgB,IAAkB;AAChC,SAAK,OAAO,gBAAgB,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,EAAE;AAAA,EAC9D;AAAA,EAEA,mBAAmB,IAAkB;AACnC,SAAK,OAAO,mBAAmB,IAAI,EAAE;AAAA,EACvC;AAAA,EAEA,WAAW,IAAqB;AAC9B,UAAM,SAAS,KAAK,OAAO,WAAW,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,EAAE;AACtE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,WAAW,IAAqB;AAC9B,UAAM,qBAAqB,KAAK,GAAG,YAAY,CAAC,aAAqB;AACnE,YAAM,SAAS,KAAK,QAAQ,QAAQ;AACpC,UAAI,CAAC,OAAQ,QAAO;AACpB,WAAK,OAAO,gBAAgB,IAAI,UAAU,OAAO,UAAS,oBAAI,KAAK,GAAE,YAAY,CAAC;AAClF,aAAO,KAAK,OAAO,WAAW,IAAI,QAAQ,EAAE;AAAA,IAC9C,CAAC;AACD,WAAO,mBAAmB,EAAE,IAAI;AAAA,EAClC;AAAA,EAEA,aAAa,MAA0B;AACrC,UAAM,qBAAqB,KAAK,GAAG,YAAY,CAAC,MAAkB;AAChE,YAAM,OAAO,KAAK,OAAO,mBAAmB,IAAI,CAAC;AACjD,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,iBAAW,OAAO,MAAM;AACtB,aAAK,OAAO,gBAAgB,IAAI,IAAI,IAAI,IAAI,SAAS,GAAG;AAAA,MAC1D;AACA,aAAO,KAAK,OAAO,aAAa,IAAI,CAAC,EAAE;AAAA,IACzC,CAAC;AACD,WAAO,mBAAmB,IAAI;AAAA,EAChC;AAAA,EAEA,gBAAgB,SAAyB;AACvC,UAAM,qBAAqB,KAAK,GAAG,YAAY,CAAC,MAAc;AAC5D,YAAM,OAAO,KAAK,OAAO,sBAAsB,IAAI,CAAC;AACpD,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,iBAAW,OAAO,MAAM;AACtB,aAAK,OAAO,gBAAgB,IAAI,IAAI,IAAI,IAAI,SAAS,GAAG;AAAA,MAC1D;AACA,aAAO,KAAK,OAAO,gBAAgB,IAAI,CAAC,EAAE;AAAA,IAC5C,CAAC;AACD,WAAO,mBAAmB,OAAO;AAAA,EACnC;AAAA,EAEA,aAAa,IAA8B;AACzC,UAAM,MAAM,KAAK,OAAO,iBAAiB,IAAI,EAAE;AAC/C,WAAO,MAAM,eAAe,GAAG,IAAI;AAAA,EACrC;AAAA,EAEA,mBAAyC;AACvC,UAAM,OAAO,KAAK,OAAO,gBAAgB,IAAI;AAC7C,WAAO,KAAK,IAAI,cAAc;AAAA,EAChC;AAAA,EAEA,uBAAuB,SAAuC;AAC5D,UAAM,OAAO,KAAK,OAAO,4BAA4B,IAAI,OAAO;AAChE,WAAO,KAAK,IAAI,cAAc;AAAA,EAChC;AAAA,EAEA,gBAAgB,IAAY,SAAwB,WAAyB;AAC3E,SAAK,OAAO,gBAAgB,IAAI,IAAI,SAAS,SAAS;AAAA,EACxD;AAAA,EAEA,gBAAgB,IAAkB;AAChC,SAAK,OAAO,gBAAgB,IAAI,EAAE;AAAA,EACpC;AAAA,EAEA,MAAM,SAA0B;AAC9B,QAAI,SAAS;AACX,YAAMC,OAAM,KAAK,OAAO,eAAe,IAAI,OAAO;AAClD,aAAOA,KAAI;AAAA,IACb;AACA,UAAM,MAAM,KAAK,OAAO,MAAM,IAAI;AAClC,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,cAAsC;AACpC,UAAM,OAAO,KAAK,OAAO,YAAY,IAAI;AACzC,WAAO,OAAO,YAAY,KAAK,IAAI,OAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAAA,EAC5D;AAAA,EAEA,gBAA6C;AAC3C,UAAM,OAAO,KAAK,OAAO,cAAc,IAAI;AAC3C,WAAO,IAAI,IAAI,KAAK,IAAI,OAAK,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAAA,EACpD;AAAA,EAEA,YAA8D;AAC5D,UAAM,MAAM,KAAK,OAAO,UAAU,IAAI;AACtC,WAAO,EAAE,QAAQ,IAAI,QAAQ,QAAQ,IAAI,OAAO;AAAA,EAClD;AAAA,EAEA,YAAY,QAAgB,GAA4E;AACtG,UAAM,OAAO,KAAK,OAAO,YAAY,IAAI,KAAK;AAC9C,WAAO,KAAK,IAAI,QAAM,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,OAAO,gBAAgB,EAAE,gBAAgB,EAAE;AAAA,EACxF;AAAA,EAEA,eAAe,KAA0B;AACvC,UAAM,SAAS;AAAA,MACb,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,OAAO,IAAI;AAAA,MACX,SAAS,IAAI;AAAA,MACb,SAAS,IAAI;AAAA,MACb,QAAQ,IAAI;AAAA,MACZ,SAAS,IAAI;AAAA,MACb,MAAM,KAAK,UAAU,IAAI,IAAI;AAAA,MAC7B,YAAY,IAAI;AAAA,MAChB,aAAa,IAAI;AAAA,MACjB,gBAAgB,IAAI;AAAA,MACpB,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,MACf,cAAc,IAAI;AAAA,MAClB,aAAa,WAAW,QAAQ,EAAE,OAAO,IAAI,OAAO,EAAE,OAAO,KAAK;AAAA,IACpE;AAEA,UAAM,WAAW,KAAK,QAAQ,IAAI,EAAE;AACpC,QAAI,UAAU;AACZ,WAAK,OAAO,WAAW,IAAI,MAAM;AAAA,IACnC,OAAO;AACL,WAAK,OAAO,WAAW,IAAI,MAAM;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,cAAc,SAAqC;AACjD,QAAI,SAAS;AACX,YAAM,OAAO,KAAK,OAAO,oBAAoB,IAAI,OAAO;AACxD,aAAO,KAAK,IAAI,WAAW;AAAA,IAC7B;AACA,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;;;AC1YA,SAAS,cAAc,KAA4B;AACjD,SAAO;AAAA,IACL,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,IACd,cAAc,IAAI;AAAA,IAClB,WAAW,IAAI;AAAA,EACjB;AACF;AAEO,IAAM,eAAN,MAAmB;AAAA,EACf;AAAA,EAET,YAAY,IAAuB;AACjC,SAAK,SAAS;AAAA,MACZ,QAAQ,GAAG,QAAQ;AAAA;AAAA;AAAA,OAGlB;AAAA,MACD,aAAa,GAAG,QAAQ,6CAA6C;AAAA,MACrE,aAAa,GAAG,QAAQ,6CAA6C;AAAA,MACrE,aAAa,GAAG,QAAQ;AAAA;AAAA,OAEvB;AAAA,MACD,QAAQ,GAAG,QAAQ;AAAA;AAAA,OAElB;AAAA,MACD,eAAe,GAAG,QAAQ;AAAA;AAAA,OAEzB;AAAA,MACD,OAAO,GAAG,QAAQ,yCAAyC;AAAA,MAC3D,QAAQ,GAAG,QAAQ,yBAAyB;AAAA,MAC5C,gBAAgB,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,OAI1B;AAAA,IACH;AAAA,EACF;AAAA,EAEA,OAAO,UAAkB,UAAkB,cAAqC;AAC9E,UAAM,SAAS,KAAK,OAAO,OAAO,IAAI,UAAU,UAAU,YAAY;AACtE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,YAAY,UAAuC;AACjD,UAAM,OAAO,KAAK,OAAO,YAAY,IAAI,QAAQ;AACjD,WAAO,KAAK,IAAI,aAAa;AAAA,EAC/B;AAAA,EAEA,YAAY,UAAuC;AACjD,UAAM,OAAO,KAAK,OAAO,YAAY,IAAI,QAAQ;AACjD,WAAO,KAAK,IAAI,aAAa;AAAA,EAC/B;AAAA,EAEA,YAAY,UAAuC;AACjD,UAAM,OAAO,KAAK,OAAO,YAAY,IAAI,UAAU,QAAQ;AAC3D,WAAO,KAAK,IAAI,aAAa;AAAA,EAC/B;AAAA,EAEA,OAAO,UAAkB,UAAkB,cAAqC;AAC9E,UAAM,SAAS,KAAK,OAAO,OAAO,IAAI,UAAU,UAAU,YAAY;AACtE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,cAAc,UAA0B;AACtC,UAAM,MAAM,KAAK,OAAO,cAAc,IAAI,UAAU,QAAQ;AAC5D,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,QAAgB;AACd,UAAM,MAAM,KAAK,OAAO,MAAM,IAAI;AAClC,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,SAA8B;AAC5B,UAAM,OAAO,KAAK,OAAO,OAAO,IAAI;AACpC,WAAO,KAAK,IAAI,aAAa;AAAA,EAC/B;AAAA,EAEA,iBAAyB;AACvB,UAAM,SAAS,KAAK,OAAO,eAAe,IAAI;AAC9C,WAAO,OAAO;AAAA,EAChB;AACF;;;AC/EO,IAAM,aAAN,MAAiB;AAAA,EACb;AAAA,EAET,YAAY,IAAuB;AAEjC,SAAK,SAAS;AAAA,MACZ,WAAW,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAUrB;AAAA,MACD,oBAAoB,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAW9B;AAAA,MACD,mBAAmB,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAY7B;AAAA,MACD,4BAA4B,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAatC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,SAAgD;AACxD,UAAM,WAAW,WAAW,QAAQ,KAAK;AACzC,QAAI,CAAC,SAAU,QAAO,CAAC;AAEvB,QAAI;AACF,YAAM,UAAU,CAAC,CAAC,QAAQ;AAC1B,YAAM,aAAa,CAAC,CAAC,QAAQ;AAE7B,UAAI;AACJ,UAAI,WAAW,YAAY;AACzB,eAAO,KAAK,OAAO,2BAA2B,IAAI;AAAA,UAChD,OAAO;AAAA,UAAU,OAAO,QAAQ;AAAA,UAChC,MAAM,QAAQ;AAAA,UAAM,eAAe,QAAQ,iBAAiB;AAAA,UAC5D,SAAS,QAAQ;AAAA,QACnB,CAAC;AAAA,MACH,WAAW,SAAS;AAClB,eAAO,KAAK,OAAO,kBAAkB,IAAI;AAAA,UACvC,OAAO;AAAA,UAAU,OAAO,QAAQ;AAAA,UAChC,MAAM,QAAQ;AAAA,UAAM,eAAe,QAAQ,iBAAiB;AAAA,QAC9D,CAAC;AAAA,MACH,WAAW,YAAY;AACrB,eAAO,KAAK,OAAO,mBAAmB,IAAI;AAAA,UACxC,OAAO;AAAA,UAAU,OAAO,QAAQ;AAAA,UAChC,SAAS,QAAQ;AAAA,QACnB,CAAC;AAAA,MACH,OAAO;AACL,eAAO,KAAK,OAAO,UAAU,IAAI;AAAA,UAC/B,OAAO;AAAA,UAAU,OAAO,QAAQ;AAAA,QAClC,CAAC;AAAA,MACH;AAEA,aAAO,KAAK,IAAI,QAAM;AAAA,QACpB,OAAO,EAAE;AAAA,QACT,UAAU,EAAE;AAAA,QACZ,MAAM,EAAE;AAAA,MACV,EAAE;AAAA,IACJ,SAAS,KAAK;AAEZ,cAAQ,MAAM,uCAAuC,eAAe,QAAQ,IAAI,UAAU,GAAG;AAC7F,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAEF;AAWA,IAAM,WAA8C;AAAA,EAClD,MAAM,CAAC,kBAAkB,SAAS,SAAS,KAAK;AAAA,EAChD,gBAAgB,CAAC,QAAQ,SAAS,OAAO;AAAA,EACzC,OAAO,CAAC,QAAQ,kBAAkB,QAAQ;AAAA,EAC1C,IAAI,CAAC,YAAY,OAAO,UAAU,UAAU;AAAA,EAC5C,UAAU,CAAC,MAAM,OAAO,UAAU,UAAU;AAAA,EAC5C,KAAK,CAAC,YAAY,SAAS,QAAQ,SAAS;AAAA,EAC5C,QAAQ,CAAC,cAAc,WAAW,QAAQ,SAAS;AAAA,EACnD,MAAM,CAAC,WAAW,QAAQ,QAAQ,QAAQ;AAAA,EAC1C,QAAQ,CAAC,iBAAiB,YAAY,OAAO;AAAA,EAC7C,KAAK,CAAC,SAAS,aAAa,SAAS,KAAK;AAAA,EAC1C,OAAO,CAAC,OAAO,aAAa,SAAS,KAAK;AAC5C;AAMA,SAAS,WAAW,OAA8B;AAChD,QAAM,QAAQ,MACX,QAAQ,YAAY,GAAG,EACvB,MAAM,KAAK,EACX,OAAO,OAAK,EAAE,SAAS,CAAC;AAE3B,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,WAAW,MAAM,QAAQ,CAAC,MAAM;AACpC,UAAM,QAAQ,EAAE,YAAY;AAC5B,UAAM,OAAO,SAAS,KAAK;AAC3B,WAAO,OAAO,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC;AAAA,EACjC,CAAC;AAED,SAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,EAAE,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,MAAM;AAC9D;","names":["rows","row"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
SyncPayloadSchema
|
|
4
|
-
} from "./chunk-
|
|
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
|
|
38
|
-
|
|
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-
|
|
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":[]}
|
|
@@ -620,6 +620,15 @@ function generateEnhanceSkill() {
|
|
|
620
620
|
].join("\n");
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
+
// src/lib/stub-marker.ts
|
|
624
|
+
var LP_STUB_OPEN = "<!-- LP-STUB: ai-recommended -->";
|
|
625
|
+
var LP_STUB_CLOSE = "<!-- /LP-STUB -->";
|
|
626
|
+
function wrapStub(content) {
|
|
627
|
+
return `${LP_STUB_OPEN}
|
|
628
|
+
${content}
|
|
629
|
+
${LP_STUB_CLOSE}`;
|
|
630
|
+
}
|
|
631
|
+
|
|
623
632
|
// src/commands/doctor/fixer-memory.ts
|
|
624
633
|
import { readFile as readFile3 } from "fs/promises";
|
|
625
634
|
import { join as join3 } from "path";
|
|
@@ -684,9 +693,39 @@ async function addSessionStartPullHook(root, placement) {
|
|
|
684
693
|
async function addSessionEndPushHook(root, placement) {
|
|
685
694
|
const target = placement === "local" ? "settings.local.json" : "settings.json";
|
|
686
695
|
return addPlacementHook(root, placement, "SessionEnd", "memory push", {
|
|
687
|
-
hooks: [{ type: "command", command: "claude-launchpad memory push -y >/dev/null 2>&1
|
|
696
|
+
hooks: [{ type: "command", command: "claude-launchpad memory push -y >/dev/null 2>&1; exit 0" }]
|
|
688
697
|
}, false, `Added SessionEnd hook for memory sync to ${target}`);
|
|
689
698
|
}
|
|
699
|
+
async function upgradeStaleSessionEndPushHook(root) {
|
|
700
|
+
let changedAny = false;
|
|
701
|
+
for (const placement of ["shared", "local"]) {
|
|
702
|
+
const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
|
|
703
|
+
const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
|
|
704
|
+
const settings = await read(root);
|
|
705
|
+
const hooks = settings.hooks;
|
|
706
|
+
const sessionEnd = hooks?.SessionEnd;
|
|
707
|
+
if (!sessionEnd) continue;
|
|
708
|
+
let changed = false;
|
|
709
|
+
const upgraded = sessionEnd.map((group) => {
|
|
710
|
+
const inner = group.hooks;
|
|
711
|
+
if (!inner) return group;
|
|
712
|
+
const rewritten = inner.map((h) => {
|
|
713
|
+
const cmd = typeof h.command === "string" ? h.command : "";
|
|
714
|
+
if (!cmd.includes("memory push") || !/&\s*exit\s+0\s*$/.test(cmd)) return h;
|
|
715
|
+
changed = true;
|
|
716
|
+
return { ...h, command: cmd.replace(/&\s*exit\s+0\s*$/, "; exit 0") };
|
|
717
|
+
});
|
|
718
|
+
return { ...group, hooks: rewritten };
|
|
719
|
+
});
|
|
720
|
+
if (!changed) continue;
|
|
721
|
+
const updated = { ...settings, hooks: { ...hooks, SessionEnd: upgraded } };
|
|
722
|
+
await write(root, updated);
|
|
723
|
+
const target = placement === "local" ? "settings.local.json" : "settings.json";
|
|
724
|
+
log.success(`Upgraded SessionEnd push hook in ${target} (now synchronous)`);
|
|
725
|
+
changedAny = true;
|
|
726
|
+
}
|
|
727
|
+
return changedAny;
|
|
728
|
+
}
|
|
690
729
|
async function removeStaleStopHook(root) {
|
|
691
730
|
const settings = await readSettingsJson(root);
|
|
692
731
|
const hooks = settings.hooks;
|
|
@@ -765,18 +804,21 @@ var FIX_TABLE = [
|
|
|
765
804
|
{ analyzer: "Hooks", match: ".env file protection", fix: (root) => addEnvProtectionHook(root) },
|
|
766
805
|
{ analyzer: "Hooks", match: "auto-format", fix: (root, detected) => addAutoFormatHook(root, detected) },
|
|
767
806
|
{ analyzer: "Hooks", match: "No PreToolUse", fix: (root) => addEnvProtectionHook(root) },
|
|
768
|
-
{ analyzer: "Quality", match: "Architecture", fix: (root) => addClaudeMdSection(root, "## Architecture", "<!-- TODO: Describe your codebase structure. Run `/lp-enhance` to auto-fill this. -->") },
|
|
769
|
-
{ analyzer: "Quality", match: "Off-Limits", fix: (root) => addClaudeMdSection(root, "## Off-Limits", OFF_LIMITS_CONTENT) },
|
|
770
|
-
{ analyzer: "Quality", match: "Commands", fix: (root) => addClaudeMdSection(root, "## Commands", "<!-- TODO: Add your dev/build/test commands -->") },
|
|
807
|
+
{ analyzer: "Quality", match: "Architecture", fix: (root) => addClaudeMdSection(root, "## Architecture", wrapStub("<!-- TODO: Describe your codebase structure. Run `/lp-enhance` to auto-fill this. -->")) },
|
|
808
|
+
{ analyzer: "Quality", match: "Off-Limits", fix: (root) => addClaudeMdSection(root, "## Off-Limits", wrapStub(OFF_LIMITS_CONTENT)) },
|
|
809
|
+
{ analyzer: "Quality", match: "Commands", fix: (root) => addClaudeMdSection(root, "## Commands", wrapStub("<!-- TODO: Add your dev/build/test commands -->")) },
|
|
771
810
|
{ analyzer: "Quality", match: "Stack", fix: (root, detected) => {
|
|
772
|
-
|
|
811
|
+
if (detected.language) {
|
|
812
|
+
const content = `- **Language**: ${detected.language}${detected.framework ? `
|
|
773
813
|
- **Framework**: ${detected.framework}` : ""}${detected.packageManager ? `
|
|
774
|
-
- **Package Manager**: ${detected.packageManager}` : ""}
|
|
775
|
-
|
|
814
|
+
- **Package Manager**: ${detected.packageManager}` : ""}`;
|
|
815
|
+
return addClaudeMdSection(root, "## Stack", content);
|
|
816
|
+
}
|
|
817
|
+
return addClaudeMdSection(root, "## Stack", wrapStub("<!-- TODO: Define your tech stack -->"));
|
|
776
818
|
} },
|
|
777
|
-
{ analyzer: "Quality", match: "Session Start", fix: (root) => addClaudeMdSection(root, "## Session Start", SESSION_START_CONTENT) },
|
|
778
|
-
{ analyzer: "Quality", match: "Backlog", fix: (root) => addClaudeMdSection(root, "## Backlog", BACKLOG_CONTENT) },
|
|
779
|
-
{ analyzer: "Quality", match: "Stop-and-Swarm", fix: (root) => addClaudeMdSection(root, "## Stop-and-Swarm", STOP_AND_SWARM_CONTENT) },
|
|
819
|
+
{ analyzer: "Quality", match: "Session Start", fix: (root) => addClaudeMdSection(root, "## Session Start", wrapStub(SESSION_START_CONTENT)) },
|
|
820
|
+
{ analyzer: "Quality", match: "Backlog", fix: (root) => addClaudeMdSection(root, "## Backlog", wrapStub(BACKLOG_CONTENT)) },
|
|
821
|
+
{ analyzer: "Quality", match: "Stop-and-Swarm", fix: (root) => addClaudeMdSection(root, "## Stop-and-Swarm", wrapStub(STOP_AND_SWARM_CONTENT)) },
|
|
780
822
|
{ analyzer: "Rules", match: "No BACKLOG.md", fix: (root) => createBacklogMd(root) },
|
|
781
823
|
{ analyzer: "Rules", match: "No .claudeignore", fix: (root, detected) => createClaudeignore(root, detected) },
|
|
782
824
|
{ analyzer: "Rules", match: "No .claude/rules/", fix: (root) => createStarterRules(root) },
|
|
@@ -797,10 +839,11 @@ var FIX_TABLE = [
|
|
|
797
839
|
{ analyzer: "MCP", match: "no allowedMcpServers", fix: (root, _det, placement) => addAllowedMcpServers(root, placement) },
|
|
798
840
|
{ analyzer: "Memory", match: "SessionStart hook to auto-pull", fix: (root, _det, placement) => addSessionStartPullHook(root, placement) },
|
|
799
841
|
{ analyzer: "Memory", match: "SessionEnd hook to auto-push", fix: (root, _det, placement) => addSessionEndPushHook(root, placement) },
|
|
842
|
+
{ analyzer: "Memory", match: "SessionEnd push hook is backgrounded", fix: (root) => upgradeStaleSessionEndPushHook(root) },
|
|
800
843
|
{ analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root, _det, placement) => {
|
|
801
844
|
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
845
|
const target = placement === "local" ? join4(root, ".claude", "CLAUDE.md") : void 0;
|
|
803
|
-
return addClaudeMdSection(root, "## Memory", content, target);
|
|
846
|
+
return addClaudeMdSection(root, "## Memory", wrapStub(content), target);
|
|
804
847
|
} }
|
|
805
848
|
];
|
|
806
849
|
function hasAutoFix(issue) {
|
|
@@ -1166,10 +1209,11 @@ export {
|
|
|
1166
1209
|
readSettingsLocalJson,
|
|
1167
1210
|
writeSettingsLocalJson,
|
|
1168
1211
|
getMemoryPlacement,
|
|
1212
|
+
LP_STUB_OPEN,
|
|
1169
1213
|
applyFixes,
|
|
1170
1214
|
log,
|
|
1171
1215
|
printBanner,
|
|
1172
1216
|
printScoreCard,
|
|
1173
1217
|
renderDoctorReport
|
|
1174
1218
|
};
|
|
1175
|
-
//# sourceMappingURL=chunk-
|
|
1219
|
+
//# sourceMappingURL=chunk-GLFJ2B43.js.map
|