memory-eternal 0.1.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/lib/sweep.js ADDED
@@ -0,0 +1,56 @@
1
+ // 记忆核心 · 会话记录挖掘(简版 sweep)。
2
+ //
3
+ // 扫描目录下的 Claude Code 会话 JSONL(*.jsonl),每文件提取对话文本,
4
+ // 按文件粒度蒸馏入库(source 带 sweep 标记;重复跑幂等靠 vault 词法去重)。
5
+ // 适用:Codex sessions 目录 / Claude Code projects 目录 / 任意兼容 JSONL。
6
+
7
+ import { promises as fs } from 'node:fs'
8
+ import path from 'node:path'
9
+ import { runStandaloneCapture, defaultVaultDir } from './capture-run.js'
10
+
11
+ async function* walk(dir) {
12
+ let entries
13
+ try { entries = await fs.readdir(dir, { withFileTypes: true }) } catch { return }
14
+ for (const e of entries) {
15
+ const p = path.join(dir, e.name)
16
+ if (e.isDirectory()) yield* walk(p)
17
+ else if (e.name.endsWith('.jsonl')) yield p
18
+ }
19
+ }
20
+
21
+ function extractConversation(jsonlText) {
22
+ const texts = []
23
+ for (const line of jsonlText.split('\n')) {
24
+ if (!line.trim()) continue
25
+ let rec
26
+ try { rec = JSON.parse(line) } catch { continue }
27
+ const msg = rec?.message
28
+ if (!msg || !msg.role) continue
29
+ let text = ''
30
+ if (typeof msg.content === 'string') text = msg.content
31
+ else if (Array.isArray(msg.content)) {
32
+ text = msg.content.filter((c) => c?.type === 'text').map((c) => c.text).join('\n')
33
+ }
34
+ if (text && text.trim().length > 4) texts.push(`${msg.role}: ${text.trim()}`)
35
+ }
36
+ return texts.slice(-60).join('\n\n')
37
+ }
38
+
39
+ export async function sweepSessions(dir, { vaultRoot = defaultVaultDir(), limit = 50 } = {}) {
40
+ const root = path.resolve(dir)
41
+ let scanned = 0
42
+ let captured = 0
43
+ let skipped = 0
44
+ for await (const file of walk(root)) {
45
+ if (scanned >= limit) break
46
+ scanned++
47
+ try {
48
+ const text = extractConversation(await fs.readFile(file, 'utf8'))
49
+ if (text.trim().length < 200) { skipped++; continue }
50
+ const out = await runStandaloneCapture(vaultRoot, text, { source: `sweep:${path.basename(file)}` })
51
+ if (out.ok && out.action !== 'skipped') captured++
52
+ else skipped++
53
+ } catch { skipped++ }
54
+ }
55
+ return { ok: true, dir: root, scanned, captured, skipped }
56
+ }