@coralai/sps-cli 0.58.14 → 0.58.16

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 (152) hide show
  1. package/README.md +2 -14
  2. package/dist/commands/agentCommand.d.ts.map +1 -1
  3. package/dist/commands/agentCommand.js +29 -26
  4. package/dist/commands/agentCommand.js.map +1 -1
  5. package/dist/commands/memoryCommand.d.ts +2 -0
  6. package/dist/commands/memoryCommand.d.ts.map +1 -0
  7. package/dist/commands/memoryCommand.js +263 -0
  8. package/dist/commands/memoryCommand.js.map +1 -0
  9. package/dist/commands/projectInit.d.ts.map +1 -1
  10. package/dist/commands/projectInit.js +21 -0
  11. package/dist/commands/projectInit.js.map +1 -1
  12. package/dist/commands/tick.d.ts.map +1 -1
  13. package/dist/commands/tick.js +1 -19
  14. package/dist/commands/tick.js.map +1 -1
  15. package/dist/console/index.d.ts.map +1 -1
  16. package/dist/console/index.js +2 -0
  17. package/dist/console/index.js.map +1 -1
  18. package/dist/console/routes/memory.d.ts +11 -0
  19. package/dist/console/routes/memory.d.ts.map +1 -0
  20. package/dist/console/routes/memory.js +63 -0
  21. package/dist/console/routes/memory.js.map +1 -0
  22. package/dist/console/routes/system.d.ts.map +1 -1
  23. package/dist/console/routes/system.js +6 -3
  24. package/dist/console/routes/system.js.map +1 -1
  25. package/dist/console-assets/assets/index-DENsMsFy.css +10 -0
  26. package/dist/console-assets/assets/{index-BiGbp-FL.js → index-R6Y8wve3.js} +194 -194
  27. package/dist/console-assets/index.html +2 -2
  28. package/dist/core/agents/AcpBackend.d.ts +2 -0
  29. package/dist/core/agents/AcpBackend.d.ts.map +1 -1
  30. package/dist/core/agents/AcpBackend.js +5 -0
  31. package/dist/core/agents/AcpBackend.js.map +1 -1
  32. package/dist/core/agents/ClaudeSdkBackend.d.ts +0 -1
  33. package/dist/core/agents/ClaudeSdkBackend.d.ts.map +1 -1
  34. package/dist/core/agents/ClaudeSdkBackend.js +1 -1
  35. package/dist/core/agents/ClaudeSdkBackend.js.map +1 -1
  36. package/dist/core/agents/CodexAppServerBackend.d.ts +0 -1
  37. package/dist/core/agents/CodexAppServerBackend.d.ts.map +1 -1
  38. package/dist/core/agents/CodexAppServerBackend.js +1 -1
  39. package/dist/core/agents/CodexAppServerBackend.js.map +1 -1
  40. package/dist/core/agents/OpenAiAgentBackend.d.ts +0 -2
  41. package/dist/core/agents/OpenAiAgentBackend.d.ts.map +1 -1
  42. package/dist/core/agents/OpenAiAgentBackend.js +5 -4
  43. package/dist/core/agents/OpenAiAgentBackend.js.map +1 -1
  44. package/dist/core/agents/resolveBackend.d.ts +0 -1
  45. package/dist/core/agents/resolveBackend.d.ts.map +1 -1
  46. package/dist/core/agents/resolveBackend.js +3 -4
  47. package/dist/core/agents/resolveBackend.js.map +1 -1
  48. package/dist/core/codegraph/claudeMdRule.d.ts +7 -0
  49. package/dist/core/codegraph/claudeMdRule.d.ts.map +1 -0
  50. package/dist/core/codegraph/claudeMdRule.js +79 -0
  51. package/dist/core/codegraph/claudeMdRule.js.map +1 -0
  52. package/dist/core/memory2/capture.d.ts +33 -0
  53. package/dist/core/memory2/capture.d.ts.map +1 -0
  54. package/dist/core/memory2/capture.js +102 -0
  55. package/dist/core/memory2/capture.js.map +1 -0
  56. package/dist/core/memory2/claudeMdRule.d.ts +7 -0
  57. package/dist/core/memory2/claudeMdRule.d.ts.map +1 -0
  58. package/dist/core/memory2/claudeMdRule.js +65 -0
  59. package/dist/core/memory2/claudeMdRule.js.map +1 -0
  60. package/dist/core/memory2/config.d.ts +20 -0
  61. package/dist/core/memory2/config.d.ts.map +1 -0
  62. package/dist/core/memory2/config.js +26 -0
  63. package/dist/core/memory2/config.js.map +1 -0
  64. package/dist/core/memory2/digest.d.ts +16 -0
  65. package/dist/core/memory2/digest.d.ts.map +1 -0
  66. package/dist/core/memory2/digest.js +52 -0
  67. package/dist/core/memory2/digest.js.map +1 -0
  68. package/dist/core/memory2/gc.d.ts +9 -0
  69. package/dist/core/memory2/gc.d.ts.map +1 -0
  70. package/dist/core/memory2/gc.js +45 -0
  71. package/dist/core/memory2/gc.js.map +1 -0
  72. package/dist/core/memory2/inject.d.ts +16 -0
  73. package/dist/core/memory2/inject.d.ts.map +1 -0
  74. package/dist/core/memory2/inject.js +45 -0
  75. package/dist/core/memory2/inject.js.map +1 -0
  76. package/dist/core/memory2/lock.d.ts +7 -0
  77. package/dist/core/memory2/lock.d.ts.map +1 -0
  78. package/dist/core/memory2/lock.js +59 -0
  79. package/dist/core/memory2/lock.js.map +1 -0
  80. package/dist/core/memory2/migrate.d.ts +12 -0
  81. package/dist/core/memory2/migrate.d.ts.map +1 -0
  82. package/dist/core/memory2/migrate.js +119 -0
  83. package/dist/core/memory2/migrate.js.map +1 -0
  84. package/dist/core/memory2/redact.d.ts +21 -0
  85. package/dist/core/memory2/redact.d.ts.map +1 -0
  86. package/dist/core/memory2/redact.js +116 -0
  87. package/dist/core/memory2/redact.js.map +1 -0
  88. package/dist/core/memory2/retrieve.d.ts +16 -0
  89. package/dist/core/memory2/retrieve.d.ts.map +1 -0
  90. package/dist/core/memory2/retrieve.js +70 -0
  91. package/dist/core/memory2/retrieve.js.map +1 -0
  92. package/dist/core/memory2/store.d.ts +30 -0
  93. package/dist/core/memory2/store.d.ts.map +1 -0
  94. package/dist/core/memory2/store.js +217 -0
  95. package/dist/core/memory2/store.js.map +1 -0
  96. package/dist/core/memory2/types.d.ts +53 -0
  97. package/dist/core/memory2/types.d.ts.map +1 -0
  98. package/dist/core/memory2/types.js +12 -0
  99. package/dist/core/memory2/types.js.map +1 -0
  100. package/dist/engines/StageEngine.d.ts.map +1 -1
  101. package/dist/engines/StageEngine.js +14 -8
  102. package/dist/engines/StageEngine.js.map +1 -1
  103. package/dist/main.js +17 -154
  104. package/dist/main.js.map +1 -1
  105. package/dist/manager/worker-manager-impl.d.ts +10 -0
  106. package/dist/manager/worker-manager-impl.d.ts.map +1 -1
  107. package/dist/manager/worker-manager-impl.js +80 -5
  108. package/dist/manager/worker-manager-impl.js.map +1 -1
  109. package/dist/providers/llm/workerTools.js +1 -1
  110. package/dist/providers/mcp/spsMcpServer.d.ts +13 -7
  111. package/dist/providers/mcp/spsMcpServer.d.ts.map +1 -1
  112. package/dist/providers/mcp/spsMcpServer.js +90 -28
  113. package/dist/providers/mcp/spsMcpServer.js.map +1 -1
  114. package/dist/providers/mcp/spsMcpStdio.js +3 -15
  115. package/dist/providers/mcp/spsMcpStdio.js.map +1 -1
  116. package/dist/providers/mcp/spsMcpStdioConfig.d.ts +2 -1
  117. package/dist/providers/mcp/spsMcpStdioConfig.d.ts.map +1 -1
  118. package/dist/providers/mcp/spsMcpStdioConfig.js +3 -1
  119. package/dist/providers/mcp/spsMcpStdioConfig.js.map +1 -1
  120. package/dist/services/MemoryService.d.ts +69 -0
  121. package/dist/services/MemoryService.d.ts.map +1 -0
  122. package/dist/services/MemoryService.js +178 -0
  123. package/dist/services/MemoryService.js.map +1 -0
  124. package/dist/services/SystemService.d.ts +17 -3
  125. package/dist/services/SystemService.d.ts.map +1 -1
  126. package/dist/services/SystemService.js +75 -17
  127. package/dist/services/SystemService.js.map +1 -1
  128. package/dist/services/container.d.ts +2 -0
  129. package/dist/services/container.d.ts.map +1 -1
  130. package/dist/services/container.js +2 -0
  131. package/dist/services/container.js.map +1 -1
  132. package/package.json +1 -2
  133. package/project-template/.claude/CLAUDE.md +0 -23
  134. package/skills/sps-pipeline/SKILL.md +12 -15
  135. package/dist/console-assets/assets/index-BHtTCxMY.css +0 -10
  136. package/dist/core/memory.d.ts +0 -85
  137. package/dist/core/memory.d.ts.map +0 -1
  138. package/dist/core/memory.js +0 -374
  139. package/dist/core/memory.js.map +0 -1
  140. package/dist/core/memoryIngest.d.ts +0 -18
  141. package/dist/core/memoryIngest.d.ts.map +0 -1
  142. package/dist/core/memoryIngest.js +0 -56
  143. package/dist/core/memoryIngest.js.map +0 -1
  144. package/dist/core/memoryProvider.d.ts +0 -63
  145. package/dist/core/memoryProvider.d.ts.map +0 -1
  146. package/dist/core/memoryProvider.js +0 -205
  147. package/dist/core/memoryProvider.js.map +0 -1
  148. package/dist/core/transcriptIngest.d.ts +0 -37
  149. package/dist/core/transcriptIngest.d.ts.map +0 -1
  150. package/dist/core/transcriptIngest.js +0 -137
  151. package/dist/core/transcriptIngest.js.map +0 -1
  152. package/skills/sps-memory/SKILL.md +0 -51
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @module core/memory2/claudeMdRule
3
+ * @description 往项目 `.claude/CLAUDE.md` 幂等注入「本地记忆(memory_*)」托管块。与 codegraph
4
+ * 同机制:只管理 START..END 之间;用户内容原样保留;不存在→追加、已存在→更新、
5
+ * 无文件→新建。规则告诉 agent:开工先 memory_recall,收尾把可复用结论 memory_save。
6
+ * @layer core
7
+ * @boundedContext memory
8
+ */
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
+ import { dirname, join } from 'node:path';
11
+ const START = '<!-- sps:memory:start — auto-managed by sps (regenerated on init/doctor); edit outside these markers -->';
12
+ const END = '<!-- sps:memory:end -->';
13
+ /** 块内正文(不含标记)。改规则改这里,重跑 init/doctor 即更新到所有项目。 */
14
+ const RULE = `## Project memory (recall past decisions, record new ones)
15
+
16
+ This project has a **local memory** — a small store of conventions, decisions,
17
+ gotchas, and patterns learned on earlier tasks. A digest is injected at the top of
18
+ your prompt inside a \`<memory>\` block; use these tools for the rest.
19
+
20
+ **When to use it:**
21
+
22
+ 1. **At task start — recall.** Before designing, call \`memory_recall\` with the
23
+ key terms of the card to pull anything relevant that isn't already in the
24
+ injected digest (project + global scope). Use \`memory_read <id>\` for full text.
25
+ 2. **When you settle something reusable — save.** After you establish a convention,
26
+ make a non-obvious decision, or hit a gotcha worth not repeating, call
27
+ \`memory_save\` with a short \`title\` + concise \`body\`. Pick a \`category\`
28
+ (convention/decision/gotcha/pattern/reference/note). Default scope is this
29
+ project; only pass \`scope: "global"\` for something true across all projects.
30
+
31
+ If the \`memory_*\` tools aren't available in your session, the same operations
32
+ are on the shell: \`sps memory recall <project> "<query>"\` and
33
+ \`sps memory save <project> --title "..." --body "..."\`.
34
+
35
+ **Don't save:** secrets/tokens/credentials, deploy addresses, customer data, or
36
+ anything the code/git history already records. Keep entries short and durable.`;
37
+ function escapeRegex(s) {
38
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
39
+ }
40
+ /**
41
+ * 幂等确保 `<repoDir>/.claude/CLAUDE.md` 含记忆托管块。
42
+ * @returns created=新建文件 / appended=追加块 / updated=更新块 / unchanged=已最新。
43
+ */
44
+ export function ensureMemoryRuleBlock(repoDir) {
45
+ const claudeMd = join(repoDir, '.claude', 'CLAUDE.md');
46
+ const block = `${START}\n\n${RULE}\n\n${END}`;
47
+ if (!existsSync(claudeMd)) {
48
+ mkdirSync(dirname(claudeMd), { recursive: true });
49
+ writeFileSync(claudeMd, `# Project Rules (Claude Code Worker)\n\n${block}\n`);
50
+ return 'created';
51
+ }
52
+ const content = readFileSync(claudeMd, 'utf-8');
53
+ const re = new RegExp(`${escapeRegex(START)}[\\s\\S]*?${escapeRegex(END)}`);
54
+ if (re.test(content)) {
55
+ const next = content.replace(re, block);
56
+ if (next === content)
57
+ return 'unchanged';
58
+ writeFileSync(claudeMd, next);
59
+ return 'updated';
60
+ }
61
+ const sep = content.endsWith('\n') ? '\n' : '\n\n';
62
+ writeFileSync(claudeMd, `${content}${sep}${block}\n`);
63
+ return 'appended';
64
+ }
65
+ //# sourceMappingURL=claudeMdRule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeMdRule.js","sourceRoot":"","sources":["../../../src/core/memory2/claudeMdRule.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,KAAK,GAAG,0GAA0G,CAAC;AACzH,MAAM,GAAG,GAAG,yBAAyB,CAAC;AAEtC,iDAAiD;AACjD,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;+EAsBkE,CAAC;AAEhF,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAID;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,GAAG,KAAK,OAAO,IAAI,OAAO,GAAG,EAAE,CAAC;IAE9C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,aAAa,CAAC,QAAQ,EAAE,2CAA2C,KAAK,IAAI,CAAC,CAAC;QAC9E,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,aAAa,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5E,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACxC,IAAI,IAAI,KAAK,OAAO;YAAE,OAAO,WAAW,CAAC;QACzC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACnD,aAAa,CAAC,QAAQ,EAAE,GAAG,OAAO,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @module core/memory2/config
3
+ * @description 记忆 v2 运行模式(设计 §8)。SPS_MEMORY_MODE = local(默认)| local+external |
4
+ * external | none。此处只做「本地读写是否启用」这个**当前真需要**的判定:mode=none
5
+ * → 本地注入/捕获全关。
6
+ *
7
+ * ⚠️ 刻意未实现(YAGNI,设计 §4/§5 明列「按需、默认关、需要才建」):
8
+ * - external / local+external 的外部后端适配器与合并(RRF)——无已接外部后端、无 scale 压力;
9
+ * - FTS5 加速(仅 store 很大且 node:sqlite 可用时才值得);
10
+ * - embedding 向量混检、LLM 蒸馏。
11
+ * 需求出现时,在此扩 `memoryMode()` 的分支 + 加一层可选适配器即可,基线纯本地永远能跑。
12
+ * @layer core
13
+ * @boundedContext memory
14
+ */
15
+ export type MemoryMode = 'local' | 'local+external' | 'external' | 'none';
16
+ /** 读运行模式。未配置/非法值 → 'local'。 */
17
+ export declare function memoryMode(): MemoryMode;
18
+ /** 记忆整体是否关闭(mode=none):关则本地注入与捕获都不做。 */
19
+ export declare function isMemoryOff(): boolean;
20
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/core/memory2/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1E,+BAA+B;AAC/B,wBAAgB,UAAU,IAAI,UAAU,CAIvC;AAED,wCAAwC;AACxC,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @module core/memory2/config
3
+ * @description 记忆 v2 运行模式(设计 §8)。SPS_MEMORY_MODE = local(默认)| local+external |
4
+ * external | none。此处只做「本地读写是否启用」这个**当前真需要**的判定:mode=none
5
+ * → 本地注入/捕获全关。
6
+ *
7
+ * ⚠️ 刻意未实现(YAGNI,设计 §4/§5 明列「按需、默认关、需要才建」):
8
+ * - external / local+external 的外部后端适配器与合并(RRF)——无已接外部后端、无 scale 压力;
9
+ * - FTS5 加速(仅 store 很大且 node:sqlite 可用时才值得);
10
+ * - embedding 向量混检、LLM 蒸馏。
11
+ * 需求出现时,在此扩 `memoryMode()` 的分支 + 加一层可选适配器即可,基线纯本地永远能跑。
12
+ * @layer core
13
+ * @boundedContext memory
14
+ */
15
+ /** 读运行模式。未配置/非法值 → 'local'。 */
16
+ export function memoryMode() {
17
+ const raw = (process.env.SPS_MEMORY_MODE ?? '').toLowerCase();
18
+ if (raw === 'none' || raw === 'external' || raw === 'local+external')
19
+ return raw;
20
+ return 'local';
21
+ }
22
+ /** 记忆整体是否关闭(mode=none):关则本地注入与捕获都不做。 */
23
+ export function isMemoryOff() {
24
+ return memoryMode() === 'none';
25
+ }
26
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/core/memory2/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,+BAA+B;AAC/B,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,gBAAgB;QAAE,OAAO,GAAG,CAAC;IACjF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,EAAE,KAAK,MAAM,CAAC;AACjC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { MemoryEntry, ScopeRef } from './types.js';
2
+ export interface DigestOpts {
3
+ /** 标题清单最多列几条(默认 60)。 */
4
+ maxTitles?: number;
5
+ /** 内联几条高 salience 正文(默认 6)。 */
6
+ maxBodies?: number;
7
+ /** 每条正文最多字符(默认 500)。 */
8
+ bodyChars?: number;
9
+ }
10
+ /** 生成 digest markdown(标题清单 + top 高 salience 正文)。 */
11
+ export declare function buildDigest(entries: MemoryEntry[], opts?: DigestOpts): string;
12
+ /** 把某作用域的 digest 落成 <scope>/MEMORY.md(派生,可重建)。 */
13
+ export declare function writeMemoryIndex(ref: ScopeRef): void;
14
+ /** 供注入用:多作用域合并的紧凑 digest(标题清单;正文靠相关性注入另给)。 */
15
+ export declare function buildInjectionDigest(refs: ScopeRef[], opts?: DigestOpts): string;
16
+ //# sourceMappingURL=digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../../src/core/memory2/digest.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,IAAI,GAAE,UAAe,GAAG,MAAM,CA0BjF;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAKpD;AAED,8CAA8C;AAC9C,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,GAAE,UAAe,GAAG,MAAM,CAGpF"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @module core/memory2/digest
3
+ * @description 从 entries 派生「常驻摘要 digest」(设计 §1B 渐进式披露):标题清单 +
4
+ * 高 salience 条目正文,有 token 预算。digest 每次派活/开会话必注入;
5
+ * 也落成 <scope>/MEMORY.md(人类可读、派生、勿手改)。
6
+ * @layer core
7
+ * @boundedContext memory
8
+ */
9
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { scopeDir } from './store.js';
12
+ import { listEntries } from './store.js';
13
+ /** 生成 digest markdown(标题清单 + top 高 salience 正文)。 */
14
+ export function buildDigest(entries, opts = {}) {
15
+ const maxTitles = opts.maxTitles ?? 60;
16
+ const maxBodies = opts.maxBodies ?? 6;
17
+ const bodyChars = opts.bodyChars ?? 500;
18
+ if (entries.length === 0)
19
+ return '';
20
+ // salience 高→低,再新鲜度
21
+ const ranked = entries.slice().sort((a, b) => b.salience - a.salience || b.updated.localeCompare(a.updated));
22
+ const lines = ['# 记忆摘要', ''];
23
+ lines.push('## 索引');
24
+ for (const e of ranked.slice(0, maxTitles)) {
25
+ const tags = e.tags.length ? ` #${e.tags.join(' #')}` : '';
26
+ lines.push(`- [${e.id}] (${e.category}, s${e.salience}) ${e.title}${tags}`);
27
+ }
28
+ if (ranked.length > maxTitles)
29
+ lines.push(`- …还有 ${ranked.length - maxTitles} 条(用 memory_recall 检索)`);
30
+ const bodies = ranked.slice(0, maxBodies);
31
+ if (bodies.length) {
32
+ lines.push('', '## 高显著度');
33
+ for (const e of bodies) {
34
+ const body = e.body.length > bodyChars ? `${e.body.slice(0, bodyChars)}…` : e.body;
35
+ lines.push('', `### ${e.title}`, body);
36
+ }
37
+ }
38
+ return lines.join('\n').trim();
39
+ }
40
+ /** 把某作用域的 digest 落成 <scope>/MEMORY.md(派生,可重建)。 */
41
+ export function writeMemoryIndex(ref) {
42
+ const dir = scopeDir(ref);
43
+ mkdirSync(dir, { recursive: true });
44
+ const digest = buildDigest(listEntries(ref));
45
+ writeFileSync(join(dir, 'MEMORY.md'), digest ? `${digest}\n` : '');
46
+ }
47
+ /** 供注入用:多作用域合并的紧凑 digest(标题清单;正文靠相关性注入另给)。 */
48
+ export function buildInjectionDigest(refs, opts = {}) {
49
+ const all = refs.flatMap((r) => (existsSync(scopeDir(r)) ? listEntries(r) : []));
50
+ return buildDigest(all, { maxBodies: 0, ...opts });
51
+ }
52
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../src/core/memory2/digest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { ScopeRef } from './types.js';
2
+ export interface GcStats {
3
+ purged: number;
4
+ deduped: number;
5
+ kept: number;
6
+ }
7
+ /** 对单作用域做 gc(持锁)。返回统计。 */
8
+ export declare function gcScope(ref: ScopeRef): GcStats;
9
+ //# sourceMappingURL=gc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../../src/core/memory2/gc.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAUD,0BAA0B;AAC1B,wBAAgB,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAwB9C"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @module core/memory2/gc
3
+ * @description 记忆收敛(设计 §6 · §5):硬删 tombstone + 去重收敛(正文归一化相同 → 留最优、软删其余)。
4
+ * 变更类操作,持作用域锁串行(withScopeLock)避免读-判-删竞态。衰减暂不做(需调参,
5
+ * 避免过度设计;留待引入使用频次统计后再定)。
6
+ * @layer core
7
+ * @boundedContext memory
8
+ */
9
+ import { normalizeForDedup } from './capture.js';
10
+ import { writeMemoryIndex } from './digest.js';
11
+ import { withScopeLock } from './lock.js';
12
+ import { deleteEntry, listEntries, purgeTombstones } from './store.js';
13
+ /** 挑一组重复里保留哪条:salience 高优先,再 updated 新,再 human>agent>capture。 */
14
+ function pickBest(group) {
15
+ const rank = (s) => (s === 'human' ? 2 : s === 'agent' ? 1 : 0);
16
+ return group.slice().sort((a, b) => b.salience - a.salience || b.updated.localeCompare(a.updated) || rank(b.source) - rank(a.source))[0];
17
+ }
18
+ /** 对单作用域做 gc(持锁)。返回统计。 */
19
+ export function gcScope(ref) {
20
+ return withScopeLock(ref, () => {
21
+ const purged = purgeTombstones(ref);
22
+ const entries = listEntries(ref);
23
+ const groups = new Map();
24
+ for (const e of entries) {
25
+ const k = normalizeForDedup(e.body);
26
+ if (!k)
27
+ continue; // 空正文不参与去重
28
+ (groups.get(k) ?? groups.set(k, []).get(k)).push(e);
29
+ }
30
+ let deduped = 0;
31
+ for (const group of groups.values()) {
32
+ if (group.length < 2)
33
+ continue;
34
+ const keep = pickBest(group);
35
+ for (const e of group) {
36
+ if (e.id !== keep.id && deleteEntry(ref, e.id))
37
+ deduped++;
38
+ }
39
+ }
40
+ if (deduped > 0 || purged > 0)
41
+ writeMemoryIndex(ref);
42
+ return { purged, deduped, kept: listEntries(ref).length };
43
+ });
44
+ }
45
+ //# sourceMappingURL=gc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.js","sourceRoot":"","sources":["../../../src/core/memory2/gc.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AASvE,iEAAiE;AACjE,SAAS,QAAQ,CAAC,KAAoB;IACpC,MAAM,IAAI,GAAG,CAAC,CAAwB,EAAU,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACjC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CACjG,CAAC,CAAC,CAAE,CAAC;AACR,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,OAAO,CAAC,GAAa;IACnC,OAAO,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE;QAC7B,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QAEpC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;QAChD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC,CAAC;gBAAE,SAAS,CAAC,WAAW;YAC7B,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAC/B,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC7B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,IAAI,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;oBAAE,OAAO,EAAE,CAAC;YAC5D,CAAC;QACH,CAAC;QAED,IAAI,OAAO,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC;YAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { ScopeRef } from './types.js';
2
+ export interface InjectOpts {
3
+ /** 相关正文注入几条(默认 5)。 */
4
+ topK?: number;
5
+ /** 每条正文最多字符(默认 400)。 */
6
+ bodyChars?: number;
7
+ /** digest 标题清单上限(默认 40)。 */
8
+ maxTitles?: number;
9
+ }
10
+ /**
11
+ * 拼 `<memory>` 注入块。无任何记忆时返回 ''(派活方据此决定是否 prepend)。
12
+ * @param refs 作用域集(worker: [project, global];harness: [agent, global])。
13
+ * @param query 当前任务文本(卡片 prompt / 首轮消息),用于相关性 top-K;空则只出 digest。
14
+ */
15
+ export declare function buildMemoryInjection(refs: ScopeRef[], query: string, opts?: InjectOpts): string;
16
+ //# sourceMappingURL=inject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../../../src/core/memory2/inject.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,sBAAsB;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,MAAM,CAuBnG"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @module core/memory2/inject
3
+ * @description 读路注入(设计 §3):派活/开会话前把记忆拼成 `<memory>` 块 = 常驻 digest
4
+ * (标题清单)+ 按当前任务文本相关性检索的 top-K 正文。worker 走 project+global,
5
+ * harness 走 agent+global。agent 无关——只产出文本,由派活方 prepend 进 prompt。
6
+ * @layer core
7
+ * @boundedContext memory
8
+ */
9
+ import { existsSync } from 'node:fs';
10
+ import { isMemoryOff } from './config.js';
11
+ import { buildInjectionDigest } from './digest.js';
12
+ import { recall } from './retrieve.js';
13
+ import { scopeDir } from './store.js';
14
+ /**
15
+ * 拼 `<memory>` 注入块。无任何记忆时返回 ''(派活方据此决定是否 prepend)。
16
+ * @param refs 作用域集(worker: [project, global];harness: [agent, global])。
17
+ * @param query 当前任务文本(卡片 prompt / 首轮消息),用于相关性 top-K;空则只出 digest。
18
+ */
19
+ export function buildMemoryInjection(refs, query, opts = {}) {
20
+ if (isMemoryOff())
21
+ return ''; // mode=none:不注入
22
+ const present = refs.filter((r) => existsSync(scopeDir(r)));
23
+ if (present.length === 0)
24
+ return '';
25
+ const topK = opts.topK ?? 5;
26
+ const bodyChars = opts.bodyChars ?? 400;
27
+ const digest = buildInjectionDigest(present, { maxTitles: opts.maxTitles ?? 40 });
28
+ const hits = query.trim() ? recall(present, query, { limit: topK }) : [];
29
+ const parts = [];
30
+ if (digest)
31
+ parts.push(digest);
32
+ if (hits.length) {
33
+ const rel = ['## 相关记忆'];
34
+ for (const h of hits) {
35
+ const e = h.entry;
36
+ const body = e.body.length > bodyChars ? `${e.body.slice(0, bodyChars)}…` : e.body;
37
+ rel.push('', `### ${e.title} [${e.scope}/${e.category}]`, body);
38
+ }
39
+ parts.push(rel.join('\n'));
40
+ }
41
+ if (parts.length === 0)
42
+ return '';
43
+ return `<memory>\n${parts.join('\n\n')}\n</memory>`;
44
+ }
45
+ //# sourceMappingURL=inject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.js","sourceRoot":"","sources":["../../../src/core/memory2/inject.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAYtC;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAgB,EAAE,KAAa,EAAE,OAAmB,EAAE;IACzF,IAAI,WAAW,EAAE;QAAE,OAAO,EAAE,CAAC,CAAC,gBAAgB;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC;IACxC,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC;IAElF,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,GAAG,GAAa,CAAC,SAAS,CAAC,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;YAClB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACnF,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,GAAG,EAAE,IAAI,CAAC,CAAC;QAClE,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,aAAa,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;AACtD,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ScopeRef } from './types.js';
2
+ /**
3
+ * 持作用域锁跑 fn(同步)。拿不到锁则忙等重试至 timeoutMs;超时抛错。fn 结束(含抛错)必释放锁。
4
+ * 陈旧锁(mtime 超 STALE_MS)自动回收。
5
+ */
6
+ export declare function withScopeLock<T>(ref: ScopeRef, fn: () => T, timeoutMs?: number): T;
7
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../src/core/memory2/lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAU3C;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,SAAS,SAAQ,GAAG,CAAC,CA8BjF"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @module core/memory2/lock
3
+ * @description 作用域级互斥锁(设计 §6):变更类操作(gc/promote/migrate 的批量搬移)串行化,
4
+ * 避免"读-判-删"竞态。基于 mkdir 原子性(POSIX)——目录建成即持锁;陈旧锁按 mtime 回收。
5
+ * 创建/单条 save 走无冲突的一条一文件,**不需**此锁;仅多步读改删才用。
6
+ * @layer core
7
+ * @boundedContext memory
8
+ */
9
+ import { existsSync, mkdirSync, rmdirSync, statSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { scopeDir } from './store.js';
12
+ const STALE_MS = 60_000; // 超 60s 的锁视为陈旧(持锁进程已死),强制回收
13
+ const RETRY_MS = 50;
14
+ function sleepBusy(ms) {
15
+ const end = Date.now() + ms;
16
+ while (Date.now() < end) { /* 忙等:锁竞争窗口极短,避免引入 async 传染 */ }
17
+ }
18
+ /**
19
+ * 持作用域锁跑 fn(同步)。拿不到锁则忙等重试至 timeoutMs;超时抛错。fn 结束(含抛错)必释放锁。
20
+ * 陈旧锁(mtime 超 STALE_MS)自动回收。
21
+ */
22
+ export function withScopeLock(ref, fn, timeoutMs = 5_000) {
23
+ const dir = scopeDir(ref);
24
+ mkdirSync(dir, { recursive: true });
25
+ const lockDir = join(dir, '.lock');
26
+ const deadline = Date.now() + timeoutMs;
27
+ for (;;) {
28
+ try {
29
+ mkdirSync(lockDir); // 原子:已存在则抛 EEXIST
30
+ break;
31
+ }
32
+ catch {
33
+ // 已被占:陈旧则回收,否则重试
34
+ try {
35
+ if (Date.now() - statSync(lockDir).mtimeMs > STALE_MS) {
36
+ rmdirSync(lockDir);
37
+ continue;
38
+ }
39
+ }
40
+ catch {
41
+ continue; // 锁刚被别人释放 → 重试抢
42
+ }
43
+ if (Date.now() > deadline)
44
+ throw new Error(`memory scope lock timeout: ${dir}`);
45
+ sleepBusy(RETRY_MS);
46
+ }
47
+ }
48
+ try {
49
+ return fn();
50
+ }
51
+ finally {
52
+ try {
53
+ if (existsSync(lockDir))
54
+ rmdirSync(lockDir);
55
+ }
56
+ catch { /* best effort */ }
57
+ }
58
+ }
59
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/core/memory2/lock.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,4BAA4B;AACrD,MAAM,QAAQ,GAAG,EAAE,CAAC;AAEpB,SAAS,SAAS,CAAC,EAAU;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAC5B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,8BAA8B,CAAC,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAI,GAAa,EAAE,EAAW,EAAE,SAAS,GAAG,KAAK;IAC5E,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAExC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB;YACtC,MAAM;QACR,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;YACjB,IAAI,CAAC;gBACH,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC;oBACtD,SAAS,CAAC,OAAO,CAAC,CAAC;oBACnB,SAAS;gBACX,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS,CAAC,gBAAgB;YAC5B,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,EAAE,CAAC,CAAC;YAChF,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YAAC,IAAI,UAAU,CAAC,OAAO,CAAC;gBAAE,SAAS,CAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAClF,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ScopeRef } from './types.js';
2
+ /** 迁移一个旧作用域目录的根级 *.md(跳 MEMORY.md / entries/ 子目录)。返回迁移条数。 */
3
+ export declare function migrateOldDir(oldDir: string, ref: ScopeRef): number;
4
+ /** 迁移单个项目的旧记忆 → v2(projects/<project>/*.md → projects/<project>/entries/)。 */
5
+ export declare function migrateProject(project: string): number;
6
+ /** 迁移全部:旧 user → global;agents/<id> → agent scope;projects/<p> → project scope。 */
7
+ export declare function migrateAll(): {
8
+ global: number;
9
+ agents: number;
10
+ projects: number;
11
+ };
12
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/core/memory2/migrate.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAkB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAa3D,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,MAAM,CA8BnE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,mFAAmF;AACnF,wBAAgB,UAAU,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CA2CjF"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @module core/memory2/migrate
3
+ * @description 把旧文件记忆(~/.coral/memory/{user,agents/<id>,projects/<p>}/*.md,frontmatter
4
+ * name/description/type)迁到 v2 entry 格式,内容不丢。设计 §8。
5
+ * 旧文件在作用域目录根;v2 entry 写进 <scope>/entries/ —— 同目录不冲突。
6
+ * @layer core
7
+ * @boundedContext memory
8
+ */
9
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { parseFrontmatter } from '../flatfile/frontmatter.js';
12
+ import { writeMemoryIndex } from './digest.js';
13
+ import { memoryRoot, saveEntry, scopeDir } from './store.js';
14
+ /** 旧 type → v2 category。 */
15
+ function mapCategory(type) {
16
+ switch (type) {
17
+ case 'convention': return 'convention';
18
+ case 'decision': return 'decision';
19
+ case 'reference': return 'reference';
20
+ case 'lesson': return 'note';
21
+ default: return 'note';
22
+ }
23
+ }
24
+ /** 迁移一个旧作用域目录的根级 *.md(跳 MEMORY.md / entries/ 子目录)。返回迁移条数。 */
25
+ export function migrateOldDir(oldDir, ref) {
26
+ if (!existsSync(oldDir))
27
+ return 0;
28
+ let n = 0;
29
+ for (const name of readdirSync(oldDir)) {
30
+ if (!name.endsWith('.md') || name === 'MEMORY.md')
31
+ continue;
32
+ const p = join(oldDir, name);
33
+ try {
34
+ if (statSync(p).isDirectory())
35
+ continue;
36
+ }
37
+ catch {
38
+ continue;
39
+ }
40
+ const { data, body } = parseFrontmatter(readFileSync(p, 'utf-8'));
41
+ const title = typeof data.name === 'string' && data.name ? data.name : name.replace(/\.md$/, '');
42
+ const desc = typeof data.description === 'string' ? data.description : '';
43
+ const cat = mapCategory(data.type);
44
+ const text = body.trim() || desc || title;
45
+ try {
46
+ saveEntry(ref, {
47
+ title,
48
+ body: desc && !text.includes(desc) ? `${desc}\n\n${text}` : text,
49
+ category: cat,
50
+ salience: cat === 'convention' || cat === 'reference' ? 4 : 3,
51
+ source: 'human',
52
+ });
53
+ n++;
54
+ }
55
+ catch {
56
+ // 命中高危脱敏被丢弃 → 跳过这条,不阻断整体迁移。
57
+ }
58
+ }
59
+ return n;
60
+ }
61
+ /** 迁移单个项目的旧记忆 → v2(projects/<project>/*.md → projects/<project>/entries/)。 */
62
+ export function migrateProject(project) {
63
+ const ref = { scope: 'project', project };
64
+ const n = migrateOldDir(scopeDir(ref), ref);
65
+ if (n > 0)
66
+ writeMemoryIndex(ref);
67
+ return n;
68
+ }
69
+ /** 迁移全部:旧 user → global;agents/<id> → agent scope;projects/<p> → project scope。 */
70
+ export function migrateAll() {
71
+ const root = memoryRoot();
72
+ let global = 0;
73
+ let agents = 0;
74
+ let projects = 0;
75
+ // 旧 user → global
76
+ const userDir = join(root, 'user');
77
+ if (existsSync(userDir)) {
78
+ const ref = { scope: 'global' };
79
+ global += migrateOldDir(userDir, ref);
80
+ if (global)
81
+ writeMemoryIndex(ref);
82
+ }
83
+ // agents/<id>(旧根级 *.md → 该 agent scope entries/)
84
+ const agentsDir = join(root, 'agents');
85
+ if (existsSync(agentsDir)) {
86
+ for (const id of readdirSync(agentsDir)) {
87
+ const dir = join(agentsDir, id);
88
+ try {
89
+ if (!statSync(dir).isDirectory())
90
+ continue;
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ const ref = { scope: 'agent', agentId: id };
96
+ const c = migrateOldDir(dir, ref);
97
+ if (c)
98
+ writeMemoryIndex(ref);
99
+ agents += c;
100
+ }
101
+ }
102
+ // projects/<p>
103
+ const projectsDir = join(root, 'projects');
104
+ if (existsSync(projectsDir)) {
105
+ for (const p of readdirSync(projectsDir)) {
106
+ const dir = join(projectsDir, p);
107
+ try {
108
+ if (!statSync(dir).isDirectory())
109
+ continue;
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ projects += migrateProject(p);
115
+ }
116
+ }
117
+ return { global, agents, projects };
118
+ }
119
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/core/memory2/migrate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /** 测试用:清空追加规则缓存(改了 SPS_MEMORY_ROOT/redact.rules 后调用)。 */
2
+ export declare function resetRedactCache(): void;
3
+ export interface RedactResult {
4
+ title: string;
5
+ body: string;
6
+ /** true=命中高危,整条应丢弃(title/body 不可用)。 */
7
+ dropped: boolean;
8
+ /** 命中的规则 label(去重),供调 debug 日志——不含原文。 */
9
+ reasons: string[];
10
+ }
11
+ /**
12
+ * 对一条记忆(title+body 合起来判)做脱敏。命中 drop 规则→dropped=true;否则对命中 mask 规则的片段打码。
13
+ * SPS_MEMORY_REDACT=off 时原样返回。
14
+ */
15
+ export declare function redactEntry(title: string, body: string): RedactResult;
16
+ /** 写入被拒(命中高危):调用方据此提示"已丢弃"。 */
17
+ export declare class RedactedEntryDropped extends Error {
18
+ readonly reasons: string[];
19
+ constructor(reasons: string[]);
20
+ }
21
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../src/core/memory2/redact.ts"],"names":[],"mappings":"AAuEA,yDAAyD;AACzD,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CA+BrE;AAED,+BAA+B;AAC/B,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;gBACf,OAAO,EAAE,MAAM,EAAE;CAK9B"}