@lossless-claude/lcm 0.2.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 (292) hide show
  1. package/.claude-plugin/commands/lcm-compact.md +31 -0
  2. package/.claude-plugin/commands/lcm-curate.md +31 -0
  3. package/.claude-plugin/commands/lcm-diagnose.md +29 -0
  4. package/.claude-plugin/commands/lcm-doctor.md +23 -0
  5. package/.claude-plugin/commands/lcm-dogfood.md +101 -0
  6. package/.claude-plugin/commands/lcm-import.md +48 -0
  7. package/.claude-plugin/commands/lcm-promote.md +29 -0
  8. package/.claude-plugin/commands/lcm-sensitive.md +55 -0
  9. package/.claude-plugin/commands/lcm-stats.md +19 -0
  10. package/.claude-plugin/commands/lcm-status.md +27 -0
  11. package/.claude-plugin/hooks/README.md +47 -0
  12. package/.claude-plugin/marketplace.json +20 -0
  13. package/.claude-plugin/mcp.mjs +12 -0
  14. package/.claude-plugin/plugin.json +46 -0
  15. package/.claude-plugin/skills/lcm-context/SKILL.md +107 -0
  16. package/.claude-plugin/skills/lcm-dogfood/SKILL.md +102 -0
  17. package/.claude-plugin/skills/lcm-dogfood/references/checks.md +239 -0
  18. package/.claude-plugin/skills/lcm-dogfood/references/known-issues.md +11 -0
  19. package/.claude-plugin/skills/lcm-dogfood/scripts/db-integrity.js +40 -0
  20. package/.claude-plugin/skills/lcm-dogfood/scripts/prompt-search-test.js +35 -0
  21. package/.claude-plugin/skills/lcm-e2e/SKILL.md +61 -0
  22. package/.claude-plugin/skills/lcm-e2e/checklist.md +367 -0
  23. package/.claude-plugin/skills/lossless-claude-upgrade/SKILL.md +47 -0
  24. package/LICENSE +21 -0
  25. package/README.md +231 -0
  26. package/dist/bin/lcm.d.ts +2 -0
  27. package/dist/bin/lcm.js +461 -0
  28. package/dist/bin/lcm.js.map +1 -0
  29. package/dist/installer/dry-run-deps.d.ts +23 -0
  30. package/dist/installer/dry-run-deps.js +66 -0
  31. package/dist/installer/dry-run-deps.js.map +1 -0
  32. package/dist/installer/install.d.ts +39 -0
  33. package/dist/installer/install.js +236 -0
  34. package/dist/installer/install.js.map +1 -0
  35. package/dist/installer/uninstall.d.ts +11 -0
  36. package/dist/installer/uninstall.js +80 -0
  37. package/dist/installer/uninstall.js.map +1 -0
  38. package/dist/src/batch-compact.d.ts +16 -0
  39. package/dist/src/batch-compact.js +121 -0
  40. package/dist/src/batch-compact.js.map +1 -0
  41. package/dist/src/compaction.d.ts +198 -0
  42. package/dist/src/compaction.js +964 -0
  43. package/dist/src/compaction.js.map +1 -0
  44. package/dist/src/connectors/constants.d.ts +5 -0
  45. package/dist/src/connectors/constants.js +6 -0
  46. package/dist/src/connectors/constants.js.map +1 -0
  47. package/dist/src/connectors/installer.d.ts +16 -0
  48. package/dist/src/connectors/installer.js +200 -0
  49. package/dist/src/connectors/installer.js.map +1 -0
  50. package/dist/src/connectors/registry.d.ts +4 -0
  51. package/dist/src/connectors/registry.js +264 -0
  52. package/dist/src/connectors/registry.js.map +1 -0
  53. package/dist/src/connectors/template-service.d.ts +5 -0
  54. package/dist/src/connectors/template-service.js +54 -0
  55. package/dist/src/connectors/template-service.js.map +1 -0
  56. package/dist/src/connectors/templates/base.md +1 -0
  57. package/dist/src/connectors/templates/mcp-base.md +1 -0
  58. package/dist/src/connectors/templates/sections/command-reference.md +15 -0
  59. package/dist/src/connectors/templates/sections/mcp-workflow.md +18 -0
  60. package/dist/src/connectors/templates/sections/workflow.md +29 -0
  61. package/dist/src/connectors/templates/skill/SKILL.md +74 -0
  62. package/dist/src/connectors/types.d.ts +19 -0
  63. package/dist/src/connectors/types.js +10 -0
  64. package/dist/src/connectors/types.js.map +1 -0
  65. package/dist/src/daemon/client.d.ts +9 -0
  66. package/dist/src/daemon/client.js +28 -0
  67. package/dist/src/daemon/client.js.map +1 -0
  68. package/dist/src/daemon/config.d.ts +48 -0
  69. package/dist/src/daemon/config.js +67 -0
  70. package/dist/src/daemon/config.js.map +1 -0
  71. package/dist/src/daemon/lifecycle.d.ts +19 -0
  72. package/dist/src/daemon/lifecycle.js +102 -0
  73. package/dist/src/daemon/lifecycle.js.map +1 -0
  74. package/dist/src/daemon/orientation.d.ts +1 -0
  75. package/dist/src/daemon/orientation.js +9 -0
  76. package/dist/src/daemon/orientation.js.map +1 -0
  77. package/dist/src/daemon/project-queue.d.ts +1 -0
  78. package/dist/src/daemon/project-queue.js +17 -0
  79. package/dist/src/daemon/project-queue.js.map +1 -0
  80. package/dist/src/daemon/project.d.ts +7 -0
  81. package/dist/src/daemon/project.js +25 -0
  82. package/dist/src/daemon/project.js.map +1 -0
  83. package/dist/src/daemon/proxy-manager.d.ts +21 -0
  84. package/dist/src/daemon/proxy-manager.js +205 -0
  85. package/dist/src/daemon/proxy-manager.js.map +1 -0
  86. package/dist/src/daemon/routes/compact.d.ts +13 -0
  87. package/dist/src/daemon/routes/compact.js +195 -0
  88. package/dist/src/daemon/routes/compact.js.map +1 -0
  89. package/dist/src/daemon/routes/describe.d.ts +3 -0
  90. package/dist/src/daemon/routes/describe.js +39 -0
  91. package/dist/src/daemon/routes/describe.js.map +1 -0
  92. package/dist/src/daemon/routes/expand.d.ts +3 -0
  93. package/dist/src/daemon/routes/expand.js +41 -0
  94. package/dist/src/daemon/routes/expand.js.map +1 -0
  95. package/dist/src/daemon/routes/grep.d.ts +3 -0
  96. package/dist/src/daemon/routes/grep.js +43 -0
  97. package/dist/src/daemon/routes/grep.js.map +1 -0
  98. package/dist/src/daemon/routes/ingest.d.ts +3 -0
  99. package/dist/src/daemon/routes/ingest.js +101 -0
  100. package/dist/src/daemon/routes/ingest.js.map +1 -0
  101. package/dist/src/daemon/routes/promote.d.ts +4 -0
  102. package/dist/src/daemon/routes/promote.js +104 -0
  103. package/dist/src/daemon/routes/promote.js.map +1 -0
  104. package/dist/src/daemon/routes/prompt-search.d.ts +3 -0
  105. package/dist/src/daemon/routes/prompt-search.js +65 -0
  106. package/dist/src/daemon/routes/prompt-search.js.map +1 -0
  107. package/dist/src/daemon/routes/recent.d.ts +3 -0
  108. package/dist/src/daemon/routes/recent.js +37 -0
  109. package/dist/src/daemon/routes/recent.js.map +1 -0
  110. package/dist/src/daemon/routes/restore.d.ts +3 -0
  111. package/dist/src/daemon/routes/restore.js +120 -0
  112. package/dist/src/daemon/routes/restore.js.map +1 -0
  113. package/dist/src/daemon/routes/search.d.ts +2 -0
  114. package/dist/src/daemon/routes/search.js +66 -0
  115. package/dist/src/daemon/routes/search.js.map +1 -0
  116. package/dist/src/daemon/routes/status.d.ts +3 -0
  117. package/dist/src/daemon/routes/status.js +80 -0
  118. package/dist/src/daemon/routes/status.js.map +1 -0
  119. package/dist/src/daemon/routes/store.d.ts +2 -0
  120. package/dist/src/daemon/routes/store.js +46 -0
  121. package/dist/src/daemon/routes/store.js.map +1 -0
  122. package/dist/src/daemon/server.d.ts +19 -0
  123. package/dist/src/daemon/server.js +183 -0
  124. package/dist/src/daemon/server.js.map +1 -0
  125. package/dist/src/daemon/summarizer.d.ts +11 -0
  126. package/dist/src/daemon/summarizer.js +51 -0
  127. package/dist/src/daemon/summarizer.js.map +1 -0
  128. package/dist/src/db/config.d.ts +31 -0
  129. package/dist/src/db/config.js +83 -0
  130. package/dist/src/db/config.js.map +1 -0
  131. package/dist/src/db/connection.d.ts +3 -0
  132. package/dist/src/db/connection.js +62 -0
  133. package/dist/src/db/connection.js.map +1 -0
  134. package/dist/src/db/features.d.ts +11 -0
  135. package/dist/src/db/features.js +36 -0
  136. package/dist/src/db/features.js.map +1 -0
  137. package/dist/src/db/migration.d.ts +4 -0
  138. package/dist/src/db/migration.js +499 -0
  139. package/dist/src/db/migration.js.map +1 -0
  140. package/dist/src/db/promoted.d.ts +47 -0
  141. package/dist/src/db/promoted.js +96 -0
  142. package/dist/src/db/promoted.js.map +1 -0
  143. package/dist/src/db/redaction-stats.d.ts +6 -0
  144. package/dist/src/db/redaction-stats.js +16 -0
  145. package/dist/src/db/redaction-stats.js.map +1 -0
  146. package/dist/src/diagnose.d.ts +39 -0
  147. package/dist/src/diagnose.js +432 -0
  148. package/dist/src/diagnose.js.map +1 -0
  149. package/dist/src/doctor/doctor.d.ts +4 -0
  150. package/dist/src/doctor/doctor.js +378 -0
  151. package/dist/src/doctor/doctor.js.map +1 -0
  152. package/dist/src/doctor/types.d.ts +24 -0
  153. package/dist/src/doctor/types.js +2 -0
  154. package/dist/src/doctor/types.js.map +1 -0
  155. package/dist/src/expansion.d.ts +100 -0
  156. package/dist/src/expansion.js +268 -0
  157. package/dist/src/expansion.js.map +1 -0
  158. package/dist/src/hooks/auto-heal.d.ts +12 -0
  159. package/dist/src/hooks/auto-heal.js +49 -0
  160. package/dist/src/hooks/auto-heal.js.map +1 -0
  161. package/dist/src/hooks/compact.d.ts +5 -0
  162. package/dist/src/hooks/compact.js +22 -0
  163. package/dist/src/hooks/compact.js.map +1 -0
  164. package/dist/src/hooks/dispatch.d.ts +7 -0
  165. package/dist/src/hooks/dispatch.js +36 -0
  166. package/dist/src/hooks/dispatch.js.map +1 -0
  167. package/dist/src/hooks/probe-precompact.d.ts +2 -0
  168. package/dist/src/hooks/probe-precompact.js +17 -0
  169. package/dist/src/hooks/probe-precompact.js.map +1 -0
  170. package/dist/src/hooks/probe-sessionstart.d.ts +2 -0
  171. package/dist/src/hooks/probe-sessionstart.js +18 -0
  172. package/dist/src/hooks/probe-sessionstart.js.map +1 -0
  173. package/dist/src/hooks/restore.d.ts +5 -0
  174. package/dist/src/hooks/restore.js +19 -0
  175. package/dist/src/hooks/restore.js.map +1 -0
  176. package/dist/src/hooks/session-end.d.ts +16 -0
  177. package/dist/src/hooks/session-end.js +73 -0
  178. package/dist/src/hooks/session-end.js.map +1 -0
  179. package/dist/src/hooks/user-prompt.d.ts +5 -0
  180. package/dist/src/hooks/user-prompt.js +31 -0
  181. package/dist/src/hooks/user-prompt.js.map +1 -0
  182. package/dist/src/import.d.ts +24 -0
  183. package/dist/src/import.js +119 -0
  184. package/dist/src/import.js.map +1 -0
  185. package/dist/src/large-files.d.ts +28 -0
  186. package/dist/src/large-files.js +413 -0
  187. package/dist/src/large-files.js.map +1 -0
  188. package/dist/src/llm/anthropic.d.ts +9 -0
  189. package/dist/src/llm/anthropic.js +54 -0
  190. package/dist/src/llm/anthropic.js.map +1 -0
  191. package/dist/src/llm/claude-process.d.ts +2 -0
  192. package/dist/src/llm/claude-process.js +55 -0
  193. package/dist/src/llm/claude-process.js.map +1 -0
  194. package/dist/src/llm/codex-process.d.ts +15 -0
  195. package/dist/src/llm/codex-process.js +142 -0
  196. package/dist/src/llm/codex-process.js.map +1 -0
  197. package/dist/src/llm/mock-summarizer.d.ts +9 -0
  198. package/dist/src/llm/mock-summarizer.js +17 -0
  199. package/dist/src/llm/mock-summarizer.js.map +1 -0
  200. package/dist/src/llm/openai.d.ts +10 -0
  201. package/dist/src/llm/openai.js +52 -0
  202. package/dist/src/llm/openai.js.map +1 -0
  203. package/dist/src/llm/types.d.ts +6 -0
  204. package/dist/src/llm/types.js +2 -0
  205. package/dist/src/llm/types.js.map +1 -0
  206. package/dist/src/mcp/server.d.ts +9 -0
  207. package/dist/src/mcp/server.js +128 -0
  208. package/dist/src/mcp/server.js.map +1 -0
  209. package/dist/src/mcp/tools/lcm-describe.d.ts +14 -0
  210. package/dist/src/mcp/tools/lcm-describe.js +12 -0
  211. package/dist/src/mcp/tools/lcm-describe.js.map +1 -0
  212. package/dist/src/mcp/tools/lcm-doctor.d.ts +8 -0
  213. package/dist/src/mcp/tools/lcm-doctor.js +9 -0
  214. package/dist/src/mcp/tools/lcm-doctor.js.map +1 -0
  215. package/dist/src/mcp/tools/lcm-expand.d.ts +18 -0
  216. package/dist/src/mcp/tools/lcm-expand.js +13 -0
  217. package/dist/src/mcp/tools/lcm-expand.js.map +1 -0
  218. package/dist/src/mcp/tools/lcm-grep.d.ts +27 -0
  219. package/dist/src/mcp/tools/lcm-grep.js +15 -0
  220. package/dist/src/mcp/tools/lcm-grep.js.map +1 -0
  221. package/dist/src/mcp/tools/lcm-search.d.ts +33 -0
  222. package/dist/src/mcp/tools/lcm-search.js +15 -0
  223. package/dist/src/mcp/tools/lcm-search.js.map +1 -0
  224. package/dist/src/mcp/tools/lcm-stats.d.ts +14 -0
  225. package/dist/src/mcp/tools/lcm-stats.js +11 -0
  226. package/dist/src/mcp/tools/lcm-stats.js.map +1 -0
  227. package/dist/src/mcp/tools/lcm-store.d.ts +26 -0
  228. package/dist/src/mcp/tools/lcm-store.js +22 -0
  229. package/dist/src/mcp/tools/lcm-store.js.map +1 -0
  230. package/dist/src/memory/index.d.ts +22 -0
  231. package/dist/src/memory/index.js +21 -0
  232. package/dist/src/memory/index.js.map +1 -0
  233. package/dist/src/promotion/dedup.d.ts +19 -0
  234. package/dist/src/promotion/dedup.js +42 -0
  235. package/dist/src/promotion/dedup.js.map +1 -0
  236. package/dist/src/promotion/detector.d.ts +15 -0
  237. package/dist/src/promotion/detector.js +31 -0
  238. package/dist/src/promotion/detector.js.map +1 -0
  239. package/dist/src/prompts/condensed-d1.yaml +38 -0
  240. package/dist/src/prompts/condensed-d2.yaml +32 -0
  241. package/dist/src/prompts/condensed-d3plus.yaml +32 -0
  242. package/dist/src/prompts/leaf-aggressive.yaml +34 -0
  243. package/dist/src/prompts/leaf-normal.yaml +34 -0
  244. package/dist/src/prompts/loader.d.ts +9 -0
  245. package/dist/src/prompts/loader.js +37 -0
  246. package/dist/src/prompts/loader.js.map +1 -0
  247. package/dist/src/prompts/promoted-merge.yaml +18 -0
  248. package/dist/src/prompts/system.yaml +5 -0
  249. package/dist/src/retrieval.d.ts +122 -0
  250. package/dist/src/retrieval.js +214 -0
  251. package/dist/src/retrieval.js.map +1 -0
  252. package/dist/src/scrub.d.ts +46 -0
  253. package/dist/src/scrub.js +184 -0
  254. package/dist/src/scrub.js.map +1 -0
  255. package/dist/src/sensitive.d.ts +4 -0
  256. package/dist/src/sensitive.js +258 -0
  257. package/dist/src/sensitive.js.map +1 -0
  258. package/dist/src/stats.d.ts +34 -0
  259. package/dist/src/stats.js +260 -0
  260. package/dist/src/stats.js.map +1 -0
  261. package/dist/src/store/conversation-store.d.ts +115 -0
  262. package/dist/src/store/conversation-store.js +447 -0
  263. package/dist/src/store/conversation-store.js.map +1 -0
  264. package/dist/src/store/fts5-sanitize.d.ts +23 -0
  265. package/dist/src/store/fts5-sanitize.js +30 -0
  266. package/dist/src/store/fts5-sanitize.js.map +1 -0
  267. package/dist/src/store/full-text-fallback.d.ts +16 -0
  268. package/dist/src/store/full-text-fallback.js +60 -0
  269. package/dist/src/store/full-text-fallback.js.map +1 -0
  270. package/dist/src/store/index.d.ts +4 -0
  271. package/dist/src/store/index.js +3 -0
  272. package/dist/src/store/index.js.map +1 -0
  273. package/dist/src/store/summary-store.d.ts +118 -0
  274. package/dist/src/store/summary-store.js +570 -0
  275. package/dist/src/store/summary-store.js.map +1 -0
  276. package/dist/src/summarize.d.ts +59 -0
  277. package/dist/src/summarize.js +619 -0
  278. package/dist/src/summarize.js.map +1 -0
  279. package/dist/src/transcript.d.ts +7 -0
  280. package/dist/src/transcript.js +51 -0
  281. package/dist/src/transcript.js.map +1 -0
  282. package/dist/src/types.d.ts +116 -0
  283. package/dist/src/types.js +8 -0
  284. package/dist/src/types.js.map +1 -0
  285. package/docs/agent-tools.md +187 -0
  286. package/docs/architecture.md +224 -0
  287. package/docs/configuration.md +168 -0
  288. package/docs/fts5.md +161 -0
  289. package/docs/hook-protocol.md +41 -0
  290. package/docs/privacy.md +101 -0
  291. package/mcp.mjs +17 -0
  292. package/package.json +79 -0
