@loreai/core 0.17.0 → 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.
- package/dist/bun/agents-file.d.ts +4 -0
- package/dist/bun/agents-file.d.ts.map +1 -1
- package/dist/bun/config.d.ts +2 -0
- package/dist/bun/config.d.ts.map +1 -1
- package/dist/bun/curator.d.ts +45 -0
- package/dist/bun/curator.d.ts.map +1 -1
- package/dist/bun/data-dir.d.ts +18 -0
- package/dist/bun/data-dir.d.ts.map +1 -0
- package/dist/bun/db.d.ts +12 -0
- package/dist/bun/db.d.ts.map +1 -1
- package/dist/bun/distillation.d.ts.map +1 -1
- package/dist/bun/embedding-vendor.d.ts +22 -38
- package/dist/bun/embedding-vendor.d.ts.map +1 -1
- package/dist/bun/embedding-worker-types.d.ts +17 -12
- package/dist/bun/embedding-worker-types.d.ts.map +1 -1
- package/dist/bun/embedding-worker.d.ts +9 -2
- package/dist/bun/embedding-worker.d.ts.map +1 -1
- package/dist/bun/embedding-worker.js +38864 -33
- package/dist/bun/embedding-worker.js.map +4 -4
- package/dist/bun/embedding.d.ts +30 -22
- package/dist/bun/embedding.d.ts.map +1 -1
- package/dist/bun/gradient.d.ts +8 -1
- package/dist/bun/gradient.d.ts.map +1 -1
- package/dist/bun/import/detect.d.ts +14 -0
- package/dist/bun/import/detect.d.ts.map +1 -0
- package/dist/bun/import/extract.d.ts +43 -0
- package/dist/bun/import/extract.d.ts.map +1 -0
- package/dist/bun/import/history.d.ts +40 -0
- package/dist/bun/import/history.d.ts.map +1 -0
- package/dist/bun/import/index.d.ts +17 -0
- package/dist/bun/import/index.d.ts.map +1 -0
- package/dist/bun/import/providers/aider.d.ts +2 -0
- package/dist/bun/import/providers/aider.d.ts.map +1 -0
- package/dist/bun/import/providers/claude-code.d.ts +2 -0
- package/dist/bun/import/providers/claude-code.d.ts.map +1 -0
- package/dist/bun/import/providers/cline.d.ts +2 -0
- package/dist/bun/import/providers/cline.d.ts.map +1 -0
- package/dist/bun/import/providers/codex.d.ts +2 -0
- package/dist/bun/import/providers/codex.d.ts.map +1 -0
- package/dist/bun/import/providers/continue.d.ts +2 -0
- package/dist/bun/import/providers/continue.d.ts.map +1 -0
- package/dist/bun/import/providers/index.d.ts +19 -0
- package/dist/bun/import/providers/index.d.ts.map +1 -0
- package/dist/bun/import/providers/opencode.d.ts +2 -0
- package/dist/bun/import/providers/opencode.d.ts.map +1 -0
- package/dist/bun/import/providers/pi.d.ts +2 -0
- package/dist/bun/import/providers/pi.d.ts.map +1 -0
- package/dist/bun/import/types.d.ts +82 -0
- package/dist/bun/import/types.d.ts.map +1 -0
- package/dist/bun/index.d.ts +4 -1
- package/dist/bun/index.d.ts.map +1 -1
- package/dist/bun/index.js +2217 -224
- package/dist/bun/index.js.map +4 -4
- package/dist/bun/instruction-detect.d.ts +66 -0
- package/dist/bun/instruction-detect.d.ts.map +1 -0
- package/dist/bun/log.d.ts +9 -0
- package/dist/bun/log.d.ts.map +1 -1
- package/dist/bun/ltm.d.ts +40 -0
- package/dist/bun/ltm.d.ts.map +1 -1
- package/dist/bun/pattern-extract.d.ts +7 -0
- package/dist/bun/pattern-extract.d.ts.map +1 -1
- package/dist/bun/prompt.d.ts +1 -1
- package/dist/bun/prompt.d.ts.map +1 -1
- package/dist/bun/recall.d.ts.map +1 -1
- package/dist/bun/search.d.ts +5 -3
- package/dist/bun/search.d.ts.map +1 -1
- package/dist/bun/temporal.d.ts.map +1 -1
- package/dist/bun/types.d.ts +1 -1
- package/dist/node/agents-file.d.ts +4 -0
- package/dist/node/agents-file.d.ts.map +1 -1
- package/dist/node/config.d.ts +2 -0
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/curator.d.ts +45 -0
- package/dist/node/curator.d.ts.map +1 -1
- package/dist/node/data-dir.d.ts +18 -0
- package/dist/node/data-dir.d.ts.map +1 -0
- package/dist/node/db.d.ts +12 -0
- package/dist/node/db.d.ts.map +1 -1
- package/dist/node/distillation.d.ts.map +1 -1
- package/dist/node/embedding-vendor.d.ts +22 -38
- package/dist/node/embedding-vendor.d.ts.map +1 -1
- package/dist/node/embedding-worker-types.d.ts +17 -12
- package/dist/node/embedding-worker-types.d.ts.map +1 -1
- package/dist/node/embedding-worker.d.ts +9 -2
- package/dist/node/embedding-worker.d.ts.map +1 -1
- package/dist/node/embedding-worker.js +38864 -33
- package/dist/node/embedding-worker.js.map +4 -4
- package/dist/node/embedding.d.ts +30 -22
- package/dist/node/embedding.d.ts.map +1 -1
- package/dist/node/gradient.d.ts +8 -1
- package/dist/node/gradient.d.ts.map +1 -1
- package/dist/node/import/detect.d.ts +14 -0
- package/dist/node/import/detect.d.ts.map +1 -0
- package/dist/node/import/extract.d.ts +43 -0
- package/dist/node/import/extract.d.ts.map +1 -0
- package/dist/node/import/history.d.ts +40 -0
- package/dist/node/import/history.d.ts.map +1 -0
- package/dist/node/import/index.d.ts +17 -0
- package/dist/node/import/index.d.ts.map +1 -0
- package/dist/node/import/providers/aider.d.ts +2 -0
- package/dist/node/import/providers/aider.d.ts.map +1 -0
- package/dist/node/import/providers/claude-code.d.ts +2 -0
- package/dist/node/import/providers/claude-code.d.ts.map +1 -0
- package/dist/node/import/providers/cline.d.ts +2 -0
- package/dist/node/import/providers/cline.d.ts.map +1 -0
- package/dist/node/import/providers/codex.d.ts +2 -0
- package/dist/node/import/providers/codex.d.ts.map +1 -0
- package/dist/node/import/providers/continue.d.ts +2 -0
- package/dist/node/import/providers/continue.d.ts.map +1 -0
- package/dist/node/import/providers/index.d.ts +19 -0
- package/dist/node/import/providers/index.d.ts.map +1 -0
- package/dist/node/import/providers/opencode.d.ts +2 -0
- package/dist/node/import/providers/opencode.d.ts.map +1 -0
- package/dist/node/import/providers/pi.d.ts +2 -0
- package/dist/node/import/providers/pi.d.ts.map +1 -0
- package/dist/node/import/types.d.ts +82 -0
- package/dist/node/import/types.d.ts.map +1 -0
- package/dist/node/index.d.ts +4 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +2217 -224
- package/dist/node/index.js.map +4 -4
- package/dist/node/instruction-detect.d.ts +66 -0
- package/dist/node/instruction-detect.d.ts.map +1 -0
- package/dist/node/log.d.ts +9 -0
- package/dist/node/log.d.ts.map +1 -1
- package/dist/node/ltm.d.ts +40 -0
- package/dist/node/ltm.d.ts.map +1 -1
- package/dist/node/pattern-extract.d.ts +7 -0
- package/dist/node/pattern-extract.d.ts.map +1 -1
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.d.ts.map +1 -1
- package/dist/node/recall.d.ts.map +1 -1
- package/dist/node/search.d.ts +5 -3
- package/dist/node/search.d.ts.map +1 -1
- package/dist/node/temporal.d.ts.map +1 -1
- package/dist/node/types.d.ts +1 -1
- package/dist/types/agents-file.d.ts +4 -0
- package/dist/types/agents-file.d.ts.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/curator.d.ts +45 -0
- package/dist/types/curator.d.ts.map +1 -1
- package/dist/types/data-dir.d.ts +18 -0
- package/dist/types/data-dir.d.ts.map +1 -0
- package/dist/types/db.d.ts +12 -0
- package/dist/types/db.d.ts.map +1 -1
- package/dist/types/distillation.d.ts.map +1 -1
- package/dist/types/embedding-vendor.d.ts +22 -38
- package/dist/types/embedding-vendor.d.ts.map +1 -1
- package/dist/types/embedding-worker-types.d.ts +17 -12
- package/dist/types/embedding-worker-types.d.ts.map +1 -1
- package/dist/types/embedding-worker.d.ts +9 -2
- package/dist/types/embedding-worker.d.ts.map +1 -1
- package/dist/types/embedding.d.ts +30 -22
- package/dist/types/embedding.d.ts.map +1 -1
- package/dist/types/gradient.d.ts +8 -1
- package/dist/types/gradient.d.ts.map +1 -1
- package/dist/types/import/detect.d.ts +14 -0
- package/dist/types/import/detect.d.ts.map +1 -0
- package/dist/types/import/extract.d.ts +43 -0
- package/dist/types/import/extract.d.ts.map +1 -0
- package/dist/types/import/history.d.ts +40 -0
- package/dist/types/import/history.d.ts.map +1 -0
- package/dist/types/import/index.d.ts +17 -0
- package/dist/types/import/index.d.ts.map +1 -0
- package/dist/types/import/providers/aider.d.ts +2 -0
- package/dist/types/import/providers/aider.d.ts.map +1 -0
- package/dist/types/import/providers/claude-code.d.ts +2 -0
- package/dist/types/import/providers/claude-code.d.ts.map +1 -0
- package/dist/types/import/providers/cline.d.ts +2 -0
- package/dist/types/import/providers/cline.d.ts.map +1 -0
- package/dist/types/import/providers/codex.d.ts +2 -0
- package/dist/types/import/providers/codex.d.ts.map +1 -0
- package/dist/types/import/providers/continue.d.ts +2 -0
- package/dist/types/import/providers/continue.d.ts.map +1 -0
- package/dist/types/import/providers/index.d.ts +19 -0
- package/dist/types/import/providers/index.d.ts.map +1 -0
- package/dist/types/import/providers/opencode.d.ts +2 -0
- package/dist/types/import/providers/opencode.d.ts.map +1 -0
- package/dist/types/import/providers/pi.d.ts +2 -0
- package/dist/types/import/providers/pi.d.ts.map +1 -0
- package/dist/types/import/types.d.ts +82 -0
- package/dist/types/import/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/instruction-detect.d.ts +66 -0
- package/dist/types/instruction-detect.d.ts.map +1 -0
- package/dist/types/log.d.ts +9 -0
- package/dist/types/log.d.ts.map +1 -1
- package/dist/types/ltm.d.ts +40 -0
- package/dist/types/ltm.d.ts.map +1 -1
- package/dist/types/pattern-extract.d.ts +7 -0
- package/dist/types/pattern-extract.d.ts.map +1 -1
- package/dist/types/prompt.d.ts +1 -1
- package/dist/types/prompt.d.ts.map +1 -1
- package/dist/types/recall.d.ts.map +1 -1
- package/dist/types/search.d.ts +5 -3
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/temporal.d.ts.map +1 -1
- package/dist/types/types.d.ts +1 -1
- package/package.json +2 -4
- package/src/agents-file.ts +41 -13
- package/src/config.ts +31 -18
- package/src/curator.ts +111 -75
- package/src/data-dir.ts +76 -0
- package/src/db.ts +110 -11
- package/src/distillation.ts +10 -2
- package/src/embedding-vendor.ts +23 -40
- package/src/embedding-worker-types.ts +19 -11
- package/src/embedding-worker.ts +111 -47
- package/src/embedding.ts +196 -171
- package/src/gradient.ts +9 -1
- package/src/import/detect.ts +37 -0
- package/src/import/extract.ts +137 -0
- package/src/import/history.ts +99 -0
- package/src/import/index.ts +45 -0
- package/src/import/providers/aider.ts +207 -0
- package/src/import/providers/claude-code.ts +339 -0
- package/src/import/providers/cline.ts +324 -0
- package/src/import/providers/codex.ts +369 -0
- package/src/import/providers/continue.ts +304 -0
- package/src/import/providers/index.ts +32 -0
- package/src/import/providers/opencode.ts +272 -0
- package/src/import/providers/pi.ts +332 -0
- package/src/import/types.ts +91 -0
- package/src/index.ts +5 -0
- package/src/instruction-detect.ts +275 -0
- package/src/log.ts +91 -3
- package/src/ltm.ts +316 -3
- package/src/pattern-extract.ts +41 -0
- package/src/prompt.ts +7 -1
- package/src/recall.ts +43 -5
- package/src/search.ts +7 -5
- package/src/temporal.ts +8 -6
- 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);
|