recallnest 3.0.0 → 3.0.1

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 (184) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +222 -254
  3. package/README_CN.md +206 -243
  4. package/package.json +3 -2
  5. package/src/capture-engine.ts +26 -4
  6. package/src/context-composer-stable.ts +5 -1
  7. package/src/embedder.ts +24 -0
  8. package/src/env-config.ts +18 -0
  9. package/src/ingest.ts +225 -17
  10. package/src/kg-extractor.ts +17 -0
  11. package/src/memory-output.ts +35 -0
  12. package/src/pivot-apply.ts +52 -1
  13. package/src/pivot-evidence.ts +128 -0
  14. package/src/ppr-traversal.ts +25 -1
  15. package/src/runtime-config.ts +9 -0
  16. package/src/session-engine.ts +11 -4
  17. package/src/session-output.ts +9 -1
  18. package/src/session-store.ts +2 -3
  19. package/src/skill-promotion.ts +67 -1
  20. package/src/__tests__/access-tracker-novelty.test.ts +0 -324
  21. package/src/__tests__/activity-counter.test.ts +0 -125
  22. package/src/__tests__/adaptive-fidelity.test.ts +0 -102
  23. package/src/__tests__/admission-control.test.ts +0 -206
  24. package/src/__tests__/age-label.test.ts +0 -33
  25. package/src/__tests__/anchor-generator.test.ts +0 -67
  26. package/src/__tests__/api-server-security.test.ts +0 -102
  27. package/src/__tests__/auto-consolidation.test.ts +0 -168
  28. package/src/__tests__/auto-recall.test.ts +0 -131
  29. package/src/__tests__/babel-integration.test.ts +0 -144
  30. package/src/__tests__/batch-dedup.test.ts +0 -65
  31. package/src/__tests__/belief-history.test.ts +0 -220
  32. package/src/__tests__/canary-eval.test.ts +0 -114
  33. package/src/__tests__/capture-engine.test.ts +0 -1328
  34. package/src/__tests__/capture-heuristic.test.ts +0 -171
  35. package/src/__tests__/cascade-forget.test.ts +0 -210
  36. package/src/__tests__/category-min-scores.test.ts +0 -116
  37. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  38. package/src/__tests__/circuit-breaker.test.ts +0 -99
  39. package/src/__tests__/cluster-consolidation.test.ts +0 -1236
  40. package/src/__tests__/confidence-metadata.test.ts +0 -125
  41. package/src/__tests__/confidence-tracker.test.ts +0 -261
  42. package/src/__tests__/conflict-advisor.test.ts +0 -206
  43. package/src/__tests__/conflict-escalation.test.ts +0 -149
  44. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  45. package/src/__tests__/conflict-output.test.ts +0 -85
  46. package/src/__tests__/conflict-store.test.ts +0 -418
  47. package/src/__tests__/connector-v1.test.ts +0 -181
  48. package/src/__tests__/consolidation-engine.test.ts +0 -520
  49. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  50. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  51. package/src/__tests__/context-composer-pins.test.ts +0 -64
  52. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  53. package/src/__tests__/context-reconstructor.test.ts +0 -65
  54. package/src/__tests__/context-renderer.test.ts +0 -91
  55. package/src/__tests__/continuity-eval.test.ts +0 -275
  56. package/src/__tests__/continuity-schema.test.ts +0 -97
  57. package/src/__tests__/contracts.test.ts +0 -262
  58. package/src/__tests__/conversation-importer.test.ts +0 -692
  59. package/src/__tests__/core-recall-eval.test.ts +0 -235
  60. package/src/__tests__/core-summary.test.ts +0 -109
  61. package/src/__tests__/data-checkup.test.ts +0 -304
  62. package/src/__tests__/decay-exemption.test.ts +0 -129
  63. package/src/__tests__/dedup-actions.test.ts +0 -58
  64. package/src/__tests__/dedup-guard.test.ts +0 -39
  65. package/src/__tests__/deterministic-id.test.ts +0 -39
  66. package/src/__tests__/distill-lock.test.ts +0 -305
  67. package/src/__tests__/distill-session-argshape.test.ts +0 -79
  68. package/src/__tests__/doctor.test.ts +0 -319
  69. package/src/__tests__/dream-pipeline.test.ts +0 -820
  70. package/src/__tests__/dream-scope-isolation.test.ts +0 -386
  71. package/src/__tests__/dream-write-batching.test.ts +0 -211
  72. package/src/__tests__/drill-down.test.ts +0 -108
  73. package/src/__tests__/embedder.test.ts +0 -122
  74. package/src/__tests__/emotion-decay.test.ts +0 -46
  75. package/src/__tests__/emotion-detector.test.ts +0 -56
  76. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  77. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  78. package/src/__tests__/entity-resolver.test.ts +0 -58
  79. package/src/__tests__/env-config-invariant.test.ts +0 -80
  80. package/src/__tests__/env-config.test.ts +0 -211
  81. package/src/__tests__/error-signature-boost.test.ts +0 -87
  82. package/src/__tests__/error-signature.test.ts +0 -95
  83. package/src/__tests__/error-taxonomy.test.ts +0 -129
  84. package/src/__tests__/ethics-layer.test.ts +0 -476
  85. package/src/__tests__/event-segmenter.test.ts +0 -153
  86. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  87. package/src/__tests__/frequency-tracker.test.ts +0 -113
  88. package/src/__tests__/freshness.test.ts +0 -186
  89. package/src/__tests__/graph-export.test.ts +0 -489
  90. package/src/__tests__/hotness-blend.test.ts +0 -283
  91. package/src/__tests__/ingest-chunking.test.ts +0 -44
  92. package/src/__tests__/ingest-scope-prefix.test.ts +0 -69
  93. package/src/__tests__/ingest.test.ts +0 -360
  94. package/src/__tests__/interference-detector.test.ts +0 -203
  95. package/src/__tests__/kg-extractor.test.ts +0 -192
  96. package/src/__tests__/kg-store.test.ts +0 -300
  97. package/src/__tests__/llm-client-pivot.test.ts +0 -628
  98. package/src/__tests__/llm-consolidation.test.ts +0 -226
  99. package/src/__tests__/maintenance-locks.test.ts +0 -197
  100. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  101. package/src/__tests__/mcp-related-scopes.test.ts +0 -151
  102. package/src/__tests__/memory-boundaries.test.ts +0 -282
  103. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -362
  104. package/src/__tests__/memory-evolution.test.ts +0 -271
  105. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  106. package/src/__tests__/memory-lint.test.ts +0 -421
  107. package/src/__tests__/memory-output.test.ts +0 -224
  108. package/src/__tests__/memory-promotion.test.ts +0 -315
  109. package/src/__tests__/memory-utility.test.ts +0 -256
  110. package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
  111. package/src/__tests__/multi-vector.test.ts +0 -242
  112. package/src/__tests__/narrative-architecture.test.ts +0 -456
  113. package/src/__tests__/noise-filter.test.ts +0 -205
  114. package/src/__tests__/noise-prototype-bank.test.ts +0 -280
  115. package/src/__tests__/obsidian-connector.test.ts +0 -363
  116. package/src/__tests__/omitted-reporting.test.ts +0 -98
  117. package/src/__tests__/openai-contract.test.ts +0 -513
  118. package/src/__tests__/pending-review.test.ts +0 -77
  119. package/src/__tests__/pii-redact.test.ts +0 -333
  120. package/src/__tests__/pivot-apply.test.ts +0 -373
  121. package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
  122. package/src/__tests__/pivot-distill.test.ts +0 -1178
  123. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  124. package/src/__tests__/ppr-traversal.test.ts +0 -256
  125. package/src/__tests__/prediction-engine.test.ts +0 -349
  126. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  127. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  128. package/src/__tests__/preference-matcher.test.ts +0 -282
  129. package/src/__tests__/preference-slots.test.ts +0 -205
  130. package/src/__tests__/promotion-distinct-sources.test.ts +0 -162
  131. package/src/__tests__/prospective-memory.test.ts +0 -283
  132. package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
  133. package/src/__tests__/query-entity-detector.test.ts +0 -171
  134. package/src/__tests__/query-expander-alias.test.ts +0 -146
  135. package/src/__tests__/query-expander.test.ts +0 -34
  136. package/src/__tests__/recall-gate.test.ts +0 -266
  137. package/src/__tests__/recall-governor.test.ts +0 -227
  138. package/src/__tests__/recall-mode.test.ts +0 -64
  139. package/src/__tests__/resolve-db-path.test.ts +0 -130
  140. package/src/__tests__/resolve-tier.test.ts +0 -64
  141. package/src/__tests__/result-synthesizer.test.ts +0 -198
  142. package/src/__tests__/resume-context.test.ts +0 -4898
  143. package/src/__tests__/retrieval-eval.test.ts +0 -163
  144. package/src/__tests__/retrieval-trace.test.ts +0 -213
  145. package/src/__tests__/retrieve-audit-revision.test.ts +0 -255
  146. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  147. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
  148. package/src/__tests__/retroactive-boost.test.ts +0 -223
  149. package/src/__tests__/salience-scoring.test.ts +0 -106
  150. package/src/__tests__/scope-inventory.test.ts +0 -200
  151. package/src/__tests__/scope-policy.test.ts +0 -69
  152. package/src/__tests__/scope-suggester.test.ts +0 -80
  153. package/src/__tests__/session-checkpoint.test.ts +0 -291
  154. package/src/__tests__/session-distiller.test.ts +0 -640
  155. package/src/__tests__/session-gc.test.ts +0 -215
  156. package/src/__tests__/skill-engine.test.ts +0 -552
  157. package/src/__tests__/skill-promotion.test.ts +0 -654
  158. package/src/__tests__/skill-verifier.test.ts +0 -122
  159. package/src/__tests__/source-heartbeat.test.ts +0 -254
  160. package/src/__tests__/stable-context-length-contract.test.ts +0 -62
  161. package/src/__tests__/store-patch-metadata.test.ts +0 -235
  162. package/src/__tests__/store-read-consistency.test.ts +0 -122
  163. package/src/__tests__/store-schema-migration.test.ts +0 -87
  164. package/src/__tests__/store-scope-audit.test.ts +0 -81
  165. package/src/__tests__/store-upsert.test.ts +0 -201
  166. package/src/__tests__/supersede-chain.test.ts +0 -154
  167. package/src/__tests__/synthesis-contract.test.ts +0 -252
  168. package/src/__tests__/synthesis-promotion.test.ts +0 -369
  169. package/src/__tests__/temporal-parser.test.ts +0 -241
  170. package/src/__tests__/temporal-validity.test.ts +0 -160
  171. package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
  172. package/src/__tests__/term-registry.test.ts +0 -121
  173. package/src/__tests__/tool-error-surfacing.test.ts +0 -98
  174. package/src/__tests__/tool-output-compressor.test.ts +0 -160
  175. package/src/__tests__/topic-tag.test.ts +0 -113
  176. package/src/__tests__/trusted-memory.test.ts +0 -867
  177. package/src/__tests__/ui-server-security.test.ts +0 -93
  178. package/src/__tests__/usage-tracker.test.ts +0 -287
  179. package/src/__tests__/utility-weight.test.ts +0 -57
  180. package/src/__tests__/verbatim-self-recall.test.ts +0 -279
  181. package/src/__tests__/version-manager.test.ts +0 -248
  182. package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
  183. package/src/__tests__/workflow-observation.test.ts +0 -433
  184. package/src/__tests__/write-verifier.test.ts +0 -122
