recallnest 2.3.0 → 2.5.4

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 (221) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/README.md +17 -31
  3. package/README_CN.md +12 -22
  4. package/assets/ui/app.js +1031 -0
  5. package/assets/ui/index.html +224 -0
  6. package/assets/ui/styles.css +781 -0
  7. package/config.json.example +4 -1
  8. package/docs/api-reference.md +1291 -0
  9. package/docs/architecture.md +168 -0
  10. package/docs/connector-spec.md +126 -0
  11. package/eval/cases-canary.json +58 -0
  12. package/eval/cases-scope-robustness.json +278 -0
  13. package/eval/cases.json +117 -20
  14. package/package.json +22 -1
  15. package/src/__tests__/access-tracker-novelty.test.ts +238 -0
  16. package/src/__tests__/activity-counter.test.ts +125 -0
  17. package/src/__tests__/adaptive-fidelity.test.ts +102 -0
  18. package/src/__tests__/admission-control.test.ts +206 -0
  19. package/src/__tests__/anchor-generator.test.ts +67 -0
  20. package/src/__tests__/auto-consolidation.test.ts +168 -0
  21. package/src/__tests__/auto-recall.test.ts +131 -0
  22. package/src/__tests__/babel-integration.test.ts +144 -0
  23. package/src/__tests__/batch-dedup.test.ts +65 -0
  24. package/src/__tests__/canary-eval.test.ts +114 -0
  25. package/src/__tests__/capture-engine.test.ts +1238 -0
  26. package/src/__tests__/capture-heuristic.test.ts +171 -0
  27. package/src/__tests__/cascade-forget.test.ts +210 -0
  28. package/src/__tests__/category-min-scores.test.ts +33 -0
  29. package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
  30. package/src/__tests__/circuit-breaker.test.ts +99 -0
  31. package/src/__tests__/cluster-consolidation.test.ts +782 -0
  32. package/src/__tests__/confidence-metadata.test.ts +125 -0
  33. package/src/__tests__/confidence-tracker.test.ts +261 -0
  34. package/src/__tests__/conflict-advisor.test.ts +206 -0
  35. package/src/__tests__/conflict-escalation.test.ts +149 -0
  36. package/src/__tests__/conflict-lifecycle.test.ts +83 -0
  37. package/src/__tests__/conflict-output.test.ts +85 -0
  38. package/src/__tests__/conflict-store.test.ts +374 -0
  39. package/src/__tests__/connector-v1.test.ts +181 -0
  40. package/src/__tests__/consolidation-engine.test.ts +333 -0
  41. package/src/__tests__/constructive-retrieval.test.ts +509 -0
  42. package/src/__tests__/context-collapse-renderer.test.ts +214 -0
  43. package/src/__tests__/context-composer-pins.test.ts +64 -0
  44. package/src/__tests__/context-composer-task-selection.test.ts +24 -0
  45. package/src/__tests__/context-reconstructor.test.ts +65 -0
  46. package/src/__tests__/context-renderer.test.ts +91 -0
  47. package/src/__tests__/continuity-eval.test.ts +275 -0
  48. package/src/__tests__/continuity-schema.test.ts +97 -0
  49. package/src/__tests__/contracts.test.ts +216 -0
  50. package/src/__tests__/conversation-importer.test.ts +692 -0
  51. package/src/__tests__/core-recall-eval.test.ts +235 -0
  52. package/src/__tests__/core-summary.test.ts +109 -0
  53. package/src/__tests__/data-checkup.test.ts +276 -0
  54. package/src/__tests__/decay-exemption.test.ts +84 -0
  55. package/src/__tests__/dedup-actions.test.ts +58 -0
  56. package/src/__tests__/dedup-guard.test.ts +39 -0
  57. package/src/__tests__/deterministic-id.test.ts +39 -0
  58. package/src/__tests__/distill-lock.test.ts +305 -0
  59. package/src/__tests__/distill-session-argshape.test.ts +79 -0
  60. package/src/__tests__/doctor.test.ts +319 -0
  61. package/src/__tests__/dream-pipeline.test.ts +321 -0
  62. package/src/__tests__/drill-down.test.ts +108 -0
  63. package/src/__tests__/embedder.test.ts +122 -0
  64. package/src/__tests__/emotion-decay.test.ts +46 -0
  65. package/src/__tests__/emotion-detector.test.ts +56 -0
  66. package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
  67. package/src/__tests__/emotion-retrieval.test.ts +56 -0
  68. package/src/__tests__/entity-resolver.test.ts +58 -0
  69. package/src/__tests__/env-config.test.ts +151 -0
  70. package/src/__tests__/error-signature-boost.test.ts +87 -0
  71. package/src/__tests__/error-signature.test.ts +95 -0
  72. package/src/__tests__/error-taxonomy.test.ts +129 -0
  73. package/src/__tests__/ethics-layer.test.ts +476 -0
  74. package/src/__tests__/event-segmenter.test.ts +153 -0
  75. package/src/__tests__/feature-flags-integration.test.ts +54 -0
  76. package/src/__tests__/frequency-tracker.test.ts +113 -0
  77. package/src/__tests__/graph-export.test.ts +489 -0
  78. package/src/__tests__/hotness-blend.test.ts +76 -0
  79. package/src/__tests__/ingest-chunking.test.ts +44 -0
  80. package/src/__tests__/ingest.test.ts +360 -0
  81. package/src/__tests__/interference-detector.test.ts +203 -0
  82. package/src/__tests__/kg-extractor.test.ts +192 -0
  83. package/src/__tests__/kg-store.test.ts +300 -0
  84. package/src/__tests__/llm-consolidation.test.ts +226 -0
  85. package/src/__tests__/maintenance-locks.test.ts +174 -0
  86. package/src/__tests__/mcp-efficiency.test.ts +288 -0
  87. package/src/__tests__/mcp-related-scopes.test.ts +151 -0
  88. package/src/__tests__/memory-boundaries.test.ts +250 -0
  89. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
  90. package/src/__tests__/memory-evolution.test.ts +271 -0
  91. package/src/__tests__/memory-health-rebalance.test.ts +144 -0
  92. package/src/__tests__/memory-lint.test.ts +421 -0
  93. package/src/__tests__/memory-output.test.ts +215 -0
  94. package/src/__tests__/memory-promotion.test.ts +315 -0
  95. package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
  96. package/src/__tests__/multi-vector.test.ts +242 -0
  97. package/src/__tests__/narrative-architecture.test.ts +456 -0
  98. package/src/__tests__/noise-filter.test.ts +205 -0
  99. package/src/__tests__/obsidian-connector.test.ts +363 -0
  100. package/src/__tests__/omitted-reporting.test.ts +98 -0
  101. package/src/__tests__/pending-review.test.ts +77 -0
  102. package/src/__tests__/pii-redact.test.ts +314 -0
  103. package/src/__tests__/post-retrieval-filter.test.ts +178 -0
  104. package/src/__tests__/ppr-traversal.test.ts +256 -0
  105. package/src/__tests__/prediction-engine.test.ts +349 -0
  106. package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
  107. package/src/__tests__/preference-guard-smoke.test.ts +211 -0
  108. package/src/__tests__/preference-matcher.test.ts +230 -0
  109. package/src/__tests__/preference-slots.test.ts +205 -0
  110. package/src/__tests__/prospective-memory.test.ts +283 -0
  111. package/src/__tests__/query-entity-detector.test.ts +171 -0
  112. package/src/__tests__/query-expander-alias.test.ts +146 -0
  113. package/src/__tests__/query-expander.test.ts +34 -0
  114. package/src/__tests__/recall-governor.test.ts +227 -0
  115. package/src/__tests__/recall-mode.test.ts +64 -0
  116. package/src/__tests__/result-synthesizer.test.ts +198 -0
  117. package/src/__tests__/resume-context.test.ts +4898 -0
  118. package/src/__tests__/retrieval-trace.test.ts +213 -0
  119. package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
  120. package/src/__tests__/retroactive-boost.test.ts +223 -0
  121. package/src/__tests__/salience-scoring.test.ts +106 -0
  122. package/src/__tests__/scope-inventory.test.ts +200 -0
  123. package/src/__tests__/scope-policy.test.ts +69 -0
  124. package/src/__tests__/scope-suggester.test.ts +80 -0
  125. package/src/__tests__/session-checkpoint.test.ts +291 -0
  126. package/src/__tests__/session-distiller.test.ts +640 -0
  127. package/src/__tests__/session-gc.test.ts +215 -0
  128. package/src/__tests__/skill-engine.test.ts +552 -0
  129. package/src/__tests__/skill-promotion.test.ts +601 -0
  130. package/src/__tests__/skill-verifier.test.ts +122 -0
  131. package/src/__tests__/source-heartbeat.test.ts +250 -0
  132. package/src/__tests__/store-patch-metadata.test.ts +205 -0
  133. package/src/__tests__/store-scope-audit.test.ts +81 -0
  134. package/src/__tests__/store-upsert.test.ts +184 -0
  135. package/src/__tests__/supersede-chain.test.ts +154 -0
  136. package/src/__tests__/temporal-parser.test.ts +241 -0
  137. package/src/__tests__/temporal-validity.test.ts +160 -0
  138. package/src/__tests__/term-registry.test.ts +121 -0
  139. package/src/__tests__/tool-error-surfacing.test.ts +98 -0
  140. package/src/__tests__/tool-output-compressor.test.ts +160 -0
  141. package/src/__tests__/topic-tag.test.ts +113 -0
  142. package/src/__tests__/trusted-memory.test.ts +839 -0
  143. package/src/__tests__/usage-tracker.test.ts +287 -0
  144. package/src/__tests__/version-manager.test.ts +213 -0
  145. package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
  146. package/src/__tests__/workflow-observation.test.ts +433 -0
  147. package/src/__tests__/write-verifier.test.ts +122 -0
  148. package/src/access-tracker.ts +38 -32
  149. package/src/activity-counter.ts +46 -18
  150. package/src/aliases.ts +133 -0
  151. package/src/api-server.ts +27 -7
  152. package/src/audit-log.ts +4 -2
  153. package/src/auto-consolidation.ts +8 -3
  154. package/src/auto-gc.ts +85 -21
  155. package/src/capture-engine.ts +54 -21
  156. package/src/cli.ts +169 -37
  157. package/src/confidence-tracker.ts +36 -18
  158. package/src/consolidation-engine.ts +109 -11
  159. package/src/context-composer.ts +8 -1
  160. package/src/data-checkup.ts +95 -6
  161. package/src/distill-lock.ts +194 -15
  162. package/src/doctor.ts +9 -2
  163. package/src/dream-pipeline.ts +118 -34
  164. package/src/env-config.ts +77 -0
  165. package/src/error-signature.ts +153 -0
  166. package/src/error-taxonomy.ts +115 -0
  167. package/src/eval.ts +251 -6
  168. package/src/idempotency.ts +8 -0
  169. package/src/ingest.ts +31 -10
  170. package/src/kg-extractor.ts +6 -2
  171. package/src/kg-store.ts +236 -44
  172. package/src/llm-consolidation.ts +2 -1
  173. package/src/mcp-server.ts +85 -1799
  174. package/src/mcp-tool-deps.ts +48 -0
  175. package/src/mcp-tools-advanced.ts +941 -0
  176. package/src/mcp-tools-core.ts +633 -0
  177. package/src/mcp-tools-governance.ts +475 -0
  178. package/src/memory-boundaries.ts +3 -1
  179. package/src/memory-evolution.ts +73 -1
  180. package/src/memory-lint.ts +183 -10
  181. package/src/memory-output.ts +114 -3
  182. package/src/memory-promotion.ts +246 -0
  183. package/src/memory-schema.ts +25 -2
  184. package/src/memory-store-port.ts +43 -0
  185. package/src/multi-vector.ts +2 -1
  186. package/src/narrative-schema.ts +2 -1
  187. package/src/noise-filter.ts +57 -6
  188. package/src/pii-detector.ts +157 -5
  189. package/src/query-expander.ts +90 -9
  190. package/src/result-synthesizer.ts +2 -1
  191. package/src/retention-policy.ts +2 -1
  192. package/src/retriever.ts +259 -139
  193. package/src/runtime-config.ts +8 -1
  194. package/src/scope-suggester.ts +113 -0
  195. package/src/session-distiller.ts +23 -2
  196. package/src/session-engine.ts +2 -1
  197. package/src/session-schema.ts +1 -0
  198. package/src/session-store.ts +5 -0
  199. package/src/skill-engine.ts +78 -0
  200. package/src/skill-promotion.ts +247 -47
  201. package/src/skill-schema.ts +16 -3
  202. package/src/skill-verifier.ts +152 -0
  203. package/src/store.ts +267 -38
  204. package/src/tool-output-compressor.ts +3 -1
  205. package/src/ui-server.ts +2 -1
  206. package/src/usage-tracker.ts +242 -0
  207. package/src/version-manager.ts +3 -2
  208. package/src/workflow-observation-engine.ts +2 -1
  209. package/src/workflow-observation-schema.ts +2 -1
  210. package/src/workflow-observation-store.ts +6 -2
  211. package/.env +0 -4
  212. package/CLAUDE.md +0 -46
  213. package/config.json +0 -55
  214. package/daily-recall.sh +0 -110
  215. package/logs/daily-recall.error.log +0 -0
  216. package/logs/daily-recall.log +0 -60
  217. package/logs/ingest-2026-04-11.log +0 -110
  218. package/logs/ingest-2026-04-12.log +0 -90
  219. package/logs/ingest-2026-04-13.log +0 -87
  220. package/logs/launchagent-stderr.log +0 -0
  221. package/logs/launchagent-stdout.log +0 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Usage Tracker — injection-vs-use 双计数影子统计(shadow usage stats)