@@ -0,0 +1,115 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ export type ConversationId = number;
3
+ export type MessageId = number;
4
+ export type SummaryId = string;
5
+ export type MessageRole = "system" | "user" | "assistant" | "tool";
6
+ export type MessagePartType = "text" | "reasoning" | "tool" | "patch" | "file" | "subtask" | "compaction" | "step_start" | "step_finish" | "snapshot" | "agent" | "retry";
7
+ export type CreateMessageInput = {
8
+ conversationId: ConversationId;
9
+ seq: number;
10
+ role: MessageRole;
11
+ content: string;
12
+ tokenCount: number;
13
+ };
14
+ export type MessageRecord = {
15
+ messageId: MessageId;
16
+ conversationId: ConversationId;
17
+ seq: number;
18
+ role: MessageRole;
19
+ content: string;
20
+ tokenCount: number;
21
+ createdAt: Date;
22
+ };
23
+ export type CreateMessagePartInput = {
24
+ sessionId: string;
25
+ partType: MessagePartType;
26
+ ordinal: number;
27
+ textContent?: string | null;
28
+ toolCallId?: string | null;
29
+ toolName?: string | null;
30
+ toolInput?: string | null;
31
+ toolOutput?: string | null;
32
+ metadata?: string | null;
33
+ };
34
+ export type MessagePartRecord = {
35
+ partId: string;
36
+ messageId: MessageId;
37
+ sessionId: string;
38
+ partType: MessagePartType;
39
+ ordinal: number;
40
+ textContent: string | null;
41
+ toolCallId: string | null;
42
+ toolName: string | null;
43
+ toolInput: string | null;
44
+ toolOutput: string | null;
45
+ metadata: string | null;
46
+ };
47
+ export type CreateConversationInput = {
48
+ sessionId: string;
49
+ title?: string;
50
+ };
51
+ export type ConversationRecord = {
52
+ conversationId: ConversationId;
53
+ sessionId: string;
54
+ title: string | null;
55
+ bootstrappedAt: Date | null;
56
+ createdAt: Date;
57
+ updatedAt: Date;
58
+ };
59
+ export type MessageSearchInput = {
60
+ conversationId?: ConversationId;
61
+ query: string;
62
+ mode: "regex" | "full_text";
63
+ since?: Date;
64
+ before?: Date;
65
+ limit?: number;
66
+ };
67
+ export type MessageSearchResult = {
68
+ messageId: MessageId;
69
+ conversationId: ConversationId;
70
+ role: MessageRole;
71
+ snippet: string;
72
+ createdAt: Date;
73
+ rank?: number;
74
+ };
75
+ export declare class ConversationStore {
76
+ private db;
77
+ private readonly fts5Available;
78
+ constructor(db: DatabaseSync, options?: {
79
+ fts5Available?: boolean;
80
+ });
81
+ withTransaction<T>(operation: () => Promise<T> | T): Promise<T>;
82
+ createConversation(input: CreateConversationInput): Promise<ConversationRecord>;
83
+ getConversation(conversationId: ConversationId): Promise<ConversationRecord | null>;
84
+ getConversationBySessionId(sessionId: string): Promise<ConversationRecord | null>;
85
+ getOrCreateConversation(sessionId: string, title?: string): Promise<ConversationRecord>;
86
+ markConversationBootstrapped(conversationId: ConversationId): Promise<void>;
87
+ listConversations(): Promise<ConversationRecord[]>;
88
+ createMessage(input: CreateMessageInput): Promise<MessageRecord>;
89
+ createMessagesBulk(inputs: CreateMessageInput[]): Promise<MessageRecord[]>;
90
+ getMessages(conversationId: ConversationId, opts?: {
91
+ afterSeq?: number;
92
+ limit?: number;
93
+ }): Promise<MessageRecord[]>;
94
+ getLastMessage(conversationId: ConversationId): Promise<MessageRecord | null>;
95
+ hasMessage(conversationId: ConversationId, role: MessageRole, content: string): Promise<boolean>;
96
+ countMessagesByIdentity(conversationId: ConversationId, role: MessageRole, content: string): Promise<number>;
97
+ getMessageById(messageId: MessageId): Promise<MessageRecord | null>;
98
+ createMessageParts(messageId: MessageId, parts: CreateMessagePartInput[]): Promise<void>;
99
+ getMessageParts(messageId: MessageId): Promise<MessagePartRecord[]>;
100
+ getMessageCount(conversationId: ConversationId): Promise<number>;
101
+ getMaxSeq(conversationId: ConversationId): Promise<number>;
102
+ /**
103
+ * Delete messages and their associated records (context_items, FTS, message_parts).
104
+ *
105
+ * Skips messages referenced in summary_messages (already compacted) to avoid
106
+ * breaking the summary DAG. Returns the count of actually deleted messages.
107
+ */
108
+ deleteMessages(messageIds: MessageId[]): Promise<number>;
109
+ searchMessages(input: MessageSearchInput): Promise<MessageSearchResult[]>;
110
+ private indexMessageForFullText;
111
+ private deleteMessageFromFullText;
112
+ private searchFullText;
113
+ private searchLike;
114
+ private searchRegex;
115
+ }
@@ -0,0 +1,447 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { sanitizeFts5Query } from "./fts5-sanitize.js";
3
+ import { buildLikeSearchPlan, createFallbackSnippet } from "./full-text-fallback.js";
4
+ // ── Row mappers ───────────────────────────────────────────────────────────────
5
+ function toConversationRecord(row) {
6
+ return {
7
+ conversationId: row.conversation_id,
8
+ sessionId: row.session_id,
9
+ title: row.title,
10
+ bootstrappedAt: row.bootstrapped_at ? new Date(row.bootstrapped_at) : null,
11
+ createdAt: new Date(row.created_at),
12
+ updatedAt: new Date(row.updated_at),
13
+ };
14
+ }
15
+ function toMessageRecord(row) {
16
+ return {
17
+ messageId: row.message_id,
18
+ conversationId: row.conversation_id,
19
+ seq: row.seq,
20
+ role: row.role,
21
+ content: row.content,
22
+ tokenCount: row.token_count,
23
+ createdAt: new Date(row.created_at),
24
+ };
25
+ }
26
+ function toSearchResult(row) {
27
+ return {
28
+ messageId: row.message_id,
29
+ conversationId: row.conversation_id,
30
+ role: row.role,
31
+ snippet: row.snippet,
32
+ createdAt: new Date(row.created_at),
33
+ rank: row.rank,
34
+ };
35
+ }
36
+ function toMessagePartRecord(row) {
37
+ return {
38
+ partId: row.part_id,
39
+ messageId: row.message_id,
40
+ sessionId: row.session_id,
41
+ partType: row.part_type,
42
+ ordinal: row.ordinal,
43
+ textContent: row.text_content,
44
+ toolCallId: row.tool_call_id,
45
+ toolName: row.tool_name,
46
+ toolInput: row.tool_input,
47
+ toolOutput: row.tool_output,
48
+ metadata: row.metadata,
49
+ };
50
+ }
51
+ // ── ConversationStore ─────────────────────────────────────────────────────────
52
+ export class ConversationStore {
53
+ db;
54
+ fts5Available;
55
+ constructor(db, options) {
56
+ this.db = db;
57
+ this.fts5Available = options?.fts5Available ?? true;
58
+ }
59
+ // ── Transaction helpers ──────────────────────────────────────────────────
60
+ async withTransaction(operation) {
61
+ this.db.exec("BEGIN IMMEDIATE");
62
+ try {
63
+ const result = await operation();
64
+ this.db.exec("COMMIT");
65
+ return result;
66
+ }
67
+ catch (error) {
68
+ this.db.exec("ROLLBACK");
69
+ throw error;
70
+ }
71
+ }
72
+ // ── Conversation operations ───────────────────────────────────────────────
73
+ async createConversation(input) {
74
+ const result = this.db
75
+ .prepare(`INSERT INTO conversations (session_id, title) VALUES (?, ?)`)
76
+ .run(input.sessionId, input.title ?? null);
77
+ const row = this.db
78
+ .prepare(`SELECT conversation_id, session_id, title, bootstrapped_at, created_at, updated_at
79
+ FROM conversations WHERE conversation_id = ?`)
80
+ .get(Number(result.lastInsertRowid));
81
+ return toConversationRecord(row);
82
+ }
83
+ async getConversation(conversationId) {
84
+ const row = this.db
85
+ .prepare(`SELECT conversation_id, session_id, title, bootstrapped_at, created_at, updated_at
86
+ FROM conversations WHERE conversation_id = ?`)
87
+ .get(conversationId);
88
+ return row ? toConversationRecord(row) : null;
89
+ }
90
+ async getConversationBySessionId(sessionId) {
91
+ const row = this.db
92
+ .prepare(`SELECT conversation_id, session_id, title, bootstrapped_at, created_at, updated_at
93
+ FROM conversations
94
+ WHERE session_id = ?
95
+ ORDER BY created_at DESC
96
+ LIMIT 1`)
97
+ .get(sessionId);
98
+ return row ? toConversationRecord(row) : null;
99
+ }
100
+ async getOrCreateConversation(sessionId, title) {
101
+ const existing = await this.getConversationBySessionId(sessionId);
102
+ if (existing) {
103
+ return existing;
104
+ }
105
+ return this.createConversation({ sessionId, title });
106
+ }
107
+ async markConversationBootstrapped(conversationId) {
108
+ this.db
109
+ .prepare(`UPDATE conversations
110
+ SET bootstrapped_at = COALESCE(bootstrapped_at, datetime('now')),
111
+ updated_at = datetime('now')
112
+ WHERE conversation_id = ?`)
113
+ .run(conversationId);
114
+ }
115
+ async listConversations() {
116
+ const rows = this.db
117
+ .prepare(`SELECT conversation_id, session_id, title, bootstrapped_at, created_at, updated_at
118
+ FROM conversations
119
+ ORDER BY created_at`)
120
+ .all();
121
+ return rows.map(toConversationRecord);
122
+ }
123
+ // ── Message operations ────────────────────────────────────────────────────
124
+ async createMessage(input) {
125
+ const result = this.db
126
+ .prepare(`INSERT INTO messages (conversation_id, seq, role, content, token_count)
127
+ VALUES (?, ?, ?, ?, ?)`)
128
+ .run(input.conversationId, input.seq, input.role, input.content, input.tokenCount);
129
+ const messageId = Number(result.lastInsertRowid);
130
+ this.indexMessageForFullText(messageId, input.content);
131
+ const row = this.db
132
+ .prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
133
+ FROM messages WHERE message_id = ?`)
134
+ .get(messageId);
135
+ return toMessageRecord(row);
136
+ }
137
+ async createMessagesBulk(inputs) {
138
+ if (inputs.length === 0) {
139
+ return [];
140
+ }
141
+ const insertStmt = this.db.prepare(`INSERT INTO messages (conversation_id, seq, role, content, token_count)
142
+ VALUES (?, ?, ?, ?, ?)`);
143
+ const selectStmt = this.db.prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
144
+ FROM messages WHERE message_id = ?`);
145
+ const records = [];
146
+ for (const input of inputs) {
147
+ const result = insertStmt.run(input.conversationId, input.seq, input.role, input.content, input.tokenCount);
148
+ const messageId = Number(result.lastInsertRowid);
149
+ this.indexMessageForFullText(messageId, input.content);
150
+ const row = selectStmt.get(messageId);
151
+ records.push(toMessageRecord(row));
152
+ }
153
+ return records;
154
+ }
155
+ async getMessages(conversationId, opts) {
156
+ const afterSeq = opts?.afterSeq ?? -1;
157
+ const limit = opts?.limit;
158
+ if (limit != null) {
159
+ const rows = this.db
160
+ .prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
161
+ FROM messages
162
+ WHERE conversation_id = ? AND seq > ?
163
+ ORDER BY seq
164
+ LIMIT ?`)
165
+ .all(conversationId, afterSeq, limit);
166
+ return rows.map(toMessageRecord);
167
+ }
168
+ const rows = this.db
169
+ .prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
170
+ FROM messages
171
+ WHERE conversation_id = ? AND seq > ?
172
+ ORDER BY seq`)
173
+ .all(conversationId, afterSeq);
174
+ return rows.map(toMessageRecord);
175
+ }
176
+ async getLastMessage(conversationId) {
177
+ const row = this.db
178
+ .prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
179
+ FROM messages
180
+ WHERE conversation_id = ?
181
+ ORDER BY seq DESC
182
+ LIMIT 1`)
183
+ .get(conversationId);
184
+ return row ? toMessageRecord(row) : null;
185
+ }
186
+ async hasMessage(conversationId, role, content) {
187
+ const row = this.db
188
+ .prepare(`SELECT 1 AS count
189
+ FROM messages
190
+ WHERE conversation_id = ? AND role = ? AND content = ?
191
+ LIMIT 1`)
192
+ .get(conversationId, role, content);
193
+ return row?.count === 1;
194
+ }
195
+ async countMessagesByIdentity(conversationId, role, content) {
196
+ const row = this.db
197
+ .prepare(`SELECT COUNT(*) AS count
198
+ FROM messages
199
+ WHERE conversation_id = ? AND role = ? AND content = ?`)
200
+ .get(conversationId, role, content);
201
+ return row?.count ?? 0;
202
+ }
203
+ async getMessageById(messageId) {
204
+ const row = this.db
205
+ .prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
206
+ FROM messages WHERE message_id = ?`)
207
+ .get(messageId);
208
+ return row ? toMessageRecord(row) : null;
209
+ }
210
+ async createMessageParts(messageId, parts) {
211
+ if (parts.length === 0) {
212
+ return;
213
+ }
214
+ const stmt = this.db.prepare(`INSERT INTO message_parts (
215
+ part_id,
216
+ message_id,
217
+ session_id,
218
+ part_type,
219
+ ordinal,
220
+ text_content,
221
+ tool_call_id,
222
+ tool_name,
223
+ tool_input,
224
+ tool_output,
225
+ metadata
226
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
227
+ for (const part of parts) {
228
+ stmt.run(randomUUID(), messageId, part.sessionId, part.partType, part.ordinal, part.textContent ?? null, part.toolCallId ?? null, part.toolName ?? null, part.toolInput ?? null, part.toolOutput ?? null, part.metadata ?? null);
229
+ }
230
+ }
231
+ async getMessageParts(messageId) {
232
+ const rows = this.db
233
+ .prepare(`SELECT
234
+ part_id,
235
+ message_id,
236
+ session_id,
237
+ part_type,
238
+ ordinal,
239
+ text_content,
240
+ tool_call_id,
241
+ tool_name,
242
+ tool_input,
243
+ tool_output,
244
+ metadata
245
+ FROM message_parts
246
+ WHERE message_id = ?
247
+ ORDER BY ordinal`)
248
+ .all(messageId);
249
+ return rows.map(toMessagePartRecord);
250
+ }
251
+ async getMessageCount(conversationId) {
252
+ const row = this.db
253
+ .prepare(`SELECT COUNT(*) AS count FROM messages WHERE conversation_id = ?`)
254
+ .get(conversationId);
255
+ return row?.count ?? 0;
256
+ }
257
+ async getMaxSeq(conversationId) {
258
+ const row = this.db
259
+ .prepare(`SELECT COALESCE(MAX(seq), 0) AS max_seq
260
+ FROM messages WHERE conversation_id = ?`)
261
+ .get(conversationId);
262
+ return row?.max_seq ?? 0;
263
+ }
264
+ // ── Deletion ──────────────────────────────────────────────────────────────
265
+ /**
266
+ * Delete messages and their associated records (context_items, FTS, message_parts).
267
+ *
268
+ * Skips messages referenced in summary_messages (already compacted) to avoid
269
+ * breaking the summary DAG. Returns the count of actually deleted messages.
270
+ */
271
+ async deleteMessages(messageIds) {
272
+ if (messageIds.length === 0) {
273
+ return 0;
274
+ }
275
+ let deleted = 0;
276
+ for (const messageId of messageIds) {
277
+ // Skip if referenced by a summary (ON DELETE RESTRICT would fail anyway)
278
+ const refRow = this.db
279
+ .prepare(`SELECT 1 AS found FROM summary_messages WHERE message_id = ? LIMIT 1`)
280
+ .get(messageId);
281
+ if (refRow) {
282
+ continue;
283
+ }
284
+ // Remove from context_items first (RESTRICT constraint)
285
+ this.db
286
+ .prepare(`DELETE FROM context_items WHERE item_type = 'message' AND message_id = ?`)
287
+ .run(messageId);
288
+ this.deleteMessageFromFullText(messageId);
289
+ // Delete the message (message_parts cascade via ON DELETE CASCADE)
290
+ this.db.prepare(`DELETE FROM messages WHERE message_id = ?`).run(messageId);
291
+ deleted += 1;
292
+ }
293
+ return deleted;
294
+ }
295
+ // ── Search ────────────────────────────────────────────────────────────────
296
+ async searchMessages(input) {
297
+ const limit = input.limit ?? 50;
298
+ if (input.mode === "full_text") {
299
+ if (this.fts5Available) {
300
+ try {
301
+ return this.searchFullText(input.query, limit, input.conversationId, input.since, input.before);
302
+ }
303
+ catch {
304
+ return this.searchLike(input.query, limit, input.conversationId, input.since, input.before);
305
+ }
306
+ }
307
+ return this.searchLike(input.query, limit, input.conversationId, input.since, input.before);
308
+ }
309
+ return this.searchRegex(input.query, limit, input.conversationId, input.since, input.before);
310
+ }
311
+ indexMessageForFullText(messageId, content) {
312
+ if (!this.fts5Available) {
313
+ return;
314
+ }
315
+ try {
316
+ this.db
317
+ .prepare(`INSERT INTO messages_fts(rowid, content) VALUES (?, ?)`)
318
+ .run(messageId, content);
319
+ }
320
+ catch {
321
+ // Full-text indexing is optional. Message persistence must still succeed.
322
+ }
323
+ }
324
+ deleteMessageFromFullText(messageId) {
325
+ if (!this.fts5Available) {
326
+ return;
327
+ }
328
+ try {
329
+ this.db.prepare(`DELETE FROM messages_fts WHERE rowid = ?`).run(messageId);
330
+ }
331
+ catch {
332
+ // Ignore FTS cleanup failures; the source row deletion is authoritative.
333
+ }
334
+ }
335
+ searchFullText(query, limit, conversationId, since, before) {
336
+ const where = ["messages_fts MATCH ?"];
337
+ const args = [sanitizeFts5Query(query)];
338
+ if (conversationId != null) {
339
+ where.push("m.conversation_id = ?");
340
+ args.push(conversationId);
341
+ }
342
+ if (since) {
343
+ where.push("julianday(m.created_at) >= julianday(?)");
344
+ args.push(since.toISOString());
345
+ }
346
+ if (before) {
347
+ where.push("julianday(m.created_at) < julianday(?)");
348
+ args.push(before.toISOString());
349
+ }
350
+ args.push(limit);
351
+ const sql = `SELECT
352
+ m.message_id,
353
+ m.conversation_id,
354
+ m.role,
355
+ snippet(messages_fts, 0, '', '', '...', 32) AS snippet,
356
+ rank,
357
+ m.created_at
358
+ FROM messages_fts
359
+ JOIN messages m ON m.message_id = messages_fts.rowid
360
+ WHERE ${where.join(" AND ")}
361
+ ORDER BY m.created_at DESC
362
+ LIMIT ?`;
363
+ const rows = this.db.prepare(sql).all(...args);
364
+ return rows.map(toSearchResult);
365
+ }
366
+ searchLike(query, limit, conversationId, since, before) {
367
+ const plan = buildLikeSearchPlan("content", query);
368
+ if (plan.terms.length === 0) {
369
+ return [];
370
+ }
371
+ const where = [...plan.where];
372
+ const args = [...plan.args];
373
+ if (conversationId != null) {
374
+ where.push("conversation_id = ?");
375
+ args.push(conversationId);
376
+ }
377
+ if (since) {
378
+ where.push("julianday(created_at) >= julianday(?)");
379
+ args.push(since.toISOString());
380
+ }
381
+ if (before) {
382
+ where.push("julianday(created_at) < julianday(?)");
383
+ args.push(before.toISOString());
384
+ }
385
+ args.push(limit);
386
+ const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
387
+ const rows = this.db
388
+ .prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
389
+ FROM messages
390
+ ${whereClause}
391
+ ORDER BY created_at DESC
392
+ LIMIT ?`)
393
+ .all(...args);
394
+ return rows.map((row) => ({
395
+ messageId: row.message_id,
396
+ conversationId: row.conversation_id,
397
+ role: row.role,
398
+ snippet: createFallbackSnippet(row.content, plan.terms),
399
+ createdAt: new Date(row.created_at),
400
+ rank: 0,
401
+ }));
402
+ }
403
+ searchRegex(pattern, limit, conversationId, since, before) {
404
+ // SQLite has no native POSIX regex; fetch candidates and filter in JS
405
+ const re = new RegExp(pattern);
406
+ const where = [];
407
+ const args = [];
408
+ if (conversationId != null) {
409
+ where.push("conversation_id = ?");
410
+ args.push(conversationId);
411
+ }
412
+ if (since) {
413
+ where.push("julianday(created_at) >= julianday(?)");
414
+ args.push(since.toISOString());
415
+ }
416
+ if (before) {
417
+ where.push("julianday(created_at) < julianday(?)");
418
+ args.push(before.toISOString());
419
+ }
420
+ const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
421
+ const rows = this.db
422
+ .prepare(`SELECT message_id, conversation_id, seq, role, content, token_count, created_at
423
+ FROM messages
424
+ ${whereClause}
425
+ ORDER BY created_at DESC`)
426
+ .all(...args);
427
+ const results = [];
428
+ for (const row of rows) {
429
+ if (results.length >= limit) {
430
+ break;
431
+ }
432
+ const match = re.exec(row.content);
433
+ if (match) {
434
+ results.push({
435
+ messageId: row.message_id,
436
+ conversationId: row.conversation_id,
437
+ role: row.role,
438
+ snippet: match[0],
439
+ createdAt: new Date(row.created_at),
440
+ rank: 0,
441
+ });
442
+ }
443
+ }
444
+ return results;
445
+ }
446
+ }
447
+ //# sourceMappingURL=conversation-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-store.js","sourceRoot":"","sources":["../../../src/store/conversation-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Sanitize a user-provided query for use in an FTS5 MATCH expression.
3
+ *
4
+ * FTS5 treats certain characters as operators:
5
+ * - `-` (NOT), `+` (required), `*` (prefix), `^` (initial token)
6
+ * - `OR`, `AND`, `NOT` (boolean operators)
7
+ * - `:` (column filter — e.g. `agent:foo` means "search column agent")
8
+ * - `"` (phrase query), `(` `)` (grouping)
9
+ * - `NEAR` (proximity)
10
+ *
11
+ * If the query contains any of these, naive MATCH will either error
12
+ * ("no such column") or return unexpected results.
13
+ *
14
+ * Strategy: wrap each whitespace-delimited token in double quotes so FTS5
15
+ * treats it as a literal phrase token. Internal double quotes are stripped.
16
+ * Empty tokens are dropped. Tokens are joined with spaces (implicit AND).
17
+ *
18
+ * Examples:
19
+ * "sub-agent restrict" → '"sub-agent" "restrict"'
20
+ * "lcm_expand OR crash" → '"lcm_expand" "OR" "crash"'
21
+ * 'hello "world"' → '"hello" "world"'
22
+ */
23
+ export declare function sanitizeFts5Query(raw: string): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Sanitize a user-provided query for use in an FTS5 MATCH expression.
3
+ *
4
+ * FTS5 treats certain characters as operators:
5
+ * - `-` (NOT), `+` (required), `*` (prefix), `^` (initial token)
6
+ * - `OR`, `AND`, `NOT` (boolean operators)
7
+ * - `:` (column filter — e.g. `agent:foo` means "search column agent")
8
+ * - `"` (phrase query), `(` `)` (grouping)
9
+ * - `NEAR` (proximity)
10
+ *
11
+ * If the query contains any of these, naive MATCH will either error
12
+ * ("no such column") or return unexpected results.
13
+ *
14
+ * Strategy: wrap each whitespace-delimited token in double quotes so FTS5
15
+ * treats it as a literal phrase token. Internal double quotes are stripped.
16
+ * Empty tokens are dropped. Tokens are joined with spaces (implicit AND).
17
+ *
18
+ * Examples:
19
+ * "sub-agent restrict" → '"sub-agent" "restrict"'
20
+ * "lcm_expand OR crash" → '"lcm_expand" "OR" "crash"'
21
+ * 'hello "world"' → '"hello" "world"'
22
+ */
23
+ export function sanitizeFts5Query(raw) {
24
+ const tokens = raw.split(/\s+/).filter(Boolean);
25
+ if (tokens.length === 0) {
26
+ return '""';
27
+ }
28
+ return tokens.map((t) => `"${t.replace(/"/g, "")}"`).join(" ");
29
+ }
30
+ //# sourceMappingURL=fts5-sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fts5-sanitize.js","sourceRoot":"","sources":["../../../src/store/fts5-sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,16 @@
1
+ export type LikeSearchPlan = {
2
+ terms: string[];
3
+ where: string[];
4
+ args: string[];
5
+ };
6
+ /**
7
+ * Convert a free-text query into a conservative LIKE search plan.
8
+ *
9
+ * The fallback keeps phrase tokens when the query uses double quotes, and
10
+ * otherwise searches for all normalized tokens as case-insensitive substrings.
11
+ */
12
+ export declare function buildLikeSearchPlan(column: string, query: string): LikeSearchPlan;
13
+ /**
14
+ * Build a compact snippet centered around the earliest matching term.
15
+ */
16
+ export declare function createFallbackSnippet(content: string, terms: string[]): string;