@loreai/core 0.17.1 → 0.18.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 (235) hide show
  1. package/dist/bun/agents-file.d.ts +4 -0
  2. package/dist/bun/agents-file.d.ts.map +1 -1
  3. package/dist/bun/config.d.ts +2 -0
  4. package/dist/bun/config.d.ts.map +1 -1
  5. package/dist/bun/curator.d.ts +45 -0
  6. package/dist/bun/curator.d.ts.map +1 -1
  7. package/dist/bun/data-dir.d.ts +18 -0
  8. package/dist/bun/data-dir.d.ts.map +1 -0
  9. package/dist/bun/db.d.ts +12 -0
  10. package/dist/bun/db.d.ts.map +1 -1
  11. package/dist/bun/distillation.d.ts.map +1 -1
  12. package/dist/bun/embedding-vendor.d.ts +22 -38
  13. package/dist/bun/embedding-vendor.d.ts.map +1 -1
  14. package/dist/bun/embedding-worker-types.d.ts +17 -12
  15. package/dist/bun/embedding-worker-types.d.ts.map +1 -1
  16. package/dist/bun/embedding-worker.d.ts +9 -2
  17. package/dist/bun/embedding-worker.d.ts.map +1 -1
  18. package/dist/bun/embedding-worker.js +38864 -33
  19. package/dist/bun/embedding-worker.js.map +4 -4
  20. package/dist/bun/embedding.d.ts +30 -22
  21. package/dist/bun/embedding.d.ts.map +1 -1
  22. package/dist/bun/gradient.d.ts +8 -1
  23. package/dist/bun/gradient.d.ts.map +1 -1
  24. package/dist/bun/import/detect.d.ts +14 -0
  25. package/dist/bun/import/detect.d.ts.map +1 -0
  26. package/dist/bun/import/extract.d.ts +43 -0
  27. package/dist/bun/import/extract.d.ts.map +1 -0
  28. package/dist/bun/import/history.d.ts +40 -0
  29. package/dist/bun/import/history.d.ts.map +1 -0
  30. package/dist/bun/import/index.d.ts +17 -0
  31. package/dist/bun/import/index.d.ts.map +1 -0
  32. package/dist/bun/import/providers/aider.d.ts +2 -0
  33. package/dist/bun/import/providers/aider.d.ts.map +1 -0
  34. package/dist/bun/import/providers/claude-code.d.ts +2 -0
  35. package/dist/bun/import/providers/claude-code.d.ts.map +1 -0
  36. package/dist/bun/import/providers/cline.d.ts +2 -0
  37. package/dist/bun/import/providers/cline.d.ts.map +1 -0
  38. package/dist/bun/import/providers/codex.d.ts +2 -0
  39. package/dist/bun/import/providers/codex.d.ts.map +1 -0
  40. package/dist/bun/import/providers/continue.d.ts +2 -0
  41. package/dist/bun/import/providers/continue.d.ts.map +1 -0
  42. package/dist/bun/import/providers/index.d.ts +19 -0
  43. package/dist/bun/import/providers/index.d.ts.map +1 -0
  44. package/dist/bun/import/providers/opencode.d.ts +2 -0
  45. package/dist/bun/import/providers/opencode.d.ts.map +1 -0
  46. package/dist/bun/import/providers/pi.d.ts +2 -0
  47. package/dist/bun/import/providers/pi.d.ts.map +1 -0
  48. package/dist/bun/import/types.d.ts +82 -0
  49. package/dist/bun/import/types.d.ts.map +1 -0
  50. package/dist/bun/index.d.ts +4 -1
  51. package/dist/bun/index.d.ts.map +1 -1
  52. package/dist/bun/index.js +2217 -224
  53. package/dist/bun/index.js.map +4 -4
  54. package/dist/bun/instruction-detect.d.ts +66 -0
  55. package/dist/bun/instruction-detect.d.ts.map +1 -0
  56. package/dist/bun/log.d.ts +9 -0
  57. package/dist/bun/log.d.ts.map +1 -1
  58. package/dist/bun/ltm.d.ts +40 -0
  59. package/dist/bun/ltm.d.ts.map +1 -1
  60. package/dist/bun/pattern-extract.d.ts +7 -0
  61. package/dist/bun/pattern-extract.d.ts.map +1 -1
  62. package/dist/bun/prompt.d.ts +1 -1
  63. package/dist/bun/prompt.d.ts.map +1 -1
  64. package/dist/bun/recall.d.ts.map +1 -1
  65. package/dist/bun/search.d.ts +5 -3
  66. package/dist/bun/search.d.ts.map +1 -1
  67. package/dist/bun/temporal.d.ts.map +1 -1
  68. package/dist/bun/types.d.ts +1 -1
  69. package/dist/node/agents-file.d.ts +4 -0
  70. package/dist/node/agents-file.d.ts.map +1 -1
  71. package/dist/node/config.d.ts +2 -0
  72. package/dist/node/config.d.ts.map +1 -1
  73. package/dist/node/curator.d.ts +45 -0
  74. package/dist/node/curator.d.ts.map +1 -1
  75. package/dist/node/data-dir.d.ts +18 -0
  76. package/dist/node/data-dir.d.ts.map +1 -0
  77. package/dist/node/db.d.ts +12 -0
  78. package/dist/node/db.d.ts.map +1 -1
  79. package/dist/node/distillation.d.ts.map +1 -1
  80. package/dist/node/embedding-vendor.d.ts +22 -38
  81. package/dist/node/embedding-vendor.d.ts.map +1 -1
  82. package/dist/node/embedding-worker-types.d.ts +17 -12
  83. package/dist/node/embedding-worker-types.d.ts.map +1 -1
  84. package/dist/node/embedding-worker.d.ts +9 -2
  85. package/dist/node/embedding-worker.d.ts.map +1 -1
  86. package/dist/node/embedding-worker.js +38864 -33
  87. package/dist/node/embedding-worker.js.map +4 -4
  88. package/dist/node/embedding.d.ts +30 -22
  89. package/dist/node/embedding.d.ts.map +1 -1
  90. package/dist/node/gradient.d.ts +8 -1
  91. package/dist/node/gradient.d.ts.map +1 -1
  92. package/dist/node/import/detect.d.ts +14 -0
  93. package/dist/node/import/detect.d.ts.map +1 -0
  94. package/dist/node/import/extract.d.ts +43 -0
  95. package/dist/node/import/extract.d.ts.map +1 -0
  96. package/dist/node/import/history.d.ts +40 -0
  97. package/dist/node/import/history.d.ts.map +1 -0
  98. package/dist/node/import/index.d.ts +17 -0
  99. package/dist/node/import/index.d.ts.map +1 -0
  100. package/dist/node/import/providers/aider.d.ts +2 -0
  101. package/dist/node/import/providers/aider.d.ts.map +1 -0
  102. package/dist/node/import/providers/claude-code.d.ts +2 -0
  103. package/dist/node/import/providers/claude-code.d.ts.map +1 -0
  104. package/dist/node/import/providers/cline.d.ts +2 -0
  105. package/dist/node/import/providers/cline.d.ts.map +1 -0
  106. package/dist/node/import/providers/codex.d.ts +2 -0
  107. package/dist/node/import/providers/codex.d.ts.map +1 -0
  108. package/dist/node/import/providers/continue.d.ts +2 -0
  109. package/dist/node/import/providers/continue.d.ts.map +1 -0
  110. package/dist/node/import/providers/index.d.ts +19 -0
  111. package/dist/node/import/providers/index.d.ts.map +1 -0
  112. package/dist/node/import/providers/opencode.d.ts +2 -0
  113. package/dist/node/import/providers/opencode.d.ts.map +1 -0
  114. package/dist/node/import/providers/pi.d.ts +2 -0
  115. package/dist/node/import/providers/pi.d.ts.map +1 -0
  116. package/dist/node/import/types.d.ts +82 -0
  117. package/dist/node/import/types.d.ts.map +1 -0
  118. package/dist/node/index.d.ts +4 -1
  119. package/dist/node/index.d.ts.map +1 -1
  120. package/dist/node/index.js +2217 -224
  121. package/dist/node/index.js.map +4 -4
  122. package/dist/node/instruction-detect.d.ts +66 -0
  123. package/dist/node/instruction-detect.d.ts.map +1 -0
  124. package/dist/node/log.d.ts +9 -0
  125. package/dist/node/log.d.ts.map +1 -1
  126. package/dist/node/ltm.d.ts +40 -0
  127. package/dist/node/ltm.d.ts.map +1 -1
  128. package/dist/node/pattern-extract.d.ts +7 -0
  129. package/dist/node/pattern-extract.d.ts.map +1 -1
  130. package/dist/node/prompt.d.ts +1 -1
  131. package/dist/node/prompt.d.ts.map +1 -1
  132. package/dist/node/recall.d.ts.map +1 -1
  133. package/dist/node/search.d.ts +5 -3
  134. package/dist/node/search.d.ts.map +1 -1
  135. package/dist/node/temporal.d.ts.map +1 -1
  136. package/dist/node/types.d.ts +1 -1
  137. package/dist/types/agents-file.d.ts +4 -0
  138. package/dist/types/agents-file.d.ts.map +1 -1
  139. package/dist/types/config.d.ts +2 -0
  140. package/dist/types/config.d.ts.map +1 -1
  141. package/dist/types/curator.d.ts +45 -0
  142. package/dist/types/curator.d.ts.map +1 -1
  143. package/dist/types/data-dir.d.ts +18 -0
  144. package/dist/types/data-dir.d.ts.map +1 -0
  145. package/dist/types/db.d.ts +12 -0
  146. package/dist/types/db.d.ts.map +1 -1
  147. package/dist/types/distillation.d.ts.map +1 -1
  148. package/dist/types/embedding-vendor.d.ts +22 -38
  149. package/dist/types/embedding-vendor.d.ts.map +1 -1
  150. package/dist/types/embedding-worker-types.d.ts +17 -12
  151. package/dist/types/embedding-worker-types.d.ts.map +1 -1
  152. package/dist/types/embedding-worker.d.ts +9 -2
  153. package/dist/types/embedding-worker.d.ts.map +1 -1
  154. package/dist/types/embedding.d.ts +30 -22
  155. package/dist/types/embedding.d.ts.map +1 -1
  156. package/dist/types/gradient.d.ts +8 -1
  157. package/dist/types/gradient.d.ts.map +1 -1
  158. package/dist/types/import/detect.d.ts +14 -0
  159. package/dist/types/import/detect.d.ts.map +1 -0
  160. package/dist/types/import/extract.d.ts +43 -0
  161. package/dist/types/import/extract.d.ts.map +1 -0
  162. package/dist/types/import/history.d.ts +40 -0
  163. package/dist/types/import/history.d.ts.map +1 -0
  164. package/dist/types/import/index.d.ts +17 -0
  165. package/dist/types/import/index.d.ts.map +1 -0
  166. package/dist/types/import/providers/aider.d.ts +2 -0
  167. package/dist/types/import/providers/aider.d.ts.map +1 -0
  168. package/dist/types/import/providers/claude-code.d.ts +2 -0
  169. package/dist/types/import/providers/claude-code.d.ts.map +1 -0
  170. package/dist/types/import/providers/cline.d.ts +2 -0
  171. package/dist/types/import/providers/cline.d.ts.map +1 -0
  172. package/dist/types/import/providers/codex.d.ts +2 -0
  173. package/dist/types/import/providers/codex.d.ts.map +1 -0
  174. package/dist/types/import/providers/continue.d.ts +2 -0
  175. package/dist/types/import/providers/continue.d.ts.map +1 -0
  176. package/dist/types/import/providers/index.d.ts +19 -0
  177. package/dist/types/import/providers/index.d.ts.map +1 -0
  178. package/dist/types/import/providers/opencode.d.ts +2 -0
  179. package/dist/types/import/providers/opencode.d.ts.map +1 -0
  180. package/dist/types/import/providers/pi.d.ts +2 -0
  181. package/dist/types/import/providers/pi.d.ts.map +1 -0
  182. package/dist/types/import/types.d.ts +82 -0
  183. package/dist/types/import/types.d.ts.map +1 -0
  184. package/dist/types/index.d.ts +4 -1
  185. package/dist/types/index.d.ts.map +1 -1
  186. package/dist/types/instruction-detect.d.ts +66 -0
  187. package/dist/types/instruction-detect.d.ts.map +1 -0
  188. package/dist/types/log.d.ts +9 -0
  189. package/dist/types/log.d.ts.map +1 -1
  190. package/dist/types/ltm.d.ts +40 -0
  191. package/dist/types/ltm.d.ts.map +1 -1
  192. package/dist/types/pattern-extract.d.ts +7 -0
  193. package/dist/types/pattern-extract.d.ts.map +1 -1
  194. package/dist/types/prompt.d.ts +1 -1
  195. package/dist/types/prompt.d.ts.map +1 -1
  196. package/dist/types/recall.d.ts.map +1 -1
  197. package/dist/types/search.d.ts +5 -3
  198. package/dist/types/search.d.ts.map +1 -1
  199. package/dist/types/temporal.d.ts.map +1 -1
  200. package/dist/types/types.d.ts +1 -1
  201. package/package.json +2 -4
  202. package/src/agents-file.ts +41 -13
  203. package/src/config.ts +31 -18
  204. package/src/curator.ts +111 -75
  205. package/src/data-dir.ts +76 -0
  206. package/src/db.ts +110 -11
  207. package/src/distillation.ts +10 -2
  208. package/src/embedding-vendor.ts +23 -40
  209. package/src/embedding-worker-types.ts +19 -11
  210. package/src/embedding-worker.ts +111 -47
  211. package/src/embedding.ts +196 -171
  212. package/src/gradient.ts +9 -1
  213. package/src/import/detect.ts +37 -0
  214. package/src/import/extract.ts +137 -0
  215. package/src/import/history.ts +99 -0
  216. package/src/import/index.ts +45 -0
  217. package/src/import/providers/aider.ts +207 -0
  218. package/src/import/providers/claude-code.ts +339 -0
  219. package/src/import/providers/cline.ts +324 -0
  220. package/src/import/providers/codex.ts +369 -0
  221. package/src/import/providers/continue.ts +304 -0
  222. package/src/import/providers/index.ts +32 -0
  223. package/src/import/providers/opencode.ts +272 -0
  224. package/src/import/providers/pi.ts +332 -0
  225. package/src/import/types.ts +91 -0
  226. package/src/index.ts +5 -0
  227. package/src/instruction-detect.ts +275 -0
  228. package/src/log.ts +91 -3
  229. package/src/ltm.ts +316 -3
  230. package/src/pattern-extract.ts +41 -0
  231. package/src/prompt.ts +7 -1
  232. package/src/recall.ts +43 -5
  233. package/src/search.ts +7 -5
  234. package/src/temporal.ts +8 -6
  235. package/src/types.ts +1 -1
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Knowledge extraction from imported conversations.
3
+ *
4
+ * Takes conversation chunks and feeds them to the curator LLM to extract
5
+ * knowledge entries directly, without going through the temporal → distill
6
+ * pipeline. This is cheaper and faster than full-pipeline import.
7
+ */
8
+ import * as ltm from "../ltm";
9
+ import { parseOps, applyOps } from "../curator";
10
+ import { CURATOR_SYSTEM, curatorUser } from "../prompt";
11
+ import type { LLMClient } from "../types";
12
+ import type { ConversationChunk } from "./types";
13
+
14
+ /**
15
+ * System prompt for import extraction.
16
+ * Extends the standard curator prompt with guidance for historical conversations.
17
+ */
18
+ const IMPORT_CURATOR_SYSTEM = `${CURATOR_SYSTEM}
19
+
20
+ ADDITIONAL CONTEXT: You are extracting knowledge from HISTORICAL conversations with a different AI coding agent. Focus on durable insights that are still relevant:
21
+ - Architecture decisions, design patterns, and project conventions
22
+ - Gotchas, non-obvious bugs, and their fixes
23
+ - Developer preferences and workflow patterns
24
+ - Key technical choices and their rationale
25
+
26
+ Ignore:
27
+ - References to the other agent's specific capabilities or limitations
28
+ - Task-specific state that is no longer current (e.g. "currently debugging X")
29
+ - Debugging steps for issues that were already resolved
30
+ - Transient conversation artifacts (greetings, acknowledgments, status updates)`;
31
+
32
+ export type ExtractionProgress = {
33
+ /** Current chunk being processed (1-based) */
34
+ current: number;
35
+ /** Total chunks to process */
36
+ total: number;
37
+ /** Knowledge entries created so far */
38
+ created: number;
39
+ /** Knowledge entries updated (dedup hit) so far */
40
+ updated: number;
41
+ };
42
+
43
+ export type ExtractionResult = {
44
+ /** Total knowledge entries created */
45
+ created: number;
46
+ /** Total entries that hit dedup (updated existing) */
47
+ updated: number;
48
+ /** Total entries deleted */
49
+ deleted: number;
50
+ /** Chunks processed successfully */
51
+ chunksProcessed: number;
52
+ /** Chunks that failed (LLM error) */
53
+ chunksFailed: number;
54
+ };
55
+
56
+ /**
57
+ * Extract knowledge entries from conversation chunks via the curator LLM.
58
+ *
59
+ * Processes chunks sequentially (not parallel) to avoid rate limits
60
+ * and to let later chunks see entries created by earlier chunks
61
+ * (better dedup via the existing entries list in the prompt).
62
+ */
63
+ export async function extractKnowledge(input: {
64
+ llm: LLMClient;
65
+ projectPath: string;
66
+ chunks: ConversationChunk[];
67
+ sessionID?: string;
68
+ model?: { providerID: string; modelID: string };
69
+ onProgress?: (progress: ExtractionProgress) => void;
70
+ }): Promise<ExtractionResult> {
71
+ const result: ExtractionResult = {
72
+ created: 0,
73
+ updated: 0,
74
+ deleted: 0,
75
+ chunksProcessed: 0,
76
+ chunksFailed: 0,
77
+ };
78
+
79
+ // Sort chunks chronologically so knowledge builds up naturally
80
+ const sorted = [...input.chunks].sort((a, b) => a.timestamp - b.timestamp);
81
+
82
+ for (let i = 0; i < sorted.length; i++) {
83
+ const chunk = sorted[i];
84
+
85
+ // Get existing entries (refreshed each iteration for dedup)
86
+ const existing = ltm.forProject(input.projectPath, false);
87
+ const existingForPrompt = existing.map((e) => ({
88
+ id: e.id,
89
+ category: e.category,
90
+ title: e.title,
91
+ content: e.content,
92
+ }));
93
+
94
+ const userContent = curatorUser({
95
+ messages: chunk.text,
96
+ existing: existingForPrompt,
97
+ });
98
+
99
+ try {
100
+ const response = await input.llm.prompt(
101
+ IMPORT_CURATOR_SYSTEM,
102
+ userContent,
103
+ {
104
+ model: input.model,
105
+ workerID: "lore-import",
106
+ thinking: false,
107
+ maxTokens: 4096,
108
+ sessionID: input.sessionID,
109
+ },
110
+ );
111
+
112
+ if (response) {
113
+ const ops = parseOps(response);
114
+ const applied = applyOps(ops, {
115
+ projectPath: input.projectPath,
116
+ sessionID: input.sessionID,
117
+ });
118
+ result.created += applied.created;
119
+ result.updated += applied.updated;
120
+ result.deleted += applied.deleted;
121
+ }
122
+
123
+ result.chunksProcessed++;
124
+ } catch {
125
+ result.chunksFailed++;
126
+ }
127
+
128
+ input.onProgress?.({
129
+ current: i + 1,
130
+ total: sorted.length,
131
+ created: result.created,
132
+ updated: result.updated,
133
+ });
134
+ }
135
+
136
+ return result;
137
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Import history — tracks which external agent sessions have been imported
3
+ * to prevent re-importing unchanged sources.
4
+ */
5
+ import { db, ensureProject } from "../db";
6
+
7
+ export type ImportRecord = {
8
+ id: string;
9
+ project_id: string;
10
+ agent_name: string;
11
+ source_id: string;
12
+ source_hash: string;
13
+ entries_created: number;
14
+ entries_updated: number;
15
+ imported_at: number;
16
+ };
17
+
18
+ /**
19
+ * Check if a specific source has already been imported with the same hash.
20
+ *
21
+ * @returns The existing record if found with the same hash, or null if
22
+ * the source hasn't been imported or the hash has changed.
23
+ */
24
+ export function isImported(
25
+ projectPath: string,
26
+ agentName: string,
27
+ sourceId: string,
28
+ sourceHash: string,
29
+ ): ImportRecord | null {
30
+ const projectId = ensureProject(projectPath);
31
+ const row = db()
32
+ .query(
33
+ `SELECT * FROM import_history
34
+ WHERE project_id = ? AND agent_name = ? AND source_id = ?`,
35
+ )
36
+ .get(projectId, agentName, sourceId) as ImportRecord | null;
37
+
38
+ if (!row) return null;
39
+ // Hash changed — source has new content since last import
40
+ if (row.source_hash !== sourceHash) return null;
41
+ return row;
42
+ }
43
+
44
+ /**
45
+ * Record a successful import of a source.
46
+ * Uses INSERT OR REPLACE to handle re-imports of changed sources.
47
+ */
48
+ export function recordImport(
49
+ projectPath: string,
50
+ agentName: string,
51
+ sourceId: string,
52
+ sourceHash: string,
53
+ stats: { created: number; updated: number },
54
+ ): void {
55
+ const projectId = ensureProject(projectPath);
56
+ db()
57
+ .query(
58
+ `INSERT OR REPLACE INTO import_history
59
+ (id, project_id, agent_name, source_id, source_hash, entries_created, entries_updated, imported_at)
60
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
61
+ )
62
+ .run(
63
+ crypto.randomUUID(),
64
+ projectId,
65
+ agentName,
66
+ sourceId,
67
+ sourceHash,
68
+ stats.created,
69
+ stats.updated,
70
+ Date.now(),
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Get all import records for a project.
76
+ * Excludes legacy "__declined__" sentinel rows from pre-v22 databases.
77
+ */
78
+ export function listImports(projectPath: string): ImportRecord[] {
79
+ const projectId = ensureProject(projectPath);
80
+ return db()
81
+ .query(
82
+ `SELECT * FROM import_history
83
+ WHERE project_id = ? AND source_id != '__declined__'
84
+ ORDER BY imported_at DESC`,
85
+ )
86
+ .all(projectId) as ImportRecord[];
87
+ }
88
+
89
+ /**
90
+ * Compute a simple hash string for idempotency checks.
91
+ * Uses a fast non-cryptographic approach: file size + message count + last timestamp.
92
+ */
93
+ export function computeHash(parts: {
94
+ size?: number;
95
+ messageCount?: number;
96
+ lastTimestamp?: number;
97
+ }): string {
98
+ return `${parts.size ?? 0}:${parts.messageCount ?? 0}:${parts.lastTimestamp ?? 0}`;
99
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Conversation import system — detects and imports knowledge from
3
+ * external AI coding agent conversation histories.
4
+ */
5
+
6
+ // Types
7
+ export type {
8
+ ConversationChunk,
9
+ DetectedSession,
10
+ DetectionResult,
11
+ AgentHistoryProvider,
12
+ } from "./types";
13
+
14
+ // Detection
15
+ export { detectAll } from "./detect";
16
+
17
+ // Provider registry
18
+ export {
19
+ registerProvider,
20
+ getProviders,
21
+ getProvider,
22
+ clearProviders,
23
+ } from "./providers";
24
+
25
+ // Extraction (lazy — avoid pulling in LLM/curator deps for detection-only use)
26
+ export { extractKnowledge, type ExtractionProgress, type ExtractionResult } from "./extract";
27
+
28
+ // Idempotency
29
+ export {
30
+ isImported,
31
+ recordImport,
32
+ computeHash,
33
+ listImports,
34
+ type ImportRecord,
35
+ } from "./history";
36
+
37
+ // Register built-in providers on first import.
38
+ // Each provider module calls registerProvider() at load time.
39
+ import "./providers/claude-code";
40
+ import "./providers/codex";
41
+ import "./providers/opencode";
42
+ import "./providers/cline";
43
+ import "./providers/continue";
44
+ import "./providers/pi";
45
+ import "./providers/aider";
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Aider conversation history provider.
3
+ *
4
+ * Reads from Aider's per-project chat history file:
5
+ * <project-dir>/.aider.chat.history.md
6
+ *
7
+ * Format: Markdown with role headers like "#### user" / "#### assistant"
8
+ * separated by horizontal rules (---).
9
+ */
10
+ import { existsSync, readFileSync, statSync } from "fs";
11
+ import { join } from "path";
12
+ import type { AgentHistoryProvider, ConversationChunk, DetectedSession } from "../types";
13
+ import { registerProvider } from "./index";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+
19
+ const HISTORY_FILE = ".aider.chat.history.md";
20
+ const DEFAULT_MAX_TOKENS = 12288;
21
+
22
+ // Aider uses "#### role" headers and "---" separators
23
+ const ROLE_HEADER_RE = /^####\s+(user|assistant|system)\s*$/i;
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Helpers
27
+ // ---------------------------------------------------------------------------
28
+
29
+ function estimateTokens(text: string): number {
30
+ return Math.ceil(text.length / 3);
31
+ }
32
+
33
+ type ParsedMessage = {
34
+ role: string;
35
+ text: string;
36
+ };
37
+
38
+ /**
39
+ * Parse Aider's markdown chat history into messages.
40
+ *
41
+ * Format:
42
+ * ```
43
+ * #### user
44
+ * message text here
45
+ *
46
+ * #### assistant
47
+ * response text here
48
+ * ```
49
+ *
50
+ * Messages are separated by `---` or by new `#### role` headers.
51
+ */
52
+ function parseAiderHistory(content: string): ParsedMessage[] {
53
+ const lines = content.split("\n");
54
+ const messages: ParsedMessage[] = [];
55
+ let currentRole: string | null = null;
56
+ let currentLines: string[] = [];
57
+
58
+ const flush = () => {
59
+ if (currentRole && currentLines.length > 0) {
60
+ const text = currentLines.join("\n").trim();
61
+ if (text) {
62
+ messages.push({ role: currentRole, text });
63
+ }
64
+ }
65
+ currentLines = [];
66
+ };
67
+
68
+ for (const line of lines) {
69
+ // Check for role header
70
+ const match = ROLE_HEADER_RE.exec(line);
71
+ if (match) {
72
+ flush();
73
+ currentRole = match[1].toLowerCase();
74
+ continue;
75
+ }
76
+
77
+ // Check for separator — starts a new conversation turn
78
+ if (line.trim() === "---") {
79
+ flush();
80
+ currentRole = null;
81
+ continue;
82
+ }
83
+
84
+ // Accumulate content if we're in a message
85
+ if (currentRole) {
86
+ currentLines.push(line);
87
+ }
88
+ }
89
+
90
+ // Flush final message
91
+ flush();
92
+
93
+ return messages;
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Provider implementation
98
+ // ---------------------------------------------------------------------------
99
+
100
+ const aiderProvider: AgentHistoryProvider = {
101
+ name: "aider",
102
+ displayName: "Aider",
103
+
104
+ detect(projectPath: string): DetectedSession[] {
105
+ const filePath = join(projectPath, HISTORY_FILE);
106
+ if (!existsSync(filePath)) return [];
107
+
108
+ let stat;
109
+ try {
110
+ stat = statSync(filePath);
111
+ } catch {
112
+ return [];
113
+ }
114
+
115
+ if (!stat.isFile() || stat.size === 0) return [];
116
+
117
+ // Quick scan to count messages without full parsing
118
+ let content: string;
119
+ try {
120
+ content = readFileSync(filePath, "utf-8");
121
+ } catch {
122
+ return [];
123
+ }
124
+
125
+ const messages = parseAiderHistory(content);
126
+ if (messages.length < 3) return [];
127
+
128
+ const estimatedTokens = estimateTokens(content);
129
+
130
+ return [
131
+ {
132
+ id: filePath,
133
+ label: `Chat history (${messages.length} messages, ${Math.round(stat.size / 1024)}KB)`,
134
+ startedAt: stat.birthtimeMs || stat.ctimeMs,
135
+ lastActivityAt: stat.mtimeMs,
136
+ estimatedTokens,
137
+ messageCount: messages.length,
138
+ },
139
+ ];
140
+ },
141
+
142
+ readChunks(
143
+ projectPath: string,
144
+ sessionIds: string[],
145
+ maxTokens: number = DEFAULT_MAX_TOKENS,
146
+ ): ConversationChunk[] {
147
+ const chunks: ConversationChunk[] = [];
148
+
149
+ for (const filePath of sessionIds) {
150
+ let content: string;
151
+ try {
152
+ content = readFileSync(filePath, "utf-8");
153
+ } catch {
154
+ continue;
155
+ }
156
+
157
+ const messages = parseAiderHistory(content);
158
+ if (messages.length === 0) continue;
159
+
160
+ // Get file mtime for timestamp
161
+ let fileTimestamp: number;
162
+ try {
163
+ fileTimestamp = statSync(filePath).mtimeMs;
164
+ } catch {
165
+ fileTimestamp = Date.now();
166
+ }
167
+
168
+ // Build chunks respecting maxTokens boundaries
169
+ let currentTexts: string[] = [];
170
+ let currentTokens = 0;
171
+ let chunkIndex = 0;
172
+
173
+ const flushChunk = () => {
174
+ if (currentTexts.length === 0) return;
175
+ chunkIndex++;
176
+ const text = currentTexts.join("\n\n");
177
+ chunks.push({
178
+ label: `Aider history (${chunkIndex})`,
179
+ text,
180
+ estimatedTokens: estimateTokens(text),
181
+ timestamp: fileTimestamp,
182
+ });
183
+ currentTexts = [];
184
+ currentTokens = 0;
185
+ };
186
+
187
+ for (const msg of messages) {
188
+ const formatted = `[${msg.role}] ${msg.text}`;
189
+ const msgTokens = estimateTokens(formatted);
190
+
191
+ if (currentTokens > 0 && currentTokens + msgTokens > maxTokens) {
192
+ flushChunk();
193
+ }
194
+
195
+ currentTexts.push(formatted);
196
+ currentTokens += msgTokens;
197
+ }
198
+
199
+ flushChunk();
200
+ }
201
+
202
+ return chunks;
203
+ },
204
+ };
205
+
206
+ // Auto-register on import
207
+ registerProvider(aiderProvider);