3
+ *
4
+ * 借鉴 Orb (MIT © 2026 Kailiang Zhang) 的 injection/usage 双计数 + cold 判定,
5
+ * TS 逻辑重写(Orb 原为 Python)。回答的痛点:"纠正写进记忆后,是否真的不再复发?"
6
+ *
7
+ * 两类信号分离:
8
+ * - injection = 记忆被检索 surface / 注入上下文的次数。已有计数 = metadata.accessCount
9
+ * (由 access-tracker 维护)。本模块只读它做对比,不修改。
10
+ * - use = 记忆在 reconstruction 输出里被 [src:ID] 真正引用的次数。新增 = metadata.usage.useCount。
11
+ *
12
+ * 设计同 confidence-tracker:纯函数返回 metadata patch,caller 用 store.update() 应用,
13
+ * 模块本身 store-agnostic、易测。命名空间 metadata.usage 独立于两套已有 accessCount
14
+ * (top-level metadata.accessCount + metadata.evolution.accessCount),避免语义打架。
15
+ *
16
+ * 【第一阶段定位:纯影子统计 · 只采原始信号】
17
+ * 写入路径(buildUsagePatch / recordMemoryUsage)只记 useCount / firstUsedAt / lastUsedAt
18
+ * 这种"被引用了"的客观事实,**不写 usageStatus**。原因:reconstruction 只在"被引用"时触发,
19
+ * 写入时 useCount 必 > 0,永远写不出 cold(被反复 surface 却从未被引用)——而 cold 恰是核心观察目标。
20
+ * 所以状态判定降级为离线/按需:deriveUsageStatus(entry) 用真实 accessCount(injection) + useCount
21
+ * 现算,cold 由此可达。第一阶段不参与 ranking、不接 forget-engine、不动 tier/confidence——
22
+ * computeUsageStatus 不接任何决策回路,纯供离线观察 / 第二阶段使用。
23
+ *
24
+ * 注:accessCount 作 injection 代理是近似(受 access-tracker 的 novelty/cooldown gate 影响);
25
+ * 并发:usage 写入与 access/confidence 共用 metadata RMW(读整段→改→写回),并发下后写覆盖先写,
26
+ * 单用户场景几乎不触发,根治(store 级 patchMetadata 单写通道)留第二阶段。
27
+ */
28
+
29
+ import type { MemoryEntry } from "./store.js";
30
+ import * as envConfig from "./env-config.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Constants(复刻 Orb decay_tick 判定阈值,供 deriveUsageStatus / computeUsageStatus 使用)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /** injection ≥ 此值 且 use == 0 → cold(被反复 surface 却从未被真正引用)。 */
37
+ export const USAGE_COLD_INJECTION_THRESHOLD = 6;
38
+ /** use / injection ≥ 此比例 → hot。 */
39
+ export const USAGE_HOT_RATIO = 0.3;
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Types
43
+ // ---------------------------------------------------------------------------
44
+
45
+ export type UsageStatus = "unused" | "cold" | "warm" | "hot";
46
+
47
+ export interface UsageMetadata {
48
+ /** 被 reconstruction [src:ID] 引用的累计次数。 */
49
+ useCount: number;
50
+ /** 首次被引用时间(ms)。 */
51
+ firstUsedAt?: number;
52
+ /** 最近被引用时间(ms)。 */
53
+ lastUsedAt?: number;
54
+ /**
55
+ * 衍生观察状态。第一阶段写入路径**不写**此字段(见文件头注释);保留 optional
56
+ * 是为了向后兼容 + 第二阶段可能持久化。读取(readUsage)仍兼容已存在的旧值。
57
+ */
58
+ usageStatus?: UsageStatus;
59
+ }
60
+
61
+ export interface UsageUpdate {
62
+ entryId: string;
63
+ oldUseCount: number;
64
+ newUseCount: number;
65
+ }
66
+
67
+ export interface UsagePatch {
68
+ /** 完整 metadata object(含原有所有字段 + 更新后的 usage)— caller 整体 JSON.stringify 写回。 */
69
+ metadata: Record<string, unknown>;
70
+ update: UsageUpdate;
71
+ }
72
+
73
+ /** 有 store 的 caller 用的最小端口(结构化类型,便于测试 mock)。
74
+ * 第二阶段起走 store.patchMetadata 单写通道:读改写在 per-id 队列内完成,
75
+ * 不再各自 getById+update(那是并发覆盖的根源)。 */
76
+ export interface UsageStorePort {
77
+ patchMetadata(
78
+ id: string,
79
+ patchFn: (meta: Record<string, unknown>, entry: MemoryEntry) => Record<string, unknown>,
80
+ scopeFilter?: string[],
81
+ ): Promise<MemoryEntry | null>;
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Helpers
86
+ // ---------------------------------------------------------------------------
87
+
88
+ /** 解析 metadata JSON,仅接受非空对象(防 "null" / 数组 / 标量等让后续 meta.usage 访问崩)。 */
89
+ function parseMeta(entry: MemoryEntry): Record<string, unknown> {
90
+ try {
91
+ const parsed: unknown = JSON.parse(entry.metadata || "{}");
92
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
93
+ ? (parsed as Record<string, unknown>)
94
+ : {};
95
+ } catch {
96
+ return {};
97
+ }
98
+ }
99
+
100
+ function asUsageStatus(v: unknown): UsageStatus | undefined {
101
+ return v === "unused" || v === "cold" || v === "warm" || v === "hot" ? v : undefined;
102
+ }
103
+
104
+ function readUsage(meta: Record<string, unknown>): UsageMetadata {
105
+ const raw = meta.usage;
106
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
107
+ const obj = raw as Record<string, unknown>;
108
+ return {
109
+ useCount: typeof obj.useCount === "number" ? obj.useCount : 0,
110
+ firstUsedAt: typeof obj.firstUsedAt === "number" ? obj.firstUsedAt : undefined,
111
+ lastUsedAt: typeof obj.lastUsedAt === "number" ? obj.lastUsedAt : undefined,
112
+ usageStatus: asUsageStatus(obj.usageStatus),
113
+ };
114
+ }
115
+ return { useCount: 0 };
116
+ }
117
+
118
+ /** injection 代理 = top-level metadata.accessCount(access-tracker 维护)。只读不写。 */
119
+ function readInjectionCount(meta: Record<string, unknown>): number {
120
+ return typeof meta.accessCount === "number" ? meta.accessCount : 0;
121
+ }
122
+
123
+ /**
124
+ * use 信号采集是否在生产路径上活跃。
125
+ * useCount 唯一的写入路径是 reconstruction 的 [src:ID] 引用,而 reconstruction
126
+ * 由 RECALLNEST_CONSTRUCTIVE_RETRIEVAL 控制——flag 关闭时 useCount 恒零,
127
+ * "cold"(access 高 + use 零)退化为"access 高",不可解释。
128
+ * 观察口(data_checkup / memory_lint / dream 快照)必须先查本开关:信号未采集时
129
+ * 如实标注并跳过判定,否则输出的是统计学完美、临床无意义的假 cold。
130
+ * (2026-06-11 临床重审:Alice 曾开过此 flag,因合成"召回缺失"主动关闭——
131
+ * 病史见 memory canonicalKey=constructive-retrieval-flag-clinical-history)
132
+ */
133
+ export function isUsageSignalActive(): boolean {
134
+ return envConfig.constructiveRetrieval();
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Public API
139
+ // ---------------------------------------------------------------------------
140
+
141
+ /** 读取 metadata.usage(缺失字段当 0/undefined)。 */
142
+ export function getUsageMetadata(entry: MemoryEntry): UsageMetadata {
143
+ return readUsage(parseMeta(entry));
144
+ }
145
+
146
+ /**
147
+ * 影子状态判定(纯函数,不裁决)。复刻 Orb decay_tick:
148
+ * use > 0 且 use/injection ≥ HOT_RATIO → hot
149
+ * use > 0 → warm
150
+ * use == 0 且 injection ≥ COLD_THRESH → cold
151
+ * 否则 → unused
152
+ */
153
+ export function computeUsageStatus(injectionCount: number, useCount: number): UsageStatus {
154
+ if (useCount > 0) {
155
+ if (injectionCount > 0 && useCount / injectionCount >= USAGE_HOT_RATIO) return "hot";
156
+ return "warm";
157
+ }
158
+ if (injectionCount >= USAGE_COLD_INJECTION_THRESHOLD) return "cold";
159
+ return "unused";
160
+ }
161
+
162
+ /**
163
+ * 离线/按需派生 usage 状态(不持久化)。
164
+ * 第一阶段写入路径不写 usageStatus,cold 等状态通过本函数用真实 accessCount(injection) + useCount
165
+ * 现算 —— 这是 cold 可观察的唯一入口。纯读,不改 store、不接决策。
166
+ */
167
+ export function deriveUsageStatus(entry: MemoryEntry): UsageStatus {
168
+ const meta = parseMeta(entry);
169
+ return computeUsageStatus(readInjectionCount(meta), readUsage(meta).useCount);
170
+ }
171
+
172
+ /**
173
+ * 构造一次"使用"的 metadata patch(纯函数,不写 store)。
174
+ * useCount +1、更新 lastUsedAt/firstUsedAt。**只记原始信号,不写 usageStatus**(见文件头)。
175
+ * 只动 metadata.usage,原有字段(accessCount/confidence/evolution/tier/...)原样保留。
176
+ * entry 为 null(未找到)→ 返回 null。
177
+ */
178
+ export function buildUsagePatch(entry: MemoryEntry | null, now: number): UsagePatch | null {
179
+ if (!entry) return null;
180
+
181
+ const meta = parseMeta(entry);
182
+ const usage = readUsage(meta);
183
+
184
+ const oldUseCount = usage.useCount;
185
+ const newUseCount = oldUseCount + 1;
186
+
187
+ const nextUsage: UsageMetadata = {
188
+ useCount: newUseCount,
189
+ firstUsedAt: usage.firstUsedAt ?? now,
190
+ lastUsedAt: now,
191
+ };
192
+
193
+ meta.usage = nextUsage;
194
+
195
+ return {
196
+ metadata: meta,
197
+ update: { entryId: entry.id, oldUseCount, newUseCount },
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Convenience wrapper:经 store.patchMetadata 队列做读改写(防并发覆盖)。
203
+ * 影子写入;entry 未找到 → 返回 null(no-op)。
204
+ * 失败由 caller 决定是否 try/catch(影子统计不应阻断主流程)。
205
+ */
206
+ export async function recordMemoryUsage(
207
+ store: UsageStorePort,
208
+ entryId: string,
209
+ now: number = Date.now(),
210
+ scope?: string,
211
+ ): Promise<UsageUpdate | null> {
212
+ let update: UsageUpdate | null = null;
213
+ const result = await store.patchMetadata(
214
+ entryId,
215
+ (_meta, entry) => {
216
+ // buildUsagePatch 读的是队列内最新一次读出的 entry,与 _meta 同源。
217
+ const patch = buildUsagePatch(entry, now);
218
+ // entry 在队列内非 null,patch 必非 null;防御性兜底保持类型干净。
219
+ if (!patch) return _meta;
220
+ update = patch.update;
221
+ return patch.metadata;
222
+ },
223
+ scope ? [scope] : undefined,
224
+ );
225
+ return result === null ? null : update;
226
+ }
227
+
228
+ /**
229
+ * 批量影子记录一次 reconstruction 引用到的所有记忆(best-effort)。
230
+ * citedIds = reconstruction 输出里 valid 的 [src:ID](caller 已用 extractCitedIds 提取并 scope 校验过)。
231
+ * 用 allSettled:单条写失败不连累其它,也绝不向上抛——影子统计不阻断主检索/resume 流程。
232
+ */
233
+ export async function recordReconstructionUsage(
234
+ store: UsageStorePort,
235
+ citedIds: string[],
236
+ now: number = Date.now(),
237
+ scope?: string,
238
+ ): Promise<void> {
239
+ if (citedIds.length === 0) return;
240
+ const unique = [...new Set(citedIds)];
241
+ await Promise.allSettled(unique.map(id => recordMemoryUsage(store, id, now, scope)));
242
+ }
@@ -12,7 +12,8 @@
12
12
  * available for re-evaluation.
13
13
  */
14
14
 
15
- import type { MemoryEntry, MemoryStore } from "./store.js";
15
+ import type { MemoryEntry } from "./store.js";
16
+ import type { MemoryStorePort } from "./memory-store-port.js";
16
17
  import { logInfo } from "./stderr-log.js";
17
18
 
18
19
  // ============================================================================
@@ -43,7 +44,7 @@ export interface VersionGroupMetadata {
43
44
  * @returns The version_group ID
44
45
  */
45
46
  export async function createVersionGroup(
46
- store: MemoryStore,
47
+ store: Pick<MemoryStorePort, "update">,
47
48
  canonical: MemoryEntry,
48
49
  member: MemoryEntry,
49
50
  scope: string,
@@ -11,6 +11,7 @@ import type {
11
11
  } from "./workflow-observation-schema.js";
12
12
  import { WorkflowObservationInputSchema, WorkflowObservationRecordSchema } from "./workflow-observation-schema.js";
13
13
  import type { WorkflowObservationStore } from "./workflow-observation-store.js";
14
+ import { buildIdempotentRecordId } from "./idempotency.js";
14
15
 
15
16
  const DEFAULT_WINDOWS = [7, 30] as const;
16
17
  const MAX_OBSERVATIONS_FOR_ANALYSIS = 1000;
@@ -148,7 +149,7 @@ export function buildWorkflowObservationRecord(rawInput: unknown): WorkflowObser
148
149
  const input = WorkflowObservationInputSchema.parse(rawInput);
149
150
  return WorkflowObservationRecordSchema.parse({
150
151
  ...input,
151
- observationId: randomUUID(),
152
+ observationId: buildIdempotentRecordId("wfo", input.idempotencyKey) ?? randomUUID(),
152
153
  resolvedScope: resolveWorkflowObservationScope(input),
153
154
  });
154
155
  }
@@ -22,6 +22,8 @@ export const WorkflowObservationInputSchema = z.object({
22
22
  tags: normalizedStringListSchema("tags", 8, 40),
23
23
  tools: normalizedStringListSchema("tools", 6, 60),
24
24
  recordedAt: z.string().datetime().default(() => new Date().toISOString()),
25
+ skillId: optionalBoundedStringSchema(128),
26
+ idempotencyKey: optionalBoundedStringSchema(160),
25
27
  });
26
28
 
27
29
  export const WorkflowObservationRecordSchema = WorkflowObservationInputSchema.extend({
@@ -84,4 +86,3 @@ export interface WorkflowEvidencePack {
84
86
  }>;
85
87
  suggestions: string[];
86
88
  }
87
-
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
 
4
4
  import { metaDir } from "./compat.js";
@@ -37,6 +37,11 @@ export class WorkflowObservationStore {
37
37
  const parsed = WorkflowObservationRecordSchema.parse(record);
38
38
  const timestampToken = parsed.recordedAt.replace(/[:.]/g, "-");
39
39
  const path = join(this.dataDir, `${timestampToken}-${parsed.observationId}.json`);
40
+ const suffix = `-${parsed.observationId}.json`;
41
+ for (const name of readdirSync(this.dataDir).filter((item) => item.endsWith(suffix))) {
42
+ const existingPath = join(this.dataDir, name);
43
+ if (existingPath !== path) unlinkSync(existingPath);
44
+ }
40
45
  writeFileSync(path, JSON.stringify(parsed, null, 2) + "\n");
41
46
  return parsed;
42
47
  }
@@ -67,4 +72,3 @@ export class WorkflowObservationStore {
67
72
  return sortNewestFirst(items).slice(0, limit);
68
73
  }
69
74
  }
70
-
package/.env DELETED
@@ -1,4 +0,0 @@
1
- JINA_API_KEY=jina_a953576fa7b746019e57d1370122fb8aNQLVOjq95WHeDBKlBr3JeCj0K5SL
2
- QWEN_API_KEY=sk-8b8f215de882422a991653d5fc3cc275
3
- RECALLNEST_EMOTION_SCORING=true
4
- RECALLNEST_CONSTRUCTIVE_RETRIEVAL=true
package/CLAUDE.md DELETED
@@ -1,46 +0,0 @@
1
- # RecallNest 项目规则
2
-
3
- ## 1. 技术栈约束(强制)
4
-
5
- - **运行时:Bun**。禁止使用 `npm`、`yarn`、`pnpm`,所有命令走 `bun`
6
- - **语言:TypeScript strict**。禁止 `any` 类型,复杂接口提取到独立类型文件
7
- - **向量存储:LanceDB**。不引入其他向量数据库
8
- - **嵌入模型:Jina v5**。不替换嵌入方案
9
- - **MCP 协议:`@modelcontextprotocol/sdk`**。MCP tools 统一走 `registerTool()`
10
-
11
- ## 2. 编码规范
12
-
13
- - 新增 MCP tool 必须同时写测试(`src/__tests__/`)
14
- - 禁止生成带 `// TODO` 或 `// placeholder` 的半成品代码——缺信息就停下来问
15
- - 修改已有 tool 的 schema 前,先确认调用方兼容性
16
- - 脚本放 `scripts/`,不在 `src/` 之外创建业务 `.ts` 文件
17
-
18
- ## 3. Agent 行为准则
19
-
20
- - **写代码后必须自己跑测试**:执行 `bun test`,读报错,自主修复,直到全绿才交付
21
- - 实现新功能前,先输出分步计划并等待确认,再进入执行阶段
22
- - 任务颗粒度过大时,主动拆分子任务逐步推进,不要一口气撸完然后崩溃
23
- - 禁止用未验证的 `git status` 结果写入 checkpoint
24
-
25
- ## 4. Git Push 规则(重要!)
26
-
27
- - **所有 push 只推 origin**(`AliceLJY/recallnest`)
28
- - **绝对不要 push 到 upstream**(`CortexReach/memory-lancedb-pro`)—— 那是上游公开仓库,推了等于暴露私有改造
29
- - `trihippo/recallnest` 已停止维护,不再推送
30
- - 默认 `git push` 即可(默认推 origin)
31
- - 需要给上游提 PR 时,走 fork + PR 流程,不直接 push
32
-
33
- ## 5. Feature Flag
34
-
35
- - `RECALLNEST_MULTI_VECTOR=true` — 多向量 L0/L1/L2 检索
36
- - `RECALLNEST_KG_MODE=true` — KG 三元组提取 + 图遍历
37
- - `RECALLNEST_EMOTION_SCORING=true` — Emotion detection + salience-weighted Weibull decay + arousal boost + retrieval scoring
38
- - `RECALLNEST_CONSTRUCTIVE_RETRIEVAL=true` — Multi-source candidate expansion + source-map grounded reconstruction (resume default, search opt-in)
39
- - `RECALLNEST_NARRATIVE_MODE=true` — Autobiographical narrative metadata layer (life-period / general-event / specific-event)
40
- - `RECALLNEST_PREDICTIVE_MEMORY=true` — Heuristic-predicted prospective reminders (zero LLM, behavioral signals)
41
-
42
- ## 6. 测试基线
43
-
44
- - 改完代码必须跑 `bun test`,全量通过才能 commit
45
- - 当前基线:1486 tests / 0 fail
46
- - 新增功能必须配套测试,基线只能涨不能降
package/config.json DELETED
@@ -1,55 +0,0 @@
1
- {
2
- "dbPath": "./data/lancedb",
3
- "recallMode": "full",
4
- "embedding": {
5
- "provider": "jina",
6
- "apiKey": "${JINA_API_KEY}",
7
- "model": "jina-embeddings-v5-text-small",
8
- "baseURL": "https://api.jina.ai/v1",
9
- "dimensions": 1024,
10
- "taskQuery": "retrieval.query",
11
- "taskPassage": "retrieval.passage"
12
- },
13
- "llm": {
14
- "apiKey": "${QWEN_API_KEY}",
15
- "model": "qwen-turbo",
16
- "baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1"
17
- },
18
- "sources": {
19
- "cc": {
20
- "path": "auto",
21
- "glob": "*.jsonl",
22
- "description": "Claude Code transcripts"
23
- },
24
- "codex": {
25
- "path": "~/.codex/sessions",
26
- "glob": "*.jsonl",
27
- "description": "Codex sessions"
28
- },
29
- "memory": {
30
- "path": "auto",
31
- "glob": "*.md",
32
- "description": "Claude Code memory files"
33
- },
34
- "gemini": {
35
- "path": "~/.gemini/tmp",
36
- "glob": "session-*.json",
37
- "description": "Gemini CLI sessions"
38
- },
39
- "desktop": {
40
- "path": "./data/desktop-import",
41
- "glob": "*.jsonl",
42
- "description": "Claude Desktop / claude.ai web conversations"
43
- }
44
- },
45
- "retrieval": {
46
- "mode": "hybrid",
47
- "vectorWeight": 0.6,
48
- "bm25Weight": 0.4,
49
- "recencyHalfLifeDays": 30,
50
- "timeDecayHalfLifeDays": 60,
51
- "hardMinScore": 0.38,
52
- "hotnessWeight": 0.15
53
- },
54
- "recallDepthDefault": "l1"
55
- }
package/daily-recall.sh DELETED
@@ -1,110 +0,0 @@
1
- #!/bin/bash
2
- # daily-recall.sh — 每日记忆回顾(v2: 纯 RecallNest,不依赖 claude -p)
3
- # 用 lm distill 从历史对话中提取结构化洞察
4
- # 每天轮换查询主题,输出存到 daily-reflections/YYYY-MM-DD.md
5
-
6
- set -euo pipefail
7
-
8
- # ── 路径配置 ──────────────────────────────────────────────────
9
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
- LM="$SCRIPT_DIR/lm"
11
- REFLECTION_DIR="$SCRIPT_DIR/daily-reflections"
12
- LOG_DIR="$SCRIPT_DIR/logs"
13
- DATE=$(date '+%Y-%m-%d')
14
- OUTPUT_FILE="$REFLECTION_DIR/$DATE.md"
15
- LOG_FILE="$LOG_DIR/daily-recall.log"
16
-
17
- # ── 环境变量 ──────────────────────────────────────────────────
18
- export PATH="$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
19
- export HOME="${HOME:-/Users/anxianjingya}"
20
-
21
- # 加载 .env(JINA_API_KEY 等)
22
- if [ -f "$SCRIPT_DIR/.env" ]; then
23
- set -a
24
- source "$SCRIPT_DIR/.env"
25
- set +a
26
- fi
27
-
28
- # ── 准备目录 ──────────────────────────────────────────────────
29
- mkdir -p "$REFLECTION_DIR"
30
- mkdir -p "$LOG_DIR"
31
-
32
- echo "=== $(date '+%Y-%m-%d %H:%M:%S') daily-recall 开始 ===" | tee -a "$LOG_FILE"
33
-
34
- # ── 每日轮换查询(7 天一轮) ──────────────────────────────────
35
- QUERIES=(
36
- "成长 经历 改变了什么"
37
- "洞察 反思 核心教训"
38
- "哲学 意识 自由意志"
39
- "写作 表达 风格探索"
40
- "技术 架构 解决方案"
41
- "感悟 人心 被看见"
42
- "踩坑 修复 经验总结"
43
- )
44
-
45
- DOW=$(date '+%u') # 1=周一 ... 7=周日
46
- IDX=$(( (DOW - 1) % ${#QUERIES[@]} ))
47
- TODAY_QUERY="${QUERIES[$IDX]}"
48
-
49
- echo " 今日主题 (#$((IDX+1))): $TODAY_QUERY" | tee -a "$LOG_FILE"
50
-
51
- # ── CC-6 + HP-3: Distill gate — lock + session gate + activity gate ─────
52
- echo " 检查 distill gate..." | tee -a "$LOG_FILE"
53
- GATE_RESULT=$(bun -e "
54
- const { acquireLock, shouldDistill } = require('./src/distill-lock.ts');
55
- const { getDistillTier } = require('./src/activity-counter.ts');
56
- const cp = parseInt(process.argv[1] || '0', 10);
57
- const tier = getDistillTier();
58
- const sessionReady = shouldDistill(cp);
59
- if (tier === 'none' && !sessionReady) { console.log('SKIP:no_activity'); process.exit(0); }
60
- if (!acquireLock()) { console.log('SKIP:lock_held'); process.exit(0); }
61
- console.log('OK:' + tier);
62
- " "$(bun run "$SCRIPT_DIR/src/cli.ts" checkpoint-count 2>/dev/null || echo 5)" 2>&1 || echo "OK:standard")
63
-
64
- if [[ "$GATE_RESULT" == SKIP:* ]]; then
65
- echo " [跳过] distill gate: $GATE_RESULT" | tee -a "$LOG_FILE"
66
- else
67
- echo " distill gate: $GATE_RESULT" | tee -a "$LOG_FILE"
68
- fi
69
-
70
- # ── 用 lm distill 提取结构化洞察 ──────────────────────────────
71
- echo " 执行 distill..." | tee -a "$LOG_FILE"
72
- DISTILL_OUTPUT=$("$LM" distill "$TODAY_QUERY" --profile writing --limit 8 --all-scopes 2>&1 || true)
73
-
74
- if [ -z "$DISTILL_OUTPUT" ]; then
75
- echo " [警告] distill 无输出,尝试 export..." | tee -a "$LOG_FILE"
76
- DISTILL_OUTPUT=$("$LM" export "$TODAY_QUERY" --profile writing --limit 5 --format md --all-scopes 2>&1 || true)
77
- fi
78
-
79
- if [ -z "$DISTILL_OUTPUT" ]; then
80
- echo " [错误] distill 和 export 均无输出,终止" | tee -a "$LOG_FILE"
81
- exit 1
82
- fi
83
-
84
- echo " distill 完成,$(echo "$DISTILL_OUTPUT" | wc -l | tr -d ' ') 行" | tee -a "$LOG_FILE"
85
-
86
- # CC-6: Release lock on success + HP-3: Reset activity counter
87
- bun -e "try { require('./src/distill-lock.ts').releaseLock(); } catch {}" 2>/dev/null || true
88
- bun -e "try { require('./src/activity-counter.ts').resetWriteCount(); } catch {}" 2>/dev/null || true
89
-
90
- # ── 写入输出文件 ──────────────────────────────────────────────
91
- cat > "$OUTPUT_FILE" << EOF
92
- # 每日回顾 · $DATE
93
-
94
- > 主题:$TODAY_QUERY
95
-
96
- $DISTILL_OUTPUT
97
-
98
- ---
99
- *由 daily-recall.sh v2 自动生成 · $(date '+%Y-%m-%d %H:%M:%S')*
100
- *引擎:RecallNest distill (profile: writing)*
101
- EOF
102
-
103
- echo " [完成] 回顾已写入: $OUTPUT_FILE" | tee -a "$LOG_FILE"
104
-
105
- # ── Checkpoint GC(2026-03-26 正式启用) ────────────
106
- echo " 执行 checkpoint-gc..." | tee -a "$LOG_FILE"
107
- GC_OUTPUT=$(bun run "$SCRIPT_DIR/src/cli.ts" checkpoint-gc 2>&1 || true)
108
- echo " checkpoint-gc: $GC_OUTPUT" | tee -a "$LOG_FILE"
109
-
110
- echo "=== $(date '+%Y-%m-%d %H:%M:%S') daily-recall 结束 ===" | tee -a "$LOG_FILE"
File without changes
@@ -1,60 +0,0 @@
1
- === 2026-04-12 08:00:01 daily-recall 开始 ===
2
- === 2026-04-12 08:00:01 daily-recall 开始 ===
3
- 今日主题 (#7): 踩坑 修复 经验总结
4
- 今日主题 (#7): 踩坑 修复 经验总结
5
- 检查 distill gate...
6
- 检查 distill gate...
7
- distill gate:
8
- distill gate:
9
- 执行 distill...
10
- 执行 distill...
11
- distill 完成,27 行
12
- distill 完成,27 行
13
- [完成] 回顾已写入: /Users/anxianjingya/recallnest/daily-reflections/2026-04-12.md
14
- [完成] 回顾已写入: /Users/anxianjingya/recallnest/daily-reflections/2026-04-12.md
15
- 执行 checkpoint-gc...
16
- 执行 checkpoint-gc...
17
- checkpoint-gc: === Checkpoint GC ===
18
-
19
- Kept : 217
20
- Archived : 0
21
- Deleted : 0
22
- Errors : 0
23
- checkpoint-gc: === Checkpoint GC ===
24
-
25
- Kept : 217
26
- Archived : 0
27
- Deleted : 0
28
- Errors : 0
29
- === 2026-04-12 08:00:13 daily-recall 结束 ===
30
- === 2026-04-12 08:00:13 daily-recall 结束 ===
31
- === 2026-04-13 08:00:01 daily-recall 开始 ===
32
- === 2026-04-13 08:00:01 daily-recall 开始 ===
33
- 今日主题 (#1): 成长 经历 改变了什么
34
- 今日主题 (#1): 成长 经历 改变了什么
35
- 检查 distill gate...
36
- 检查 distill gate...
37
- distill gate:
38
- distill gate:
39
- 执行 distill...
40
- 执行 distill...
41
- distill 完成,31 行
42
- distill 完成,31 行
43
- [完成] 回顾已写入: /Users/anxianjingya/recallnest/daily-reflections/2026-04-13.md
44
- [完成] 回顾已写入: /Users/anxianjingya/recallnest/daily-reflections/2026-04-13.md
45
- 执行 checkpoint-gc...
46
- 执行 checkpoint-gc...
47
- checkpoint-gc: === Checkpoint GC ===
48
-
49
- Kept : 218
50
- Archived : 0
51
- Deleted : 0
52
- Errors : 0
53
- checkpoint-gc: === Checkpoint GC ===
54
-
55
- Kept : 218
56
- Archived : 0
57
- Deleted : 0
58
- Errors : 0
59
- === 2026-04-13 08:00:22 daily-recall 结束 ===
60
- === 2026-04-13 08:00:22 daily-recall 结束 ===