trove-cli 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +200 -2
- package/dist/adapters/file-source.d.ts +31 -0
- package/dist/adapters/file-source.js +97 -0
- package/dist/adapters/file-source.js.map +1 -0
- package/dist/adapters/index.d.ts +34 -0
- package/dist/adapters/index.js +146 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +30 -0
- package/dist/adapters/opencode.js +135 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/cli.d.ts +58 -0
- package/dist/cli.js +972 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.js +99 -0
- package/dist/config.js.map +1 -0
- package/dist/core/archive.d.ts +27 -0
- package/dist/core/archive.js +101 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/chunker.d.ts +28 -0
- package/dist/core/chunker.js +67 -0
- package/dist/core/chunker.js.map +1 -0
- package/dist/core/clean.d.ts +36 -0
- package/dist/core/clean.js +182 -0
- package/dist/core/clean.js.map +1 -0
- package/dist/core/collect.d.ts +39 -0
- package/dist/core/collect.js +194 -0
- package/dist/core/collect.js.map +1 -0
- package/dist/core/concurrency.d.ts +2 -0
- package/dist/core/concurrency.js +16 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/cursor.d.ts +31 -0
- package/dist/core/cursor.js +31 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/events/claude.d.ts +7 -0
- package/dist/core/events/claude.js +287 -0
- package/dist/core/events/claude.js.map +1 -0
- package/dist/core/events/codex.d.ts +6 -0
- package/dist/core/events/codex.js +149 -0
- package/dist/core/events/codex.js.map +1 -0
- package/dist/core/events/doubao.d.ts +9 -0
- package/dist/core/events/doubao.js +151 -0
- package/dist/core/events/doubao.js.map +1 -0
- package/dist/core/events/index.d.ts +6 -0
- package/dist/core/events/index.js +20 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/events/opencode.d.ts +7 -0
- package/dist/core/events/opencode.js +189 -0
- package/dist/core/events/opencode.js.map +1 -0
- package/dist/core/events/schema.d.ts +83 -0
- package/dist/core/events/schema.js +35 -0
- package/dist/core/events/schema.js.map +1 -0
- package/dist/core/layout.d.ts +26 -0
- package/dist/core/layout.js +122 -0
- package/dist/core/layout.js.map +1 -0
- package/dist/core/lessons/analyze.d.ts +40 -0
- package/dist/core/lessons/analyze.js +107 -0
- package/dist/core/lessons/analyze.js.map +1 -0
- package/dist/core/lessons/audit.d.ts +51 -0
- package/dist/core/lessons/audit.js +57 -0
- package/dist/core/lessons/audit.js.map +1 -0
- package/dist/core/lessons/compaction.d.ts +5 -0
- package/dist/core/lessons/compaction.js +58 -0
- package/dist/core/lessons/compaction.js.map +1 -0
- package/dist/core/lessons/config.d.ts +15 -0
- package/dist/core/lessons/config.js +31 -0
- package/dist/core/lessons/config.js.map +1 -0
- package/dist/core/lessons/existing.d.ts +4 -0
- package/dist/core/lessons/existing.js +22 -0
- package/dist/core/lessons/existing.js.map +1 -0
- package/dist/core/lessons/extract.d.ts +21 -0
- package/dist/core/lessons/extract.js +106 -0
- package/dist/core/lessons/extract.js.map +1 -0
- package/dist/core/lessons/frontmatter.d.ts +29 -0
- package/dist/core/lessons/frontmatter.js +87 -0
- package/dist/core/lessons/frontmatter.js.map +1 -0
- package/dist/core/lessons/graph.d.ts +256 -0
- package/dist/core/lessons/graph.js +86 -0
- package/dist/core/lessons/graph.js.map +1 -0
- package/dist/core/lessons/jev.d.ts +36 -0
- package/dist/core/lessons/jev.js +100 -0
- package/dist/core/lessons/jev.js.map +1 -0
- package/dist/core/lessons/layout.d.ts +2 -0
- package/dist/core/lessons/layout.js +56 -0
- package/dist/core/lessons/layout.js.map +1 -0
- package/dist/core/lessons/learn.d.ts +38 -0
- package/dist/core/lessons/learn.js +190 -0
- package/dist/core/lessons/learn.js.map +1 -0
- package/dist/core/lessons/llm.d.ts +42 -0
- package/dist/core/lessons/llm.js +106 -0
- package/dist/core/lessons/llm.js.map +1 -0
- package/dist/core/lessons/memo.d.ts +19 -0
- package/dist/core/lessons/memo.js +170 -0
- package/dist/core/lessons/memo.js.map +1 -0
- package/dist/core/lessons/merge.d.ts +39 -0
- package/dist/core/lessons/merge.js +79 -0
- package/dist/core/lessons/merge.js.map +1 -0
- package/dist/core/lessons/queue.d.ts +6 -0
- package/dist/core/lessons/queue.js +52 -0
- package/dist/core/lessons/queue.js.map +1 -0
- package/dist/core/lessons/recent.d.ts +21 -0
- package/dist/core/lessons/recent.js +111 -0
- package/dist/core/lessons/recent.js.map +1 -0
- package/dist/core/lessons/schema.d.ts +26 -0
- package/dist/core/lessons/schema.js +64 -0
- package/dist/core/lessons/schema.js.map +1 -0
- package/dist/core/lessons/select.d.ts +19 -0
- package/dist/core/lessons/select.js +55 -0
- package/dist/core/lessons/select.js.map +1 -0
- package/dist/core/lessons/sidecar.d.ts +23 -0
- package/dist/core/lessons/sidecar.js +171 -0
- package/dist/core/lessons/sidecar.js.map +1 -0
- package/dist/core/lessons/storage.d.ts +21 -0
- package/dist/core/lessons/storage.js +65 -0
- package/dist/core/lessons/storage.js.map +1 -0
- package/dist/core/lessons/usage.d.ts +31 -0
- package/dist/core/lessons/usage.js +91 -0
- package/dist/core/lessons/usage.js.map +1 -0
- package/dist/core/lessons/write.d.ts +18 -0
- package/dist/core/lessons/write.js +131 -0
- package/dist/core/lessons/write.js.map +1 -0
- package/dist/core/library/build.d.ts +22 -0
- package/dist/core/library/build.js +163 -0
- package/dist/core/library/build.js.map +1 -0
- package/dist/core/library/group.d.ts +14 -0
- package/dist/core/library/group.js +129 -0
- package/dist/core/library/group.js.map +1 -0
- package/dist/core/library/index-file.d.ts +7 -0
- package/dist/core/library/index-file.js +250 -0
- package/dist/core/library/index-file.js.map +1 -0
- package/dist/core/library/schema.d.ts +78 -0
- package/dist/core/library/schema.js +37 -0
- package/dist/core/library/schema.js.map +1 -0
- package/dist/core/library/skills.d.ts +26 -0
- package/dist/core/library/skills.js +168 -0
- package/dist/core/library/skills.js.map +1 -0
- package/dist/core/library/sync.d.ts +48 -0
- package/dist/core/library/sync.js +271 -0
- package/dist/core/library/sync.js.map +1 -0
- package/dist/core/lock.d.ts +6 -0
- package/dist/core/lock.js +56 -0
- package/dist/core/lock.js.map +1 -0
- package/dist/core/pull.d.ts +23 -0
- package/dist/core/pull.js +147 -0
- package/dist/core/pull.js.map +1 -0
- package/dist/core/push.d.ts +29 -0
- package/dist/core/push.js +170 -0
- package/dist/core/push.js.map +1 -0
- package/dist/core/remote-layout.d.ts +67 -0
- package/dist/core/remote-layout.js +94 -0
- package/dist/core/remote-layout.js.map +1 -0
- package/dist/core/sessions.d.ts +29 -0
- package/dist/core/sessions.js +96 -0
- package/dist/core/sessions.js.map +1 -0
- package/dist/core/state.d.ts +94 -0
- package/dist/core/state.js +62 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/sync.d.ts +48 -0
- package/dist/core/sync.js +140 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/core/time-window.d.ts +8 -0
- package/dist/core/time-window.js +36 -0
- package/dist/core/time-window.js.map +1 -0
- package/dist/daemon.d.ts +73 -0
- package/dist/daemon.js +272 -0
- package/dist/daemon.js.map +1 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +28 -0
- package/dist/format.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +9 -0
- package/dist/main.js.map +1 -0
- package/dist/paths.d.ts +19 -0
- package/dist/paths.js +19 -0
- package/dist/paths.js.map +1 -0
- package/dist/progress-console.d.ts +12 -0
- package/dist/progress-console.js +42 -0
- package/dist/progress-console.js.map +1 -0
- package/dist/progress.d.ts +7 -0
- package/dist/progress.js +6 -0
- package/dist/progress.js.map +1 -0
- package/dist/remote/create-remote.d.ts +20 -0
- package/dist/remote/create-remote.js +86 -0
- package/dist/remote/create-remote.js.map +1 -0
- package/dist/remote/prompt.d.ts +17 -0
- package/dist/remote/prompt.js +111 -0
- package/dist/remote/prompt.js.map +1 -0
- package/dist/remote/s3-sigv4.d.ts +28 -0
- package/dist/remote/s3-sigv4.js +100 -0
- package/dist/remote/s3-sigv4.js.map +1 -0
- package/dist/remote/s3-store.d.ts +75 -0
- package/dist/remote/s3-store.js +438 -0
- package/dist/remote/s3-store.js.map +1 -0
- package/dist/remote/s3-vendors.d.ts +31 -0
- package/dist/remote/s3-vendors.js +47 -0
- package/dist/remote/s3-vendors.js.map +1 -0
- package/dist/remote/store.d.ts +44 -0
- package/dist/remote/store.js +2 -0
- package/dist/remote/store.js.map +1 -0
- package/dist/remote/webdav-store.d.ts +41 -0
- package/dist/remote/webdav-store.js +285 -0
- package/dist/remote/webdav-store.js.map +1 -0
- package/dist/sqlite.d.ts +3 -0
- package/dist/sqlite.js +12 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/status.d.ts +82 -0
- package/dist/status.js +162 -0
- package/dist/status.js.map +1 -0
- package/package.json +43 -4
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** lessons 目录骨架(0.2 实体化口径)的幂等初始化。INDEX.md 初始为空索引但分区标题齐全。 */
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { access, mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { libraryLessonsDir } from '../layout.js';
|
|
6
|
+
const SUBDIRS = [
|
|
7
|
+
'preferences',
|
|
8
|
+
'entities/projects',
|
|
9
|
+
'entities/people',
|
|
10
|
+
'entities/organizations',
|
|
11
|
+
'entities/tools',
|
|
12
|
+
'entities/tech',
|
|
13
|
+
'experiences',
|
|
14
|
+
'events',
|
|
15
|
+
'resources/docs',
|
|
16
|
+
'resources/repos',
|
|
17
|
+
'resources/links',
|
|
18
|
+
'archive/preferences',
|
|
19
|
+
'archive/entities',
|
|
20
|
+
'archive/experiences',
|
|
21
|
+
'archive/events',
|
|
22
|
+
'archive/resources',
|
|
23
|
+
];
|
|
24
|
+
export const INDEX_TEMPLATE = [
|
|
25
|
+
'# INDEX',
|
|
26
|
+
'',
|
|
27
|
+
'## Preferences',
|
|
28
|
+
'',
|
|
29
|
+
'## Entities',
|
|
30
|
+
'',
|
|
31
|
+
'## Experiences',
|
|
32
|
+
'',
|
|
33
|
+
'## Events',
|
|
34
|
+
'',
|
|
35
|
+
'## Resources',
|
|
36
|
+
'',
|
|
37
|
+
].join('\n');
|
|
38
|
+
const OVERVIEW_TEMPLATE = '# 学习沉淀概览\n\n';
|
|
39
|
+
export async function ensureLessonsLayout(vaultRoot) {
|
|
40
|
+
const root = libraryLessonsDir(vaultRoot);
|
|
41
|
+
await mkdir(root, { recursive: true });
|
|
42
|
+
for (const sub of SUBDIRS) {
|
|
43
|
+
await mkdir(join(root, sub), { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
await ensureFile(join(root, 'INDEX.md'), INDEX_TEMPLATE);
|
|
46
|
+
await ensureFile(join(root, '.overview.md'), OVERVIEW_TEMPLATE);
|
|
47
|
+
}
|
|
48
|
+
async function ensureFile(path, content) {
|
|
49
|
+
try {
|
|
50
|
+
await access(path, constants.F_OK);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
await writeFile(path, content, 'utf8');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/core/lessons/layout.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,OAAO,GAAG;IACd,aAAa;IACb,mBAAmB;IACnB,iBAAiB;IACjB,wBAAwB;IACxB,gBAAgB;IAChB,eAAe;IACf,aAAa;IACb,QAAQ;IACR,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;IACjB,qBAAqB;IACrB,kBAAkB;IAClB,qBAAqB;IACrB,gBAAgB;IAChB,mBAAmB;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,SAAS;IACT,EAAE;IACF,gBAAgB;IAChB,EAAE;IACF,aAAa;IACb,EAAE;IACF,gBAAgB;IAChB,EAAE;IACF,WAAW;IACX,EAAE;IACF,cAAc;IACd,EAAE;CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAiB;IACzD,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,cAAc,CAAC,CAAC;IACzD,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAClE,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,OAAe;IACrD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Event } from '../events/schema.js';
|
|
2
|
+
import type { Config } from '../../config.js';
|
|
3
|
+
import type { LlmProvider } from './llm.js';
|
|
4
|
+
import type { JevProvider } from './jev.js';
|
|
5
|
+
import type { MemoryDiff } from './audit.js';
|
|
6
|
+
export interface LessonProviderDeps {
|
|
7
|
+
llm?: LlmProvider;
|
|
8
|
+
jev?: JevProvider;
|
|
9
|
+
/** 可控时钟,测试注入;缺省系统时间。 */
|
|
10
|
+
now?: () => Date;
|
|
11
|
+
/** 覆盖环境变量;缺省读 process.env。 */
|
|
12
|
+
env?: Record<string, string | undefined>;
|
|
13
|
+
/** token 用量台账路径;缺省 ~/.trove/usage.jsonl。 */
|
|
14
|
+
usagePath?: string;
|
|
15
|
+
/** 模型端点配置(~/.trove/config.json 的 llm/jev 段);缺省由调用方注入。 */
|
|
16
|
+
config?: Pick<Config, 'llm' | 'jev'>;
|
|
17
|
+
}
|
|
18
|
+
export interface ExtractionOutcome {
|
|
19
|
+
diff?: MemoryDiff;
|
|
20
|
+
skipReason?: string;
|
|
21
|
+
}
|
|
22
|
+
/** 从统一索引解析单会话的 canonical 事件;裸 session 错配多个 harness 时报歧义。 */
|
|
23
|
+
export declare function loadSessionEvents(vaultRoot: string, sessionId: string): Promise<Event[]>;
|
|
24
|
+
/** 对单会话跑全链路;被过滤(阶段 4)时不落任何记忆文件,通过 skipReason 区分。 */
|
|
25
|
+
export declare function runLessonExtraction(vaultRoot: string, sessionId: string, deps?: LessonProviderDeps, opts?: {
|
|
26
|
+
force?: boolean;
|
|
27
|
+
}): Promise<ExtractionOutcome>;
|
|
28
|
+
/**
|
|
29
|
+
* 从队列取最多 batch 个候选,逐个跑流水线;处理完(成功/被过滤/失败)都出队,失败落 .failed 标记。
|
|
30
|
+
* 只学队列里的「本轮新会话」,历史会话走 `trove learn <id>` 手动补。缺配置时不 drain 队列。
|
|
31
|
+
*/
|
|
32
|
+
export declare function learnPendingBatch(vaultRoot: string, queuePath: string, deps?: LessonProviderDeps, batch?: number, opts?: {
|
|
33
|
+
log?: (line: string) => void;
|
|
34
|
+
}): Promise<string | undefined>;
|
|
35
|
+
/** 手动批量学习:取最近 limit 个未学习会话逐条跑提取,与 daemon 队列学习互不影响。 */
|
|
36
|
+
export declare function learnRecentBatch(vaultRoot: string, limit: number, deps?: LessonProviderDeps, opts?: {
|
|
37
|
+
log?: (line: string) => void;
|
|
38
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/** 阶段 9:经验提取的编排入口。把「选会话 → 跑流水线 → 批量跳过已审计」接到 CLI 与 daemon。 */
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { libraryDir, libraryLessonsDir } from '../layout.js';
|
|
5
|
+
import { trovePaths } from '../../paths.js';
|
|
6
|
+
import { loadLibraryIndex, rebuildUnifiedIndex } from '../library/index-file.js';
|
|
7
|
+
import { ensureLessonsLayout } from './layout.js';
|
|
8
|
+
import { LocalStorageAdapter } from './storage.js';
|
|
9
|
+
import { readLlmConfig, readJevConfig } from './config.js';
|
|
10
|
+
import { DeepSeekLlm, LlmParseError } from './llm.js';
|
|
11
|
+
import { createUsageSink } from './usage.js';
|
|
12
|
+
import { TypeSafeJevScorer } from './jev.js';
|
|
13
|
+
import { buildLessonGraph } from './graph.js';
|
|
14
|
+
import { readLearnQueue, removeLearn } from './queue.js';
|
|
15
|
+
/** 从统一索引解析单会话的 canonical 事件;裸 session 错配多个 harness 时报歧义。 */
|
|
16
|
+
export async function loadSessionEvents(vaultRoot, sessionId) {
|
|
17
|
+
const index = (await loadLibraryIndex(vaultRoot)) ?? (await rebuildUnifiedIndex(vaultRoot));
|
|
18
|
+
const matches = Object.values(index.sessions).filter((s) => s.session === sessionId || `${s.harness}:${s.session}` === sessionId);
|
|
19
|
+
if (matches.length === 0)
|
|
20
|
+
throw new Error(`没有找到会话:${sessionId}`);
|
|
21
|
+
if (matches.length > 1) {
|
|
22
|
+
const keys = matches.map((s) => `${s.harness}:${s.session}`).join('、');
|
|
23
|
+
throw new Error(`会话 ${sessionId} 存在歧义:命中多个 harness(${keys}),请用 harness:session 指定`);
|
|
24
|
+
}
|
|
25
|
+
const s = matches[0];
|
|
26
|
+
const path = join(libraryDir(vaultRoot), ...s.canonical.path.split('/'));
|
|
27
|
+
const text = await readFile(path, 'utf8');
|
|
28
|
+
const events = [];
|
|
29
|
+
for (const line of text.split('\n')) {
|
|
30
|
+
if (!line.trim())
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
events.push(JSON.parse(line));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// 跳过无法解析的行,镜像 index-file 的容错口径
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return events;
|
|
40
|
+
}
|
|
41
|
+
function resolveProviders(deps) {
|
|
42
|
+
// 懒构造:LLM/Jev 配置缺失时由调用方决定跳过还是报错,不拖垮无关命令与 daemon。
|
|
43
|
+
const usagePath = deps.usagePath ?? trovePaths(deps.env?.TROVE_HOME).usageLog;
|
|
44
|
+
const llm = deps.llm ?? new DeepSeekLlm(readLlmConfig(deps.config), { onUsage: createUsageSink(usagePath) });
|
|
45
|
+
const jev = deps.jev ?? new TypeSafeJevScorer(readJevConfig(deps.config));
|
|
46
|
+
return { llm, jev };
|
|
47
|
+
}
|
|
48
|
+
/** 对单会话跑全链路;被过滤(阶段 4)时不落任何记忆文件,通过 skipReason 区分。 */
|
|
49
|
+
export async function runLessonExtraction(vaultRoot, sessionId, deps = {}, opts = {}) {
|
|
50
|
+
await ensureLessonsLayout(vaultRoot);
|
|
51
|
+
const storage = new LocalStorageAdapter(libraryLessonsDir(vaultRoot));
|
|
52
|
+
// 幂等键 = audit/<sessionId>.json:已提取过默认跳过,force 强制重跑(配合 merge 层幂等不翻倍)。
|
|
53
|
+
if (!opts.force && (await storage.exists(`audit/${sessionId}.json`))) {
|
|
54
|
+
return { skipReason: 'already_learned' };
|
|
55
|
+
}
|
|
56
|
+
const { llm, jev } = resolveProviders(deps);
|
|
57
|
+
const graph = buildLessonGraph({
|
|
58
|
+
llm,
|
|
59
|
+
jev,
|
|
60
|
+
storage,
|
|
61
|
+
loadEvents: (id) => loadSessionEvents(vaultRoot, id),
|
|
62
|
+
now: deps.now,
|
|
63
|
+
});
|
|
64
|
+
const out = await graph.invoke({ sessionId }).catch((e) => {
|
|
65
|
+
// 模型重试后仍输出非 JSON:当跳过忽略,不记失败;落 skipped 标记阻止反复重跑。
|
|
66
|
+
if (e instanceof LlmParseError)
|
|
67
|
+
return null;
|
|
68
|
+
throw e;
|
|
69
|
+
});
|
|
70
|
+
if (out === null) {
|
|
71
|
+
await storage
|
|
72
|
+
.write(`audit/${sessionId}.skipped.json`, JSON.stringify({ session_id: sessionId, reason: 'llm_parse_failed', at: new Date().toISOString() }, null, 2))
|
|
73
|
+
.catch(() => { });
|
|
74
|
+
return { skipReason: 'llm_parse_failed' };
|
|
75
|
+
}
|
|
76
|
+
if (out.skip) {
|
|
77
|
+
// 只在「进行中」豁免:会话尚未结束,等结束后重新进入候选;其余跳过(摘要 trivial/无分类/Jev 低分)落标记,避免反复分析。
|
|
78
|
+
if (out.skip.reason !== 'in_progress') {
|
|
79
|
+
// 落盘失败不视为提取失败:最多下次再分析一次,不误报 .failed。
|
|
80
|
+
await storage
|
|
81
|
+
.write(`audit/${sessionId}.skipped.json`, JSON.stringify({ session_id: sessionId, reason: out.skip.reason, at: new Date().toISOString() }, null, 2))
|
|
82
|
+
.catch(() => { });
|
|
83
|
+
}
|
|
84
|
+
return { skipReason: out.skip.reason };
|
|
85
|
+
}
|
|
86
|
+
return { diff: out.diff };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 从队列取最多 batch 个候选,逐个跑流水线;处理完(成功/被过滤/失败)都出队,失败落 .failed 标记。
|
|
90
|
+
* 只学队列里的「本轮新会话」,历史会话走 `trove learn <id>` 手动补。缺配置时不 drain 队列。
|
|
91
|
+
*/
|
|
92
|
+
export async function learnPendingBatch(vaultRoot, queuePath, deps = {}, batch = 3, opts = {}) {
|
|
93
|
+
const ids = await readLearnQueue(queuePath);
|
|
94
|
+
if (ids.length === 0)
|
|
95
|
+
return undefined;
|
|
96
|
+
let providers;
|
|
97
|
+
try {
|
|
98
|
+
providers = resolveProviders(deps);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
opts.log?.(`跳过经验提取:${e.message}`);
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const candidates = ids.slice(0, batch);
|
|
105
|
+
const storage = new LocalStorageAdapter(libraryLessonsDir(vaultRoot));
|
|
106
|
+
let extracted = 0;
|
|
107
|
+
let skipped = 0;
|
|
108
|
+
let failed = 0;
|
|
109
|
+
for (const key of candidates) {
|
|
110
|
+
try {
|
|
111
|
+
const outcome = await runLessonExtraction(vaultRoot, key, { llm: providers.llm, jev: providers.jev, now: deps.now });
|
|
112
|
+
if (outcome.skipReason) {
|
|
113
|
+
skipped++;
|
|
114
|
+
opts.log?.(`会话 ${key} 未达阈值,跳过:${outcome.skipReason}`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
extracted++;
|
|
118
|
+
const d = outcome.diff?.summary;
|
|
119
|
+
opts.log?.(`会话 ${key} 提取完成:新增 ${d?.adds ?? 0}、更新 ${d?.updates ?? 0}、跳过 ${d?.skips ?? 0}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
failed++;
|
|
124
|
+
const msg = e.message;
|
|
125
|
+
// 落失败标记,阻止同一坏会话被反复重跑完整 LLM 流水线;标记可删后重试。
|
|
126
|
+
await storage.write(`audit/${key}.failed.json`, JSON.stringify({ session_id: key, error: msg, at: new Date().toISOString() }, null, 2)).catch(() => { });
|
|
127
|
+
opts.log?.(`会话 ${key} 提取失败:${msg}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
await removeLearn(queuePath, candidates);
|
|
131
|
+
const remaining = (await readLearnQueue(queuePath)).length;
|
|
132
|
+
return `经验提取:新增 ${extracted} · 跳过 ${skipped} · 失败 ${failed} · 待办 ${remaining}`;
|
|
133
|
+
}
|
|
134
|
+
/** 从统一索引按 startedAt 降序取最多 limit 个「未学习」会话 key(harness:session)。 */
|
|
135
|
+
async function recentUnlearnedKeys(vaultRoot, limit) {
|
|
136
|
+
const index = (await loadLibraryIndex(vaultRoot)) ?? (await rebuildUnifiedIndex(vaultRoot));
|
|
137
|
+
const storage = new LocalStorageAdapter(libraryLessonsDir(vaultRoot));
|
|
138
|
+
const entries = Object.values(index.sessions).sort((a, b) => (b.startedAt ?? '') > (a.startedAt ?? '') ? 1 : -1);
|
|
139
|
+
const keys = [];
|
|
140
|
+
for (const s of entries) {
|
|
141
|
+
const key = `${s.harness}:${s.session}`;
|
|
142
|
+
// 已成功(.json)或已失败(.failed.json)都算已处理;双形式兜底历史裸 session 落盘。
|
|
143
|
+
const handled = (await storage.exists(`audit/${key}.json`)) ||
|
|
144
|
+
(await storage.exists(`audit/${s.session}.json`)) ||
|
|
145
|
+
(await storage.exists(`audit/${key}.failed.json`)) ||
|
|
146
|
+
(await storage.exists(`audit/${s.session}.failed.json`)) ||
|
|
147
|
+
(await storage.exists(`audit/${key}.skipped.json`)) ||
|
|
148
|
+
(await storage.exists(`audit/${s.session}.skipped.json`));
|
|
149
|
+
if (!handled)
|
|
150
|
+
keys.push(key);
|
|
151
|
+
if (keys.length >= limit)
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
return keys;
|
|
155
|
+
}
|
|
156
|
+
/** 手动批量学习:取最近 limit 个未学习会话逐条跑提取,与 daemon 队列学习互不影响。 */
|
|
157
|
+
export async function learnRecentBatch(vaultRoot, limit, deps = {}, opts = {}) {
|
|
158
|
+
const keys = await recentUnlearnedKeys(vaultRoot, limit);
|
|
159
|
+
if (keys.length === 0)
|
|
160
|
+
return '没有可学习的会话(都已学习或索引为空)';
|
|
161
|
+
// 缺配置在此抛出,与单条 `trove learn <id>` 一致,不静默吞掉手动触发的错误。
|
|
162
|
+
const { llm, jev } = resolveProviders(deps);
|
|
163
|
+
const storage = new LocalStorageAdapter(libraryLessonsDir(vaultRoot));
|
|
164
|
+
let extracted = 0;
|
|
165
|
+
let skipped = 0;
|
|
166
|
+
let failed = 0;
|
|
167
|
+
for (let i = 0; i < keys.length; i++) {
|
|
168
|
+
const key = keys[i];
|
|
169
|
+
try {
|
|
170
|
+
const outcome = await runLessonExtraction(vaultRoot, key, { llm, jev, now: deps.now });
|
|
171
|
+
if (outcome.skipReason) {
|
|
172
|
+
skipped++;
|
|
173
|
+
opts.log?.(`[${i + 1}/${keys.length}] ${key} 未达阈值,跳过:${outcome.skipReason}`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
extracted++;
|
|
177
|
+
const d = outcome.diff?.summary;
|
|
178
|
+
opts.log?.(`[${i + 1}/${keys.length}] ${key} 完成:新增 ${d?.adds ?? 0}、更新 ${d?.updates ?? 0}、跳过 ${d?.skips ?? 0}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
failed++;
|
|
183
|
+
const msg = e.message;
|
|
184
|
+
await storage.write(`audit/${key}.failed.json`, JSON.stringify({ session_id: key, error: msg, at: new Date().toISOString() }, null, 2)).catch(() => { });
|
|
185
|
+
opts.log?.(`[${i + 1}/${keys.length}] ${key} 失败:${msg}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return `经验提取完成:新增 ${extracted} · 跳过 ${skipped} · 失败 ${failed} · 共 ${keys.length} 个会话`;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=learn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learn.js","sourceRoot":"","sources":["../../../src/core/lessons/learn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { LlmConfig } from './config.js';
|
|
2
|
+
import type { UsageSink } from './usage.js';
|
|
3
|
+
export interface LlmProvider {
|
|
4
|
+
/**
|
|
5
|
+
* 用提示词生成符合给定 JSON Schema 的对象。schema 为 JSON Schema 对象描述。
|
|
6
|
+
* task 只用于 token 用量归因(如 learn:summarize),不影响生成结果。
|
|
7
|
+
*/
|
|
8
|
+
generate<T>(prompt: string, schema: Record<string, unknown>, task?: string): Promise<T>;
|
|
9
|
+
}
|
|
10
|
+
export interface DeepSeekLlmOptions {
|
|
11
|
+
/** 每次调用后上报 token 用量;上报失败不影响生成结果。 */
|
|
12
|
+
onUsage?: UsageSink;
|
|
13
|
+
}
|
|
14
|
+
/** DeepSeek(OpenAI 兼容)实现:json_object 模式返回纯 JSON,内容手动解析(DeepSeek 不支持 json_schema 的 response_format)。 */
|
|
15
|
+
export declare class DeepSeekLlm implements LlmProvider {
|
|
16
|
+
private options;
|
|
17
|
+
private llm;
|
|
18
|
+
private model;
|
|
19
|
+
constructor(config: LlmConfig, options?: DeepSeekLlmOptions);
|
|
20
|
+
generate<T>(prompt: string, schema: Record<string, unknown>, task?: string): Promise<T>;
|
|
21
|
+
private invokeText;
|
|
22
|
+
}
|
|
23
|
+
export interface MessageUsage {
|
|
24
|
+
promptTokens: number;
|
|
25
|
+
completionTokens: number;
|
|
26
|
+
totalTokens: number;
|
|
27
|
+
}
|
|
28
|
+
/** 从 LangChain AIMessage 提取 token 用量:优先 usage_metadata,回退 OpenAI 的 response_metadata.tokenUsage。 */
|
|
29
|
+
export declare function usageOfMessage(res: unknown): MessageUsage | null;
|
|
30
|
+
/** 模型返回文本无法解析为 JSON 时抛出;区别于网络/服务错误,供上层决定「重试后忽略」还是「记失败」。 */
|
|
31
|
+
export declare class LlmParseError extends Error {
|
|
32
|
+
constructor(message: string);
|
|
33
|
+
}
|
|
34
|
+
/** 清洗 LLM 返回文本(去掉 ```json 围栏与首尾空白)并解析为对象;失败抛 LlmParseError。 */
|
|
35
|
+
export declare function parseJsonObject<T>(text: string): T;
|
|
36
|
+
export type LlmResponder = (prompt: string, schema: Record<string, unknown>, task?: string) => unknown | Promise<unknown>;
|
|
37
|
+
/** mock 实现:按注入的回调返回固定对象,不发起任何网络请求。 */
|
|
38
|
+
export declare class MockLlm implements LlmProvider {
|
|
39
|
+
private responder;
|
|
40
|
+
constructor(responder: LlmResponder);
|
|
41
|
+
generate<T>(prompt: string, schema: Record<string, unknown>, task?: string): Promise<T>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** LLM 抽象:给提示词 + 期望的 JSON 结构,返回解析后的对象。一期接 DeepSeek,测试用 mock 注入固定返回。 */
|
|
2
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
3
|
+
/** DeepSeek(OpenAI 兼容)实现:json_object 模式返回纯 JSON,内容手动解析(DeepSeek 不支持 json_schema 的 response_format)。 */
|
|
4
|
+
export class DeepSeekLlm {
|
|
5
|
+
options;
|
|
6
|
+
llm;
|
|
7
|
+
model;
|
|
8
|
+
constructor(config, options = {}) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.model = config.model;
|
|
11
|
+
this.llm = new ChatOpenAI({
|
|
12
|
+
model: config.model,
|
|
13
|
+
apiKey: config.apiKey,
|
|
14
|
+
configuration: { baseURL: config.baseUrl },
|
|
15
|
+
modelKwargs: { response_format: { type: 'json_object' } },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async generate(prompt, schema, task = 'llm') {
|
|
19
|
+
// schema 注入 prompt 引导字段;json_object 要求 prompt 含 "json" 字样,故用 JSON Schema 措辞。
|
|
20
|
+
const full = `${prompt}\n\n按以下 JSON Schema 返回一个 JSON 对象,不要输出其它内容:\n${JSON.stringify(schema)}`;
|
|
21
|
+
try {
|
|
22
|
+
return parseJsonObject(await this.invokeText(full, task));
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
if (!(e instanceof LlmParseError))
|
|
26
|
+
throw e;
|
|
27
|
+
// 纠错:模型偶尔无视 json_object 输出非 JSON(如写文档会话),重试一次并把格式约束说死。
|
|
28
|
+
const retry = `${full}\n\n上次输出不是合法 JSON。忽略原任务,只返回一个 JSON 对象,不要任何解释或原文。`;
|
|
29
|
+
return parseJsonObject(await this.invokeText(retry, task));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async invokeText(full, task) {
|
|
33
|
+
const res = await this.llm.invoke(full);
|
|
34
|
+
// tokens 已实际消耗:先记账再解析,解析失败也不能漏记;写盘失败不拖垮提取。
|
|
35
|
+
const usage = usageOfMessage(res);
|
|
36
|
+
if (usage) {
|
|
37
|
+
try {
|
|
38
|
+
await this.options.onUsage?.({ task, model: this.model, ...usage });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// 用量统计失败不影响生成
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return typeof res.content === 'string' ? res.content : JSON.stringify(res.content);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** 从 LangChain AIMessage 提取 token 用量:优先 usage_metadata,回退 OpenAI 的 response_metadata.tokenUsage。 */
|
|
48
|
+
export function usageOfMessage(res) {
|
|
49
|
+
if (typeof res !== 'object' || res === null)
|
|
50
|
+
return null;
|
|
51
|
+
const r = res;
|
|
52
|
+
if (isObject(r.usage_metadata)) {
|
|
53
|
+
const m = r.usage_metadata;
|
|
54
|
+
const usage = normalize(intOf(m.input_tokens), intOf(m.output_tokens), intOf(m.total_tokens));
|
|
55
|
+
if (usage)
|
|
56
|
+
return usage;
|
|
57
|
+
}
|
|
58
|
+
const response = r.response_metadata;
|
|
59
|
+
if (isObject(response) && isObject(response.tokenUsage)) {
|
|
60
|
+
const m = response.tokenUsage;
|
|
61
|
+
const usage = normalize(intOf(m.promptTokens), intOf(m.completionTokens), intOf(m.totalTokens));
|
|
62
|
+
if (usage)
|
|
63
|
+
return usage;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function normalize(promptTokens, completionTokens, totalTokens) {
|
|
68
|
+
const total = totalTokens || promptTokens + completionTokens;
|
|
69
|
+
return promptTokens + completionTokens + total > 0
|
|
70
|
+
? { promptTokens, completionTokens, totalTokens: total }
|
|
71
|
+
: null;
|
|
72
|
+
}
|
|
73
|
+
function isObject(v) {
|
|
74
|
+
return typeof v === 'object' && v !== null;
|
|
75
|
+
}
|
|
76
|
+
function intOf(v) {
|
|
77
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.round(v) : 0;
|
|
78
|
+
}
|
|
79
|
+
/** 模型返回文本无法解析为 JSON 时抛出;区别于网络/服务错误,供上层决定「重试后忽略」还是「记失败」。 */
|
|
80
|
+
export class LlmParseError extends Error {
|
|
81
|
+
constructor(message) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = 'LlmParseError';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** 清洗 LLM 返回文本(去掉 ```json 围栏与首尾空白)并解析为对象;失败抛 LlmParseError。 */
|
|
87
|
+
export function parseJsonObject(text) {
|
|
88
|
+
const cleaned = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '').trim();
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(cleaned);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new LlmParseError(`LLM 返回无法解析为 JSON:${text.slice(0, 200)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** mock 实现:按注入的回调返回固定对象,不发起任何网络请求。 */
|
|
97
|
+
export class MockLlm {
|
|
98
|
+
responder;
|
|
99
|
+
constructor(responder) {
|
|
100
|
+
this.responder = responder;
|
|
101
|
+
}
|
|
102
|
+
async generate(prompt, schema, task) {
|
|
103
|
+
return (await this.responder(prompt, schema, task));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../../src/core/lessons/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MemoryCandidate } from './extract.js';
|
|
2
|
+
import type { LessonProviderDeps, ExtractionOutcome } from './learn.js';
|
|
3
|
+
/** 纯查表:Claude Code 记忆 type 映射到 trove type,未知 type 返回 undefined。 */
|
|
4
|
+
export declare function mapClaudeMemoType(claudeType: string): string | undefined;
|
|
5
|
+
export interface ClaudeMemo {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
type: string;
|
|
9
|
+
/** frontmatter 之后的正文(已去首尾空白)。 */
|
|
10
|
+
body: string;
|
|
11
|
+
}
|
|
12
|
+
/** 解析 Claude Code 记忆文件的 YAML frontmatter。兼容两种变体:顶层 `type:` 或 `metadata:` 块下的 `type:`。 */
|
|
13
|
+
export declare function parseClaudeMemo(text: string): ClaudeMemo | null;
|
|
14
|
+
/** 遍历 claudeRoot/projects/<项目>/memory 下的 .md 记忆文件,解析并映射为候选;排除各项目的 MEMORY.md 索引。 */
|
|
15
|
+
export declare function collectClaudeMemos(claudeRoot: string): Promise<MemoryCandidate[]>;
|
|
16
|
+
/** memo 渠道编排:幂等键 audit/memo-claude-code.json。只构造 LLM(merge 的 patch/conflict 用),不构造 jev。 */
|
|
17
|
+
export declare function learnClaudeMemo(vaultRoot: string, claudeRoot: string, deps?: LessonProviderDeps, opts?: {
|
|
18
|
+
force?: boolean;
|
|
19
|
+
}): Promise<ExtractionOutcome>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/** 「memo 渠道」:学习 Claude Code 自带记忆系统(~/.claude/projects/<项目>/memory/*.md)。
|
|
2
|
+
* 与「会话渠道」(graph.ts)并行:记忆本身已是「标题+正文+明确 type」的结构化内容,
|
|
3
|
+
* 不归档、不走 select/analyze/extract,直接映射为 MemoryCandidate 复用 merge/write/audit 落盘。 */
|
|
4
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { libraryLessonsDir } from '../layout.js';
|
|
7
|
+
import { ensureLessonsLayout } from './layout.js';
|
|
8
|
+
import { LocalStorageAdapter } from './storage.js';
|
|
9
|
+
import { readLlmConfig } from './config.js';
|
|
10
|
+
import { DeepSeekLlm } from './llm.js';
|
|
11
|
+
import { loadExistingMemories } from './existing.js';
|
|
12
|
+
import { mergeMemories } from './merge.js';
|
|
13
|
+
import { applyOperations } from './write.js';
|
|
14
|
+
import { buildMemoryDiff, writeMemoryDiff } from './audit.js';
|
|
15
|
+
import { getMemoryType, slugify } from './schema.js';
|
|
16
|
+
/** Claude Code 记忆 type → trove 17 类 type。映射表硬编码;project→gotcha 是近似兜底,要调再抽配置。 */
|
|
17
|
+
const CLAUDE_MEMO_TYPE_MAP = {
|
|
18
|
+
user: 'preferences/profile',
|
|
19
|
+
feedback: 'experiences/correction',
|
|
20
|
+
project: 'experiences/gotcha',
|
|
21
|
+
reference: 'resources/doc',
|
|
22
|
+
};
|
|
23
|
+
/** 纯查表:Claude Code 记忆 type 映射到 trove type,未知 type 返回 undefined。 */
|
|
24
|
+
export function mapClaudeMemoType(claudeType) {
|
|
25
|
+
return CLAUDE_MEMO_TYPE_MAP[claudeType];
|
|
26
|
+
}
|
|
27
|
+
/** 解析 Claude Code 记忆文件的 YAML frontmatter。兼容两种变体:顶层 `type:` 或 `metadata:` 块下的 `type:`。 */
|
|
28
|
+
export function parseClaudeMemo(text) {
|
|
29
|
+
if (!text.startsWith('---'))
|
|
30
|
+
return null;
|
|
31
|
+
const close = text.indexOf('\n---', 4);
|
|
32
|
+
if (close === -1)
|
|
33
|
+
return null;
|
|
34
|
+
const fm = text.slice(4, close);
|
|
35
|
+
const body = text.slice(close + 4).trim();
|
|
36
|
+
let name = '';
|
|
37
|
+
let description = '';
|
|
38
|
+
let type = '';
|
|
39
|
+
let inMetadata = false;
|
|
40
|
+
for (const line of fm.split('\n')) {
|
|
41
|
+
if (inMetadata) {
|
|
42
|
+
// metadata 子块:缩进的 type 优先取顶层(顶层已有时不覆盖)
|
|
43
|
+
const m = /^\s+type\s*:\s*(.*)$/.exec(line);
|
|
44
|
+
if (m && !type)
|
|
45
|
+
type = m[1].trim();
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (/^metadata\s*:/.test(line)) {
|
|
49
|
+
inMetadata = true;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const m = /^(name|description|type)\s*:\s*(.*)$/.exec(line);
|
|
53
|
+
if (!m)
|
|
54
|
+
continue;
|
|
55
|
+
const val = m[2].trim();
|
|
56
|
+
if (m[1] === 'name')
|
|
57
|
+
name = val;
|
|
58
|
+
else if (m[1] === 'description')
|
|
59
|
+
description = val;
|
|
60
|
+
else if (m[1] === 'type')
|
|
61
|
+
type = val;
|
|
62
|
+
}
|
|
63
|
+
if (!type)
|
|
64
|
+
return null;
|
|
65
|
+
return { name, description, type, body };
|
|
66
|
+
}
|
|
67
|
+
/** 遍历 claudeRoot/projects/<项目>/memory 下的 .md 记忆文件,解析并映射为候选;排除各项目的 MEMORY.md 索引。 */
|
|
68
|
+
export async function collectClaudeMemos(claudeRoot) {
|
|
69
|
+
const projectsRoot = join(claudeRoot, 'projects');
|
|
70
|
+
let projects = [];
|
|
71
|
+
try {
|
|
72
|
+
projects = (await readdir(projectsRoot, { withFileTypes: true }))
|
|
73
|
+
.filter((e) => e.isDirectory())
|
|
74
|
+
.map((e) => e.name);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
const candidates = [];
|
|
80
|
+
for (const project of projects) {
|
|
81
|
+
const memDir = join(projectsRoot, project, 'memory');
|
|
82
|
+
let files = [];
|
|
83
|
+
try {
|
|
84
|
+
files = await readdir(memDir);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const f of files) {
|
|
90
|
+
if (f === 'MEMORY.md' || !f.endsWith('.md'))
|
|
91
|
+
continue;
|
|
92
|
+
let text;
|
|
93
|
+
try {
|
|
94
|
+
text = await readFile(join(memDir, f), 'utf8');
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const memo = parseClaudeMemo(text);
|
|
100
|
+
if (!memo)
|
|
101
|
+
continue;
|
|
102
|
+
const troveType = mapClaudeMemoType(memo.type);
|
|
103
|
+
if (!troveType)
|
|
104
|
+
continue;
|
|
105
|
+
const title = (memo.description || memo.name).trim();
|
|
106
|
+
if (!title || !memo.body.trim())
|
|
107
|
+
continue;
|
|
108
|
+
candidates.push({
|
|
109
|
+
type: troveType,
|
|
110
|
+
title,
|
|
111
|
+
content: memo.body.trim(),
|
|
112
|
+
slug: slugify(memo.name || title),
|
|
113
|
+
evidence: [],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return mergeFixedPatchCandidates(candidates);
|
|
118
|
+
}
|
|
119
|
+
/** patch 到「固定文件名(无占位符)」的同 type 多条合并为一条:merge 层基于同一旧 existing 逐条 patch 会互相覆盖。 */
|
|
120
|
+
function mergeFixedPatchCandidates(candidates) {
|
|
121
|
+
const groups = new Map();
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const c of candidates) {
|
|
124
|
+
const schema = getMemoryType(c.type);
|
|
125
|
+
if (schema?.mergeOp === 'patch' && !schema.filename.includes('{')) {
|
|
126
|
+
const g = groups.get(c.type);
|
|
127
|
+
if (g)
|
|
128
|
+
g.push(c);
|
|
129
|
+
else
|
|
130
|
+
groups.set(c.type, [c]);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
out.push(c);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const group of groups.values()) {
|
|
137
|
+
if (group.length === 1) {
|
|
138
|
+
out.push(group[0]);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
out.push({
|
|
142
|
+
type: group[0].type,
|
|
143
|
+
title: group[0].title,
|
|
144
|
+
content: group.map((g) => g.content).join('\n\n'),
|
|
145
|
+
slug: group[0].slug,
|
|
146
|
+
evidence: [],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
/** memo 渠道编排:幂等键 audit/memo-claude-code.json。只构造 LLM(merge 的 patch/conflict 用),不构造 jev。 */
|
|
153
|
+
export async function learnClaudeMemo(vaultRoot, claudeRoot, deps = {}, opts = {}) {
|
|
154
|
+
await ensureLessonsLayout(vaultRoot);
|
|
155
|
+
const storage = new LocalStorageAdapter(libraryLessonsDir(vaultRoot));
|
|
156
|
+
if (!opts.force && (await storage.exists('audit/memo-claude-code.json'))) {
|
|
157
|
+
return { skipReason: 'already_learned' };
|
|
158
|
+
}
|
|
159
|
+
const candidates = await collectClaudeMemos(claudeRoot);
|
|
160
|
+
if (candidates.length === 0)
|
|
161
|
+
return { skipReason: 'no_memo' };
|
|
162
|
+
const llm = deps.llm ?? new DeepSeekLlm(readLlmConfig(deps.config));
|
|
163
|
+
const existing = await loadExistingMemories(storage);
|
|
164
|
+
const ops = await mergeMemories(candidates, existing, { llm });
|
|
165
|
+
await applyOperations(ops, { storage, now: deps.now, source: 'memo-claude-code' });
|
|
166
|
+
const diff = buildMemoryDiff(ops, { sessionId: 'memo-claude-code', project: 'claude-code' });
|
|
167
|
+
await writeMemoryDiff(diff, storage);
|
|
168
|
+
return { diff };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=memo.js.map
|