@@ -1,211 +0,0 @@
1
- import { describe, expect, it, afterEach, beforeEach } from "bun:test";
2
-
3
- import * as envConfig from "../env-config.js";
4
-
5
- // Every RECALLNEST_* key this suite mutates. Snapshot + restore so we never leak
6
- // env state into sibling test files (the whole `bun test` run shares one process).
7
- const KEYS = [
8
- "RECALLNEST_MULTI_VECTOR",
9
- "RECALLNEST_EMOTION_SCORING",
10
- "RECALLNEST_PREDICTIVE_MEMORY",
11
- "RECALLNEST_SYNTHESIZE",
12
- "RECALLNEST_LLM_CONSOLIDATION",
13
- "RECALLNEST_CONSTRUCTIVE_RETRIEVAL",
14
- "RECALLNEST_NARRATIVE_MODE",
15
- "RECALLNEST_KG_MODE",
16
- "RECALLNEST_CORE_SUMMARY",
17
- "RECALLNEST_ERROR_SIGNATURE_BOOST",
18
- "RECALLNEST_USAGE_DECAY",
19
- "RECALLNEST_DATA_DIR",
20
- "RECALLNEST_MCP_TIER",
21
- "RECALLNEST_RECALL_MODE",
22
- "RECALLNEST_UI_PORT",
23
- "RECALLNEST_API_PORT",
24
- "RECALLNEST_DREAM_BUDGET_MS",
25
- "RECALLNEST_SYNTHESIS_MODEL",
26
- ];
27
-
28
- const saved: Record<string, string | undefined> = {};
29
-
30
- beforeEach(() => {
31
- for (const key of KEYS) {
32
- saved[key] = process.env[key];
33
- delete process.env[key];
34
- }
35
- });
36
-
37
- afterEach(() => {
38
- for (const key of KEYS) {
39
- if (saved[key] === undefined) delete process.env[key];
40
- else process.env[key] = saved[key];
41
- }
42
- });
43
-
44
- describe('env-config boolean flags — strict === "true"', () => {
45
- const accessors: Array<[string, () => boolean, string]> = [
46
- ["multiVector", envConfig.multiVector, "RECALLNEST_MULTI_VECTOR"],
47
- ["emotionScoring", envConfig.emotionScoring, "RECALLNEST_EMOTION_SCORING"],
48
- ["predictiveMemory", envConfig.predictiveMemory, "RECALLNEST_PREDICTIVE_MEMORY"],
49
- ["synthesize", envConfig.synthesize, "RECALLNEST_SYNTHESIZE"],
50
- ["llmConsolidation", envConfig.llmConsolidation, "RECALLNEST_LLM_CONSOLIDATION"],
51
- ["constructiveRetrieval", envConfig.constructiveRetrieval, "RECALLNEST_CONSTRUCTIVE_RETRIEVAL"],
52
- ["narrativeMode", envConfig.narrativeMode, "RECALLNEST_NARRATIVE_MODE"],
53
- ["kgMode", envConfig.kgMode, "RECALLNEST_KG_MODE"],
54
- ["coreSummary", envConfig.coreSummary, "RECALLNEST_CORE_SUMMARY"],
55
- ["errorSignatureBoost", envConfig.errorSignatureBoost, "RECALLNEST_ERROR_SIGNATURE_BOOST"],
56
- ["usageDecay", envConfig.usageDecay, "RECALLNEST_USAGE_DECAY"],
57
- ];
58
-
59
- for (const [name, accessor, key] of accessors) {
60
- it(`${name}: false unless the value is exactly "true"`, () => {
61
- delete process.env[key];
62
- expect(accessor()).toBe(false);
63
-
64
- process.env[key] = "true";
65
- expect(accessor()).toBe(true);
66
-
67
- // No trim, no case-folding, no truthy coercion — only the literal "true".
68
- for (const fuzzy of ["True", "TRUE", " true ", "1", "yes", "false", ""]) {
69
- process.env[key] = fuzzy;
70
- expect(accessor()).toBe(false);
71
- }
72
- });
73
- }
74
- });
75
-
76
- describe("env-config string settings — || default (empty string falls through)", () => {
77
- it('dataDir defaults to "data" when unset or empty', () => {
78
- delete process.env.RECALLNEST_DATA_DIR;
79
- expect(envConfig.dataDir()).toBe("data");
80
-
81
- process.env.RECALLNEST_DATA_DIR = ""; // || not ??
82
- expect(envConfig.dataDir()).toBe("data");
83
-
84
- process.env.RECALLNEST_DATA_DIR = "/tmp/custom-data";
85
- expect(envConfig.dataDir()).toBe("/tmp/custom-data");
86
- });
87
-
88
- it('mcpTier defaults to "advanced"; valid passes through; illegal value kept verbatim', () => {
89
- delete process.env.RECALLNEST_MCP_TIER;
90
- expect(envConfig.mcpTier()).toBe("advanced");
91
-
92
- process.env.RECALLNEST_MCP_TIER = ""; // || not ??
93
- expect(envConfig.mcpTier()).toBe("advanced");
94
-
95
- process.env.RECALLNEST_MCP_TIER = "core";
96
- expect(envConfig.mcpTier()).toBe("core");
97
-
98
- // No runtime validation — an unknown non-empty value is preserved as-is
99
- // (downstream shouldRegisterTool relies on this; do not coerce to default).
100
- process.env.RECALLNEST_MCP_TIER = "garbage";
101
- expect(envConfig.mcpTier()).toBe("garbage");
102
- });
103
- });
104
-
105
- describe("env-config raw passthrough — caller validates / clamps / falls back", () => {
106
- const rawAccessors: Array<[string, () => string | undefined, string]> = [
107
- ["recallModeRaw", envConfig.recallModeRaw, "RECALLNEST_RECALL_MODE"],
108
- ["uiPortRaw", envConfig.uiPortRaw, "RECALLNEST_UI_PORT"],
109
- ["apiPortRaw", envConfig.apiPortRaw, "RECALLNEST_API_PORT"],
110
- ];
111
-
112
- for (const [name, accessor, key] of rawAccessors) {
113
- it(`${name}: returns the raw env value, undefined when unset`, () => {
114
- delete process.env[key];
115
- expect(accessor()).toBeUndefined();
116
-
117
- process.env[key] = "verbatim-123";
118
- expect(accessor()).toBe("verbatim-123");
119
-
120
- // Raw passthrough: empty string is NOT defaulted here — the caller decides.
121
- process.env[key] = "";
122
- expect(accessor()).toBe("");
123
- });
124
- }
125
- });
126
-
127
- describe("env-config recallModeRaw — must not pre-default (preserves resolveRecallMode config fallback)", () => {
128
- it('returns the raw value or undefined, never a hardcoded mode like "summary"', () => {
129
- delete process.env.RECALLNEST_RECALL_MODE;
130
- // Must be undefined, NOT "summary" — resolveRecallMode owns the config fallback.
131
- expect(envConfig.recallModeRaw()).toBeUndefined();
132
-
133
- process.env.RECALLNEST_RECALL_MODE = "light";
134
- expect(envConfig.recallModeRaw()).toBe("light");
135
-
136
- // Invalid modes pass through verbatim — the whitelist check lives in resolveRecallMode.
137
- process.env.RECALLNEST_RECALL_MODE = "bogus";
138
- expect(envConfig.recallModeRaw()).toBe("bogus");
139
- });
140
- });
141
-
142
- describe("env-config accessors are lazy (read at call time, never frozen)", () => {
143
- it("reflect runtime mutation rather than a module-load snapshot", () => {
144
- delete process.env.RECALLNEST_EMOTION_SCORING;
145
- expect(envConfig.emotionScoring()).toBe(false);
146
-
147
- process.env.RECALLNEST_EMOTION_SCORING = "true";
148
- expect(envConfig.emotionScoring()).toBe(true); // would fail if cached at import
149
-
150
- delete process.env.RECALLNEST_EMOTION_SCORING;
151
- expect(envConfig.emotionScoring()).toBe(false);
152
- });
153
- });
154
-
155
- describe("env-config dreamBudgetMs — the two silent failure modes it exists to stop", () => {
156
- const FALLBACK = 2 * 60 * 60 * 1000;
157
-
158
- it("falls back when unset", () => {
159
- delete process.env.RECALLNEST_DREAM_BUDGET_MS;
160
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
161
- });
162
-
163
- it('empty string does NOT become 0 (old `?? default` let "" through → Number("") === 0 → every scope skipped)', () => {
164
- process.env.RECALLNEST_DREAM_BUDGET_MS = "";
165
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
166
- process.env.RECALLNEST_DREAM_BUDGET_MS = " ";
167
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
168
- });
169
-
170
- it('non-numeric does NOT become NaN (NaN deadline makes `Date.now() > deadline` permanently false → budget guard gone)', () => {
171
- for (const bad of ["2h", "abc", "30min", "1_000"]) {
172
- process.env.RECALLNEST_DREAM_BUDGET_MS = bad;
173
- const ms = envConfig.dreamBudgetMs(FALLBACK);
174
- expect(Number.isFinite(ms)).toBe(true);
175
- expect(ms).toBe(FALLBACK);
176
- // The regression this guards: a NaN budget silently disables the deadline.
177
- expect(Date.now() > Date.now() + ms).toBe(false);
178
- }
179
- });
180
-
181
- it("rejects zero and negatives (they would expire the deadline instantly)", () => {
182
- for (const bad of ["0", "-1", "-60000"]) {
183
- process.env.RECALLNEST_DREAM_BUDGET_MS = bad;
184
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
185
- }
186
- });
187
-
188
- it("accepts a valid override", () => {
189
- process.env.RECALLNEST_DREAM_BUDGET_MS = "900000";
190
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(900_000);
191
- });
192
- });
193
-
194
- describe("env-config synthesisModel — dream 合成的模型旋钮", () => {
195
- it("未设 → undefined(调用方回落到 config.llm.model,保持老行为)", () => {
196
- delete process.env.RECALLNEST_SYNTHESIS_MODEL;
197
- expect(envConfig.synthesisModel()).toBeUndefined();
198
- });
199
-
200
- it("空串 / 纯空白也算未设,不会把模型名设成空字符串", () => {
201
- for (const blank of ["", " ", "\t"]) {
202
- process.env.RECALLNEST_SYNTHESIS_MODEL = blank;
203
- expect(envConfig.synthesisModel()).toBeUndefined();
204
- }
205
- });
206
-
207
- it("正常值去空白后返回", () => {
208
- process.env.RECALLNEST_SYNTHESIS_MODEL = " qwen-plus ";
209
- expect(envConfig.synthesisModel()).toBe("qwen-plus");
210
- });
211
- });
@@ -1,87 +0,0 @@
1
- import { describe, expect, it, afterEach, beforeEach } from "bun:test";
2
-
3
- import { applyErrorSignatureBoost } from "../retriever.js";
4
- import type { RetrievalResult } from "../retriever.js";
5
- import { extractErrorSignatures } from "../error-signature.js";
6
-
7
- const FLAG = "RECALLNEST_ERROR_SIGNATURE_BOOST";
8
-
9
- function makeResult(opts: { category: string; score: number; sigs?: string[] }): RetrievalResult {
10
- const metadata = opts.sigs ? JSON.stringify({ error_signature: opts.sigs }) : "{}";
11
- return {
12
- entry: {
13
- id: "m1",
14
- text: "x",
15
- scope: "test",
16
- category: opts.category,
17
- timestamp: "2026-06-13T00:00:00.000Z",
18
- metadata,
19
- },
20
- score: opts.score,
21
- sources: {},
22
- } as unknown as RetrievalResult;
23
- }
24
-
25
- let saved: string | undefined;
26
- beforeEach(() => {
27
- saved = process.env[FLAG];
28
- delete process.env[FLAG];
29
- });
30
- afterEach(() => {
31
- if (saved === undefined) delete process.env[FLAG];
32
- else process.env[FLAG] = saved;
33
- });
34
-
35
- describe("applyErrorSignatureBoost — A1 检索端精确召回 boost", () => {
36
- const QUERY = "build broke: xmlsec1 not found";
37
- const matchingSig = () => extractErrorSignatures({ problem: QUERY })[0];
38
-
39
- it("flag off(默认)→ 原样返回同一引用、分数不变", () => {
40
- const input = [makeResult({ category: "cases", score: 0.4, sigs: [matchingSig()] })];
41
- const out = applyErrorSignatureBoost(input, QUERY);
42
- expect(out).toBe(input); // no-op,bit-identical
43
- expect(out[0].score).toBe(0.4);
44
- });
45
-
46
- it("flag on + 指纹命中 case → 乘法 boost (×1.5)", () => {
47
- process.env[FLAG] = "true";
48
- const input = [makeResult({ category: "cases", score: 0.4, sigs: [matchingSig()] })];
49
- const out = applyErrorSignatureBoost(input, QUERY);
50
- expect(out[0].score).toBeCloseTo(0.6, 6); // 0.4 * 1.5
51
- });
52
-
53
- it("flag on + 命中但高分 → clamp 到 1、不溢出", () => {
54
- process.env[FLAG] = "true";
55
- const input = [makeResult({ category: "cases", score: 0.8, sigs: [matchingSig()] })];
56
- const out = applyErrorSignatureBoost(input, QUERY);
57
- expect(out[0].score).toBe(1); // 0.8 * 1.5 = 1.2 → clamp 1
58
- });
59
-
60
- it("flag on + 非 case category(patterns)→ 不 boost", () => {
61
- process.env[FLAG] = "true";
62
- const input = [makeResult({ category: "patterns", score: 0.4, sigs: [matchingSig()] })];
63
- const out = applyErrorSignatureBoost(input, QUERY);
64
- expect(out[0].score).toBe(0.4);
65
- });
66
-
67
- it("flag on + case 但指纹不重叠 → 不 boost", () => {
68
- process.env[FLAG] = "true";
69
- const input = [makeResult({ category: "cases", score: 0.4, sigs: ["zzz_nonexistent_signature_xyz"] })];
70
- const out = applyErrorSignatureBoost(input, QUERY);
71
- expect(out[0].score).toBe(0.4);
72
- });
73
-
74
- it("flag on + case 无 error_signature metadata → 不 boost", () => {
75
- process.env[FLAG] = "true";
76
- const input = [makeResult({ category: "cases", score: 0.4 })];
77
- const out = applyErrorSignatureBoost(input, QUERY);
78
- expect(out[0].score).toBe(0.4);
79
- });
80
-
81
- it("flag on + query 抽不出错误指纹 → 原样返回同一引用", () => {
82
- process.env[FLAG] = "true";
83
- const input = [makeResult({ category: "cases", score: 0.4, sigs: [matchingSig()] })];
84
- const out = applyErrorSignatureBoost(input, "今天天气不错,喝杯咖啡");
85
- expect(out).toBe(input);
86
- });
87
- });
@@ -1,95 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { extractErrorSignatures, MAX_SIGNATURES } from "../error-signature.js";
3
-
4
- describe("extractErrorSignatures — English patterns", () => {
5
- test("bare 'X not found' (no colon) is extracted — MemOS 原 6 正则会漏", () => {
6
- const sigs = extractErrorSignatures({ problem: "Build broke: xmlsec1 not found" });
7
- expect(sigs.some(s => s.includes("xmlsec1 not found"))).toBe(true);
8
- });
9
-
10
- test("'exit code N' is extracted", () => {
11
- const sigs = extractErrorSignatures({ problem: "Deploy failed with exit code 1" });
12
- expect(sigs.some(s => s.includes("exit code 1"))).toBe(true);
13
- });
14
-
15
- test("'<Name>Error: body' extracted and ranks top by specificity", () => {
16
- const sigs = extractErrorSignatures({ problem: "NameError: name foo is not defined" });
17
- expect(sigs.some(s => s.includes("NameError"))).toBe(true);
18
- expect(sigs[0]).toContain("NameError");
19
- });
20
-
21
- test("'No such file or directory' standalone is extracted (case-insensitive)", () => {
22
- const sigs = extractErrorSignatures({ problem: "open config: No such file or directory" });
23
- expect(sigs.some(s => /No such file or directory/i.test(s))).toBe(true);
24
- });
25
-
26
- test("bare 3-digit numbers in benign text do NOT false-match as HTTP error", () => {
27
- const sigs = extractErrorSignatures({ problem: "deployed 500 records and processed 404 rows in the weekly batch" });
28
- expect(sigs.some(s => s.includes("500 records") || s.includes("404 rows"))).toBe(false);
29
- });
30
-
31
- test("real HTTP status phrase is still extracted after tightening", () => {
32
- const sigs = extractErrorSignatures({ problem: "API call returned 500 Internal Server Error on submit" });
33
- expect(sigs.some(s => s.includes("500 Internal"))).toBe(true);
34
- });
35
- });
36
-
37
- describe("extractErrorSignatures — Chinese (MemOS 原版会被 alpha 检查滤掉)", () => {
38
- test("中文错误现象被抽取", () => {
39
- const sigs = extractErrorSignatures({ problem: "部署时权限被拒绝,找不到文件" });
40
- expect(sigs.some(s => s.includes("找不到") || s.includes("权限被拒"))).toBe(true);
41
- });
42
-
43
- test("中文短片段(<6 字符但 ≥3 中文字)保留", () => {
44
- const sigs = extractErrorSignatures({ problem: "找不到模块" });
45
- expect(sigs.length).toBeGreaterThan(0);
46
- });
47
- });
48
-
49
- describe("extractErrorSignatures — input gating", () => {
50
- test("outcome 成功态(无失败词)不被纳入,不抽反向信号", () => {
51
- const sigs = extractErrorSignatures({
52
- problem: "ImportError: no module named requests",
53
- outcome: "exit code 0 部署成功",
54
- });
55
- expect(sigs.some(s => s.includes("ImportError"))).toBe(true);
56
- expect(sigs.some(s => s.includes("exit code 0"))).toBe(false);
57
- });
58
-
59
- test("context 参与抽取(原始 stderr 常在 context)", () => {
60
- const sigs = extractErrorSignatures({
61
- problem: "服务起不来",
62
- context: "stderr: pg_config: command not found",
63
- });
64
- expect(sigs.some(s => s.includes("pg_config") || s.includes("command not found"))).toBe(true);
65
- });
66
-
67
- test("结果不超过 MAX_SIGNATURES 条", () => {
68
- const sigs = extractErrorSignatures({
69
- problem: "NameError: a. TypeError: b. ValueError: c. KeyError: d. exit code 1. 404 Not Found. 权限被拒绝.",
70
- });
71
- expect(sigs.length).toBeLessThanOrEqual(MAX_SIGNATURES);
72
- });
73
- });
74
-
75
- describe("extractErrorSignatures — 过泛中文词收紧(P3-A 检索端噪声治理)", () => {
76
- const BARE_GENERIC = /^(?:报错|出错|错误|异常|失败|超时|崩溃|无法|未定义|不能)[了的过着吧呢啊啦哦呀嘛。,!?!?,\s]*$/;
77
-
78
- test("裸过泛词 + 语气助词不被抽成指纹(失败了 / 超时了 / 崩溃了 / 报错了)", () => {
79
- for (const bare of ["部署失败了", "请求超时了", "程序崩溃了", "系统报错了"]) {
80
- const sigs = extractErrorSignatures({ problem: bare });
81
- expect(sigs.some(s => BARE_GENERIC.test(s.trim()))).toBe(false);
82
- }
83
- });
84
-
85
- test("过泛词带具体锚点(英文 / 路径 / 多位数字)时保留", () => {
86
- const sigs = extractErrorSignatures({ problem: "操作失败:xmlsec1 not found" });
87
- expect(sigs.some(s => s.includes("xmlsec1"))).toBe(true);
88
- });
89
-
90
- test("具体中文短语不受收紧影响(找不到 / 权限被拒 / 连接失败)", () => {
91
- expect(extractErrorSignatures({ problem: "找不到模块" }).length).toBeGreaterThan(0);
92
- expect(extractErrorSignatures({ problem: "权限被拒绝" }).some(s => s.includes("权限被拒"))).toBe(true);
93
- expect(extractErrorSignatures({ problem: "数据库连接失败" }).some(s => s.includes("连接失败"))).toBe(true);
94
- });
95
- });
@@ -1,129 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { classifyError, toErrorResult, runToolSafely } from "../error-taxonomy.js";
4
-
5
- // P-error-taxonomy: 借鉴 RepoPrompt CE 的 error taxonomy。
6
- // 每种失败标 {reasonCode, retryable, responsibility},让 agent 判断重试/改参/上报。
7
-
8
- describe("classifyError", () => {
9
- it("classifies transport errors as retryable / transport", () => {
10
- for (const m of ["ECONNRESET", "socket hang up", "fetch failed", "request timed out", "ETIMEDOUT"]) {
11
- const c = classifyError(new Error(m));
12
- expect(c.reasonCode).toBe("transport_error");
13
- expect(c.retryable).toBe(true);
14
- expect(c.responsibility).toBe("transport");
15
- }
16
- });
17
-
18
- it("classifies rate-limit / overload as retryable / peer", () => {
19
- for (const m of ["429 Too Many Requests", "rate limit exceeded", "529 overloaded", "quota exceeded"]) {
20
- const c = classifyError(new Error(m));
21
- expect(c.reasonCode).toBe("upstream_overloaded");
22
- expect(c.retryable).toBe(true);
23
- expect(c.responsibility).toBe("peer");
24
- }
25
- });
26
-
27
- it("classifies auth failures as non-retryable / host", () => {
28
- const c = classifyError(new Error("401 Unauthorized: invalid api key"));
29
- expect(c.reasonCode).toBe("upstream_auth");
30
- expect(c.retryable).toBe(false);
31
- expect(c.responsibility).toBe("host");
32
- });
33
-
34
- it("classifies missing-scope as non-retryable / peer", () => {
35
- const c = classifyError(new Error("search_memory requires a scope. Pass scope explicitly, provide sessionId..."));
36
- expect(c.reasonCode).toBe("scope_required");
37
- expect(c.retryable).toBe(false);
38
- expect(c.responsibility).toBe("peer");
39
- });
40
-
41
- it("classifies validation errors as invalid_input / peer", () => {
42
- const c = classifyError(new Error("validation failed: expected string received number"));
43
- expect(c.reasonCode).toBe("invalid_input");
44
- expect(c.responsibility).toBe("peer");
45
- });
46
-
47
- it("classifies store errors as app", () => {
48
- const c = classifyError(new Error("LanceDB table not found"));
49
- expect(c.reasonCode).toBe("store_error");
50
- expect(c.responsibility).toBe("app");
51
- });
52
-
53
- it("classifies config/env-missing as non-retryable / host (P2-1/P2-3)", () => {
54
- const c = classifyError(new Error("Environment variable OPENAI_API_KEY not set"));
55
- expect(c.reasonCode).toBe("config_missing");
56
- expect(c.retryable).toBe(false);
57
- expect(c.responsibility).toBe("host");
58
- });
59
-
60
- it("classifies bad memory references as invalid_memory_ref / peer — caller-side (P2-3)", () => {
61
- for (const m of [
62
- "Invalid memory ID format: bad",
63
- 'Ambiguous prefix "12345678" matches 2+ memories. Use a longer prefix or full ID.',
64
- "Memory 12345678 is outside accessible scopes",
65
- ]) {
66
- const c = classifyError(new Error(m));
67
- expect(c.reasonCode).toBe("invalid_memory_ref");
68
- expect(c.responsibility).toBe("peer");
69
- }
70
- });
71
-
72
- it("falls back to internal_error / app / non-retryable for unknown errors", () => {
73
- const c = classifyError(new Error("something totally unexpected happened"));
74
- expect(c.reasonCode).toBe("internal_error");
75
- expect(c.retryable).toBe(false);
76
- expect(c.responsibility).toBe("app");
77
- });
78
-
79
- it("handles non-Error throwables", () => {
80
- const c = classifyError("plain string failure");
81
- expect(c.reasonCode).toBe("internal_error");
82
- expect(c.responsibility).toBe("app");
83
- });
84
- });
85
-
86
- describe("toErrorResult", () => {
87
- it("produces an isError result with a machine-readable classification block", () => {
88
- const r = toErrorResult("search_memory", new Error("ECONNRESET"));
89
- expect(r.isError).toBe(true);
90
- expect(r.content[0].type).toBe("text");
91
- const text = r.content[0].text;
92
- expect(text).toContain('Tool "search_memory" failed');
93
- expect(text).toContain("reason_code: transport_error");
94
- expect(text).toContain("retryable: true");
95
- expect(text).toContain("responsibility: transport");
96
- expect(text).toContain("detail: ECONNRESET");
97
- });
98
-
99
- it("preserves the original detail for unknown errors", () => {
100
- const r = toErrorResult("forget_memory", new Error("boom"));
101
- expect(r.isError).toBe(true);
102
- expect(r.content[0].text).toContain("reason_code: internal_error");
103
- expect(r.content[0].text).toContain("detail: boom");
104
- });
105
- });
106
-
107
- describe("runToolSafely", () => {
108
- it("returns handler result on success (pass-through)", async () => {
109
- const ok = { content: [{ type: "text", text: "ok" }] };
110
- const r = await runToolSafely("search_memory", async () => ok);
111
- expect(r).toEqual(ok);
112
- });
113
-
114
- it("catches handler throw and returns a classified isError result", async () => {
115
- const r = await runToolSafely("search_memory", async () => { throw new Error("ECONNRESET"); }) as { isError: boolean; content: { text: string }[] };
116
- expect(r.isError).toBe(true);
117
- expect(r.content[0].text).toContain("reason_code: transport_error");
118
- });
119
-
120
- it("catches lazy-init failure and classifies it (P2-1 regression lock)", async () => {
121
- // 模拟 ensureComponents 抛错(缺 env var)——必须被分类返回 isError,而非裸抛绕过。
122
- const r = await runToolSafely("store_memory", async () => {
123
- throw new Error("Environment variable JINA_API_KEY not set");
124
- }) as { isError: boolean; content: { text: string }[] };
125
- expect(r.isError).toBe(true);
126
- expect(r.content[0].text).toContain("reason_code: config_missing");
127
- expect(r.content[0].text).toContain("responsibility: host");
128
- });
129
- });