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.
Files changed (44) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-UJP5PJTA.js → chunk-5YUKTNBM.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-YXPJDIMK.js → chunk-GLFJ2B43.js} +56 -12
  8. package/dist/chunk-GLFJ2B43.js.map +1 -0
  9. package/dist/{chunk-AR64LWGW.js → chunk-WLD2PA3B.js} +25 -4
  10. package/dist/chunk-WLD2PA3B.js.map +1 -0
  11. package/dist/{chunk-J765H3HZ.js → chunk-YF6HCPVY.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 +313 -58
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/memory/server.js +5 -5
  17. package/dist/{context-VAXF3EW3.js → context-4X4CLMU3.js} +6 -6
  18. package/dist/{install-M3JWBGMK.js → install-P4TFYUJT.js} +6 -6
  19. package/dist/install-P4TFYUJT.js.map +1 -0
  20. package/dist/{pull-ZQFCMK46.js → pull-7SR7P3US.js} +9 -9
  21. package/dist/{push-5ZJNWAE7.js → push-SCTO5TZQ.js} +40 -17
  22. package/dist/push-SCTO5TZQ.js.map +1 -0
  23. package/dist/{require-deps-QW2IU6I3.js → require-deps-MCFEZOIF.js} +3 -3
  24. package/dist/{stats-NPXPJNBO.js → stats-MLWRNOHU.js} +7 -7
  25. package/dist/{sync-clean-JQLVE4WU.js → sync-clean-2BMOFDV7.js} +2 -2
  26. package/dist/{sync-status-IYG7ZYC5.js → sync-status-J7BVY6KF.js} +9 -9
  27. package/dist/{tui-74FMIMUM.js → tui-JE5L7SXC.js} +5 -5
  28. package/package.json +3 -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-5YUKTNBM.js.map} +0 -0
  37. /package/dist/{chunk-J765H3HZ.js.map → chunk-YF6HCPVY.js.map} +0 -0
  38. /package/dist/{context-VAXF3EW3.js.map → context-4X4CLMU3.js.map} +0 -0
  39. /package/dist/{pull-ZQFCMK46.js.map → pull-7SR7P3US.js.map} +0 -0
  40. /package/dist/{require-deps-QW2IU6I3.js.map → require-deps-MCFEZOIF.js.map} +0 -0
  41. /package/dist/{stats-NPXPJNBO.js.map → stats-MLWRNOHU.js.map} +0 -0
  42. /package/dist/{sync-clean-JQLVE4WU.js.map → sync-clean-2BMOFDV7.js.map} +0 -0
  43. /package/dist/{sync-status-IYG7ZYC5.js.map → sync-status-J7BVY6KF.js.map} +0 -0
  44. /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 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-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-UJP5PJTA.js.map
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 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":[]}
@@ -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 & exit 0" }]
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
- const content = detected.language ? `- **Language**: ${detected.language}${detected.framework ? `
811
+ if (detected.language) {
812
+ const content = `- **Language**: ${detected.language}${detected.framework ? `
773
813
  - **Framework**: ${detected.framework}` : ""}${detected.packageManager ? `
774
- - **Package Manager**: ${detected.packageManager}` : ""}` : "<!-- TODO: Define your tech stack -->";
775
- return addClaudeMdSection(root, "## Stack", content);
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-YXPJDIMK.js.map
1219
+ //# sourceMappingURL=chunk-GLFJ2B43.js.map