recallnest 2.3.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/README.md +49 -38
  3. package/README_CN.md +46 -31
  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 +25 -4
  15. package/src/__tests__/access-tracker-novelty.test.ts +304 -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__/api-server-security.test.ts +102 -0
  21. package/src/__tests__/auto-consolidation.test.ts +168 -0
  22. package/src/__tests__/auto-recall.test.ts +131 -0
  23. package/src/__tests__/babel-integration.test.ts +144 -0
  24. package/src/__tests__/batch-dedup.test.ts +65 -0
  25. package/src/__tests__/belief-history.test.ts +220 -0
  26. package/src/__tests__/canary-eval.test.ts +114 -0
  27. package/src/__tests__/capture-engine.test.ts +1328 -0
  28. package/src/__tests__/capture-heuristic.test.ts +171 -0
  29. package/src/__tests__/cascade-forget.test.ts +210 -0
  30. package/src/__tests__/category-min-scores.test.ts +116 -0
  31. package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
  32. package/src/__tests__/circuit-breaker.test.ts +99 -0
  33. package/src/__tests__/cluster-consolidation.test.ts +945 -0
  34. package/src/__tests__/confidence-metadata.test.ts +125 -0
  35. package/src/__tests__/confidence-tracker.test.ts +261 -0
  36. package/src/__tests__/conflict-advisor.test.ts +206 -0
  37. package/src/__tests__/conflict-escalation.test.ts +149 -0
  38. package/src/__tests__/conflict-lifecycle.test.ts +83 -0
  39. package/src/__tests__/conflict-output.test.ts +85 -0
  40. package/src/__tests__/conflict-store.test.ts +418 -0
  41. package/src/__tests__/connector-v1.test.ts +181 -0
  42. package/src/__tests__/consolidation-engine.test.ts +430 -0
  43. package/src/__tests__/constructive-retrieval.test.ts +509 -0
  44. package/src/__tests__/context-collapse-renderer.test.ts +214 -0
  45. package/src/__tests__/context-composer-pins.test.ts +64 -0
  46. package/src/__tests__/context-composer-task-selection.test.ts +24 -0
  47. package/src/__tests__/context-reconstructor.test.ts +65 -0
  48. package/src/__tests__/context-renderer.test.ts +91 -0
  49. package/src/__tests__/continuity-eval.test.ts +275 -0
  50. package/src/__tests__/continuity-schema.test.ts +97 -0
  51. package/src/__tests__/contracts.test.ts +262 -0
  52. package/src/__tests__/conversation-importer.test.ts +692 -0
  53. package/src/__tests__/core-recall-eval.test.ts +235 -0
  54. package/src/__tests__/core-summary.test.ts +109 -0
  55. package/src/__tests__/data-checkup.test.ts +276 -0
  56. package/src/__tests__/decay-exemption.test.ts +129 -0
  57. package/src/__tests__/dedup-actions.test.ts +58 -0
  58. package/src/__tests__/dedup-guard.test.ts +39 -0
  59. package/src/__tests__/deterministic-id.test.ts +39 -0
  60. package/src/__tests__/distill-lock.test.ts +305 -0
  61. package/src/__tests__/distill-session-argshape.test.ts +79 -0
  62. package/src/__tests__/doctor.test.ts +319 -0
  63. package/src/__tests__/dream-pipeline.test.ts +780 -0
  64. package/src/__tests__/drill-down.test.ts +108 -0
  65. package/src/__tests__/embedder.test.ts +122 -0
  66. package/src/__tests__/emotion-decay.test.ts +46 -0
  67. package/src/__tests__/emotion-detector.test.ts +56 -0
  68. package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
  69. package/src/__tests__/emotion-retrieval.test.ts +56 -0
  70. package/src/__tests__/entity-resolver.test.ts +58 -0
  71. package/src/__tests__/env-config.test.ts +151 -0
  72. package/src/__tests__/error-signature-boost.test.ts +87 -0
  73. package/src/__tests__/error-signature.test.ts +95 -0
  74. package/src/__tests__/error-taxonomy.test.ts +129 -0
  75. package/src/__tests__/ethics-layer.test.ts +476 -0
  76. package/src/__tests__/event-segmenter.test.ts +153 -0
  77. package/src/__tests__/feature-flags-integration.test.ts +54 -0
  78. package/src/__tests__/frequency-tracker.test.ts +113 -0
  79. package/src/__tests__/freshness.test.ts +186 -0
  80. package/src/__tests__/graph-export.test.ts +489 -0
  81. package/src/__tests__/hotness-blend.test.ts +283 -0
  82. package/src/__tests__/ingest-chunking.test.ts +44 -0
  83. package/src/__tests__/ingest.test.ts +360 -0
  84. package/src/__tests__/interference-detector.test.ts +203 -0
  85. package/src/__tests__/kg-extractor.test.ts +192 -0
  86. package/src/__tests__/kg-store.test.ts +300 -0
  87. package/src/__tests__/llm-client-pivot.test.ts +628 -0
  88. package/src/__tests__/llm-consolidation.test.ts +226 -0
  89. package/src/__tests__/maintenance-locks.test.ts +189 -0
  90. package/src/__tests__/mcp-efficiency.test.ts +288 -0
  91. package/src/__tests__/mcp-related-scopes.test.ts +151 -0
  92. package/src/__tests__/memory-boundaries.test.ts +282 -0
  93. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
  94. package/src/__tests__/memory-evolution.test.ts +271 -0
  95. package/src/__tests__/memory-health-rebalance.test.ts +144 -0
  96. package/src/__tests__/memory-lint.test.ts +421 -0
  97. package/src/__tests__/memory-output.test.ts +215 -0
  98. package/src/__tests__/memory-promotion.test.ts +315 -0
  99. package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
  100. package/src/__tests__/multi-vector.test.ts +242 -0
  101. package/src/__tests__/narrative-architecture.test.ts +456 -0
  102. package/src/__tests__/noise-filter.test.ts +205 -0
  103. package/src/__tests__/noise-prototype-bank.test.ts +280 -0
  104. package/src/__tests__/obsidian-connector.test.ts +363 -0
  105. package/src/__tests__/omitted-reporting.test.ts +98 -0
  106. package/src/__tests__/pending-review.test.ts +77 -0
  107. package/src/__tests__/pii-redact.test.ts +333 -0
  108. package/src/__tests__/pivot-apply.test.ts +373 -0
  109. package/src/__tests__/pivot-distill-supervisor.test.ts +457 -0
  110. package/src/__tests__/pivot-distill.test.ts +1178 -0
  111. package/src/__tests__/post-retrieval-filter.test.ts +178 -0
  112. package/src/__tests__/ppr-traversal.test.ts +256 -0
  113. package/src/__tests__/prediction-engine.test.ts +349 -0
  114. package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
  115. package/src/__tests__/preference-guard-smoke.test.ts +211 -0
  116. package/src/__tests__/preference-matcher.test.ts +282 -0
  117. package/src/__tests__/preference-slots.test.ts +205 -0
  118. package/src/__tests__/prospective-memory.test.ts +283 -0
  119. package/src/__tests__/pull-from-macbook-sync-only.test.ts +679 -0
  120. package/src/__tests__/query-entity-detector.test.ts +171 -0
  121. package/src/__tests__/query-expander-alias.test.ts +146 -0
  122. package/src/__tests__/query-expander.test.ts +34 -0
  123. package/src/__tests__/recall-gate.test.ts +266 -0
  124. package/src/__tests__/recall-governor.test.ts +227 -0
  125. package/src/__tests__/recall-mode.test.ts +64 -0
  126. package/src/__tests__/resolve-db-path.test.ts +130 -0
  127. package/src/__tests__/resolve-tier.test.ts +64 -0
  128. package/src/__tests__/result-synthesizer.test.ts +198 -0
  129. package/src/__tests__/resume-context.test.ts +4898 -0
  130. package/src/__tests__/retrieval-eval.test.ts +163 -0
  131. package/src/__tests__/retrieval-trace.test.ts +213 -0
  132. package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
  133. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +123 -0
  134. package/src/__tests__/retroactive-boost.test.ts +223 -0
  135. package/src/__tests__/salience-scoring.test.ts +106 -0
  136. package/src/__tests__/scope-inventory.test.ts +200 -0
  137. package/src/__tests__/scope-policy.test.ts +69 -0
  138. package/src/__tests__/scope-suggester.test.ts +80 -0
  139. package/src/__tests__/session-checkpoint.test.ts +291 -0
  140. package/src/__tests__/session-distiller.test.ts +640 -0
  141. package/src/__tests__/session-gc.test.ts +215 -0
  142. package/src/__tests__/skill-engine.test.ts +552 -0
  143. package/src/__tests__/skill-promotion.test.ts +654 -0
  144. package/src/__tests__/skill-verifier.test.ts +122 -0
  145. package/src/__tests__/source-heartbeat.test.ts +250 -0
  146. package/src/__tests__/stable-context-length-contract.test.ts +62 -0
  147. package/src/__tests__/store-patch-metadata.test.ts +205 -0
  148. package/src/__tests__/store-read-consistency.test.ts +122 -0
  149. package/src/__tests__/store-schema-migration.test.ts +87 -0
  150. package/src/__tests__/store-scope-audit.test.ts +81 -0
  151. package/src/__tests__/store-upsert.test.ts +201 -0
  152. package/src/__tests__/supersede-chain.test.ts +154 -0
  153. package/src/__tests__/temporal-parser.test.ts +241 -0
  154. package/src/__tests__/temporal-validity.test.ts +160 -0
  155. package/src/__tests__/term-registry-terminal-roster.test.ts +127 -0
  156. package/src/__tests__/term-registry.test.ts +121 -0
  157. package/src/__tests__/tool-error-surfacing.test.ts +98 -0
  158. package/src/__tests__/tool-output-compressor.test.ts +160 -0
  159. package/src/__tests__/topic-tag.test.ts +113 -0
  160. package/src/__tests__/trusted-memory.test.ts +839 -0
  161. package/src/__tests__/ui-server-security.test.ts +93 -0
  162. package/src/__tests__/usage-tracker.test.ts +287 -0
  163. package/src/__tests__/version-manager.test.ts +213 -0
  164. package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
  165. package/src/__tests__/workflow-observation.test.ts +433 -0
  166. package/src/__tests__/write-verifier.test.ts +122 -0
  167. package/src/access-tracker.ts +90 -34
  168. package/src/activity-counter.ts +46 -18
  169. package/src/adaptive-retrieval.ts +11 -0
  170. package/src/aliases.ts +133 -0
  171. package/src/api-server.ts +36 -9
  172. package/src/audit-log.ts +4 -2
  173. package/src/auto-consolidation.ts +8 -3
  174. package/src/auto-gc.ts +85 -21
  175. package/src/auto-recall.ts +61 -1
  176. package/src/belief-history.ts +164 -0
  177. package/src/capture-engine.ts +142 -32
  178. package/src/cli.ts +268 -41
  179. package/src/confidence-tracker.ts +36 -18
  180. package/src/conflict-engine.ts +24 -3
  181. package/src/consolidation-engine.ts +255 -23
  182. package/src/context-composer-stable-selection.ts +7 -1
  183. package/src/context-composer.ts +8 -1
  184. package/src/data-checkup.ts +96 -7
  185. package/src/decay-engine.ts +59 -7
  186. package/src/distill-lock.ts +194 -15
  187. package/src/doctor.ts +13 -4
  188. package/src/dream-pipeline.ts +473 -41
  189. package/src/env-config.ts +118 -0
  190. package/src/error-signature.ts +153 -0
  191. package/src/error-taxonomy.ts +115 -0
  192. package/src/eval.ts +402 -14
  193. package/src/freshness.ts +192 -0
  194. package/src/http-request-policy.ts +148 -0
  195. package/src/idempotency.ts +8 -0
  196. package/src/ingest.ts +308 -12
  197. package/src/kg-extractor.ts +13 -3
  198. package/src/kg-store.ts +236 -44
  199. package/src/llm-client.ts +106 -11
  200. package/src/llm-consolidation.ts +2 -1
  201. package/src/mcp-server.ts +87 -1800
  202. package/src/mcp-tool-deps.ts +48 -0
  203. package/src/mcp-tools-advanced.ts +954 -0
  204. package/src/mcp-tools-core.ts +637 -0
  205. package/src/mcp-tools-governance.ts +475 -0
  206. package/src/memory-boundaries.ts +26 -3
  207. package/src/memory-evolution.ts +73 -1
  208. package/src/memory-health-rebalance.ts +4 -0
  209. package/src/memory-lint.ts +183 -10
  210. package/src/memory-output.ts +129 -4
  211. package/src/memory-promotion.ts +246 -0
  212. package/src/memory-schema.ts +25 -2
  213. package/src/memory-store-port.ts +46 -0
  214. package/src/multi-vector.ts +2 -1
  215. package/src/narrative-schema.ts +2 -1
  216. package/src/noise-filter.ts +57 -6
  217. package/src/noise-prototype-bank.ts +408 -0
  218. package/src/pii-detector.ts +207 -5
  219. package/src/pivot-apply.ts +399 -0
  220. package/src/preference-matcher.ts +37 -3
  221. package/src/query-expander.ts +90 -9
  222. package/src/recall-gate.ts +305 -0
  223. package/src/result-synthesizer.ts +2 -1
  224. package/src/retention-policy.ts +2 -1
  225. package/src/retrieval-profiles.ts +46 -4
  226. package/src/retriever.ts +354 -157
  227. package/src/retroactive-boost.ts +6 -2
  228. package/src/runtime-config.ts +78 -6
  229. package/src/scope-suggester.ts +113 -0
  230. package/src/session-distiller.ts +23 -2
  231. package/src/session-engine.ts +2 -1
  232. package/src/session-schema.ts +1 -0
  233. package/src/session-store.ts +5 -0
  234. package/src/skill-engine.ts +78 -0
  235. package/src/skill-promotion.ts +304 -48
  236. package/src/skill-schema.ts +16 -3
  237. package/src/skill-verifier.ts +152 -0
  238. package/src/store.ts +321 -57
  239. package/src/term-registry.ts +35 -0
  240. package/src/tool-output-compressor.ts +3 -1
  241. package/src/ui-server.ts +9 -1
  242. package/src/usage-tracker.ts +242 -0
  243. package/src/version-manager.ts +3 -2
  244. package/src/version.ts +19 -0
  245. package/src/workflow-observation-engine.ts +2 -1
  246. package/src/workflow-observation-schema.ts +2 -1
  247. package/src/workflow-observation-store.ts +6 -2
  248. package/.env +0 -4
  249. package/CLAUDE.md +0 -46
  250. package/config.json +0 -55
  251. package/daily-recall.sh +0 -110
  252. package/logs/daily-recall.error.log +0 -0
  253. package/logs/daily-recall.log +0 -60
  254. package/logs/ingest-2026-04-11.log +0 -110
  255. package/logs/ingest-2026-04-12.log +0 -90
  256. package/logs/ingest-2026-04-13.log +0 -87
  257. package/logs/launchagent-stderr.log +0 -0
  258. package/logs/launchagent-stdout.log +0 -0
@@ -0,0 +1,98 @@
1
+ import { describe, expect, it } from "bun:test";
2
+
3
+ import { registerAdvancedTools } from "../mcp-tools-advanced.js";
4
+ import { registerGovernanceTools } from "../mcp-tools-governance.js";
5
+ import { runToolSafely } from "../error-taxonomy.js";
6
+
7
+ /**
8
+ * P1-7 第二阶段回归:验证「主操作错误」与「C 类逻辑失败」经 registerTool 的
9
+ * runToolSafely 包装后带 isError + reason_code,而不是被 handler 自吞成「看起来成功」的文本。
10
+ *
11
+ * 手法:用注入式 registerTool 捕获真实 handler,并复刻生产 registerTool 的 runToolSafely
12
+ * 包装,使测试看到与生产一致的 isError 行为(无需起 MCP server)。
13
+ */
14
+
15
+ interface ToolResult {
16
+ content: { type: string; text: string }[];
17
+ isError?: boolean;
18
+ }
19
+ type CapturedHandler = (args: Record<string, unknown>) => Promise<ToolResult>;
20
+
21
+ function captureTools(
22
+ register: (deps: any) => void,
23
+ getComponents: () => unknown,
24
+ ): Map<string, CapturedHandler> {
25
+ const handlers = new Map<string, CapturedHandler>();
26
+ const deps = {
27
+ registerTool: (
28
+ name: string,
29
+ _desc: string,
30
+ _schema: unknown,
31
+ handler: (...a: unknown[]) => unknown,
32
+ ) => {
33
+ // 复刻 mcp-server.ts registerTool 的兜底:把 handler 交给 runToolSafely。
34
+ handlers.set(name, (args) => runToolSafely(name, async () => handler(args)) as Promise<ToolResult>);
35
+ },
36
+ getComponents,
37
+ conflictStore: {},
38
+ workflowObservationStore: {},
39
+ getKGExtractor: () => null,
40
+ getKGStore: () => null,
41
+ };
42
+ register(deps as any);
43
+ return handlers;
44
+ }
45
+
46
+ describe("memory_drill_down error surfacing (P1-7)", () => {
47
+ it("surfaces a store error as isError with store_error reason (no swallow)", async () => {
48
+ const handlers = captureTools(registerAdvancedTools, () => ({
49
+ store: {
50
+ getById: async () => {
51
+ throw new Error("lancedb table read failed");
52
+ },
53
+ },
54
+ }));
55
+ const drill = handlers.get("memory_drill_down");
56
+ expect(drill).toBeDefined();
57
+ const r = await drill!({ id: "abcd1234", level: "full" });
58
+ expect(r.isError).toBe(true);
59
+ expect(r.content[0].text).toContain("store_error");
60
+ });
61
+
62
+ it("still returns plain not-found text (read-only, no isError)", async () => {
63
+ const handlers = captureTools(registerAdvancedTools, () => ({
64
+ store: { getById: async () => null },
65
+ }));
66
+ const r = await handlers.get("memory_drill_down")!({ id: "abcd1234", level: "full" });
67
+ expect(r.isError).toBeUndefined();
68
+ expect(r.content[0].text).toContain("No memory found");
69
+ });
70
+ });
71
+
72
+ describe("forget_memory error surfacing (P1-7 C 类)", () => {
73
+ it("throws logic failure into the taxonomy layer (isError) not fake-success text", async () => {
74
+ const handlers = captureTools(registerAdvancedTools, () => ({
75
+ // store.get → null ⇒ forgetMemory 早返回 success:false(不触碰 kg/audit)
76
+ store: { get: async () => null },
77
+ }));
78
+ const r = await handlers.get("forget_memory")!({ memoryId: "abcd1234", confirm: false });
79
+ expect(r.isError).toBe(true);
80
+ expect(r.content[0].text).toContain("not found");
81
+ });
82
+ });
83
+
84
+ describe("manage_alias error surfacing (P1-7 C 类)", () => {
85
+ it("throws invalid_input on missing add params (not returned as success text)", async () => {
86
+ const handlers = captureTools(registerGovernanceTools, () => ({}));
87
+ const r = await handlers.get("manage_alias")!({ action: "add" });
88
+ expect(r.isError).toBe(true);
89
+ expect(r.content[0].text).toContain("invalid_input");
90
+ });
91
+
92
+ it("throws invalid_input on missing remove trigger", async () => {
93
+ const handlers = captureTools(registerGovernanceTools, () => ({}));
94
+ const r = await handlers.get("manage_alias")!({ action: "remove" });
95
+ expect(r.isError).toBe(true);
96
+ expect(r.content[0].text).toContain("invalid_input");
97
+ });
98
+ });
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { compressToolOutput } from "../tool-output-compressor.js";
3
+
4
+ describe("compressToolOutput", () => {
5
+ // ========================================================================
6
+ // Base64 replacement
7
+ // ========================================================================
8
+
9
+ it("replaces large base64 blocks with size placeholder", () => {
10
+ const base64 = "A".repeat(2000);
11
+ const text = `Here is a screenshot:\n${base64}\nEnd of screenshot.`;
12
+ const result = compressToolOutput(text);
13
+ expect(result).toContain("[image:");
14
+ expect(result).toContain("base64");
15
+ expect(result).not.toContain("AAAA");
16
+ });
17
+
18
+ it("preserves short base64-like strings", () => {
19
+ const text = "The hash is abc123==";
20
+ expect(compressToolOutput(text)).toBe(text);
21
+ });
22
+
23
+ // ========================================================================
24
+ // Git output compression
25
+ // ========================================================================
26
+
27
+ it("compresses git push boilerplate", () => {
28
+ const text = `$ git push origin main
29
+ Enumerating objects: 5, done.
30
+ Counting objects: 100% (5/5), done.
31
+ Delta compression using up to 8 threads
32
+ Compressing objects: 100% (3/3), done.
33
+ Writing objects: 100% (3/3), 450 bytes | 450.00 KiB/s, done.
34
+ Total 3 (delta 2), reused 0 (delta 0)
35
+ remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
36
+ To github.com:user/repo.git
37
+ abc1234..def5678 main -> main
38
+
39
+ Next I'll check the status.`;
40
+
41
+ const result = compressToolOutput(text);
42
+ expect(result).toContain("[git: ok");
43
+ expect(result).toContain("main");
44
+ expect(result).not.toContain("Enumerating objects");
45
+ expect(result).toContain("Next I'll check the status");
46
+ });
47
+
48
+ it("preserves git push failures", () => {
49
+ const text = `$ git push
50
+ Enumerating objects: 5, done.
51
+ error: failed to push some refs to 'origin'
52
+ hint: Updates were rejected because the tip of your branch is behind -> main`;
53
+
54
+ const result = compressToolOutput(text);
55
+ expect(result).toContain("error");
56
+ expect(result).toContain("rejected");
57
+ });
58
+
59
+ // ========================================================================
60
+ // Test output compression
61
+ // ========================================================================
62
+
63
+ it("compresses passing test summary", () => {
64
+ const text = `$ bun test
65
+ running 15 tests
66
+ test a ... ok
67
+ test b ... ok
68
+ test c ... ok
69
+ test result: ok. 15 passed; 0 failed; 0 ignored
70
+
71
+ All tests passed.`;
72
+
73
+ const result = compressToolOutput(text);
74
+ expect(result.length).toBeLessThanOrEqual(text.length);
75
+ expect(result).toContain("All tests passed");
76
+ });
77
+
78
+ it("preserves failing test output", () => {
79
+ const text = `$ npm test
80
+ Tests: 2 failed, 8 passed
81
+ FAIL src/main.test.ts
82
+ ✕ should handle edge case`;
83
+
84
+ const result = compressToolOutput(text);
85
+ expect(result).toContain("failed");
86
+ expect(result).toContain("FAIL");
87
+ });
88
+
89
+ // ========================================================================
90
+ // Safety: user/AI content untouched
91
+ // ========================================================================
92
+
93
+ it("never modifies plain conversation text", () => {
94
+ const text = "User: How do I fix this git push error?\n\nAssistant: You need to pull first.";
95
+ expect(compressToolOutput(text)).toBe(text);
96
+ });
97
+
98
+ it("preserves AI reasoning about tool results", () => {
99
+ const text = "The test result shows 15 passed, which means our fix works.";
100
+ expect(compressToolOutput(text)).toBe(text);
101
+ });
102
+
103
+ // ========================================================================
104
+ // Edge cases
105
+ // ========================================================================
106
+
107
+ it("handles empty input", () => {
108
+ expect(compressToolOutput("")).toBe("");
109
+ });
110
+
111
+ it("handles short input", () => {
112
+ expect(compressToolOutput("hi")).toBe("hi");
113
+ });
114
+
115
+ it("handles text without tool outputs", () => {
116
+ const text = "This is a normal conversation.\nNo tool outputs here.";
117
+ expect(compressToolOutput(text)).toBe(text);
118
+ });
119
+
120
+ // ========================================================================
121
+ // Large output truncation
122
+ // ========================================================================
123
+
124
+ it("truncates large unmatched tool output", () => {
125
+ // Use content with spaces/special chars to avoid base64 regex match
126
+ const largeOutput = Array.from({ length: 150 }, (_, i) => `line ${i}: some detailed output here`).join("\n");
127
+ const text = `$ some-command\n${largeOutput}\n\nNext step.`;
128
+ const result = compressToolOutput(text);
129
+ expect(result).toContain("[...");
130
+ expect(result).toContain("chars truncated");
131
+ expect(result.length).toBeLessThan(text.length);
132
+ });
133
+
134
+ // ========================================================================
135
+ // 回归:工具输出块位于字符串结尾(原 \z 锚点 bug)
136
+ // 旧实现 lookahead 末项是 \z(JS 无此锚点 → 当字面字符 z):
137
+ // - 块结尾无字面 z 时整块漏匹配 → 末尾大输出逃过截断
138
+ // - 块含字面 z 时惰性匹配在第一个 z 处误截断
139
+ // 修复为 (?![\s\S])(字符串绝对结尾)后两种都应正确处理。
140
+ // ========================================================================
141
+
142
+ it("truncates a large tool output block that ends the string (no trailing marker)", () => {
143
+ // 末尾块、无字母 z、后面没有下一个命令/空行大写/## → 旧 \z 实现整块漏匹配
144
+ const largeOutput = Array.from({ length: 150 }, (_, i) => `line ${i}: detailed output here`).join("\n");
145
+ const text = `$ some-command\n${largeOutput}`;
146
+ const result = compressToolOutput(text);
147
+ expect(result).toContain("chars truncated");
148
+ expect(result.length).toBeLessThan(text.length);
149
+ });
150
+
151
+ it("does not cut a trailing tool output block at a literal 'z'", () => {
152
+ // 末尾大块含字母 z → 旧 \z 实现在第一个 z 处截断匹配,导致块太短逃过截断
153
+ const largeOutput = Array.from({ length: 150 }, (_, i) => `line ${i}: zebra output zone`).join("\n");
154
+ const text = `$ some-command\n${largeOutput}`;
155
+ const result = compressToolOutput(text);
156
+ expect(result).toContain("chars truncated");
157
+ // 截断尾部应保留块真正的结尾,而不是停在某个 z 之前
158
+ expect(result).toContain("zone");
159
+ });
160
+ });
@@ -0,0 +1,113 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { detectTopicTag, extractTopicTag, injectTopicTag } from "../topic-tag.js";
3
+
4
+ describe("detectTopicTag", () => {
5
+ it("detects auth-related content", () => {
6
+ expect(detectTopicTag("Implement JWT authentication with OAuth2 flow")).toBe("auth");
7
+ });
8
+
9
+ it("detects deploy-related content", () => {
10
+ expect(detectTopicTag("Set up CI/CD pipeline with GitHub Actions for production deployment")).toBe("deploy");
11
+ });
12
+
13
+ it("detects infra-related content", () => {
14
+ expect(detectTopicTag("Configure Docker containers and Kubernetes cluster")).toBe("infra");
15
+ });
16
+
17
+ it("detects testing-related content", () => {
18
+ expect(detectTopicTag("Write unit tests with bun test and add coverage for the new module")).toBe("testing");
19
+ });
20
+
21
+ it("detects database-related content", () => {
22
+ expect(detectTopicTag("Run SQL migration to add new columns to postgres users table")).toBe("database");
23
+ });
24
+
25
+ it("detects api-related content", () => {
26
+ expect(detectTopicTag("Design REST API endpoints for the new webhook integration")).toBe("api");
27
+ });
28
+
29
+ it("detects ui-related content", () => {
30
+ expect(detectTopicTag("Build React component with Tailwind CSS for the dashboard layout")).toBe("ui");
31
+ });
32
+
33
+ it("detects performance-related content", () => {
34
+ expect(detectTopicTag("Profile cache hit rates and optimize latency bottleneck")).toBe("perf");
35
+ });
36
+
37
+ it("detects security-related content", () => {
38
+ expect(detectTopicTag("Fix XSS vulnerability and add CSRF protection")).toBe("security");
39
+ });
40
+
41
+ it("detects memory-related content", () => {
42
+ expect(detectTopicTag("Improve recall retrieval with better embedding vectors for LanceDB")).toBe("memory");
43
+ });
44
+
45
+ it("detects mcp-related content", () => {
46
+ expect(detectTopicTag("Register new MCP tool in the Model Context Protocol server")).toBe("mcp");
47
+ });
48
+
49
+ it("detects llm-related content", () => {
50
+ expect(detectTopicTag("Optimize prompt engineering for better token efficiency with Claude")).toBe("llm");
51
+ });
52
+
53
+ it("returns undefined for generic text", () => {
54
+ expect(detectTopicTag("Had a great day today")).toBeUndefined();
55
+ });
56
+
57
+ it("returns undefined for empty text", () => {
58
+ expect(detectTopicTag("")).toBeUndefined();
59
+ });
60
+
61
+ it("picks strongest match when multiple topics present", () => {
62
+ const tag = detectTopicTag("Deploy the authentication service to production using Docker");
63
+ expect(tag).toBeDefined();
64
+ // Multiple topics present; strongest wins
65
+ expect(["auth", "deploy", "infra"]).toContain(tag);
66
+ });
67
+
68
+ it("handles Chinese text with English tech terms", () => {
69
+ expect(detectTopicTag("配置 Docker 容器编排和 Kubernetes 集群")).toBe("infra");
70
+ });
71
+
72
+ it("truncates very long text for efficiency", () => {
73
+ const longText = "Fix authentication bug. ".repeat(500);
74
+ expect(detectTopicTag(longText)).toBe("auth");
75
+ });
76
+ });
77
+
78
+ describe("extractTopicTag", () => {
79
+ it("extracts topicTag from metadata JSON", () => {
80
+ expect(extractTopicTag('{"topicTag":"auth","source":"manual"}')).toBe("auth");
81
+ });
82
+
83
+ it("returns undefined when no topicTag", () => {
84
+ expect(extractTopicTag('{"source":"manual"}')).toBeUndefined();
85
+ });
86
+
87
+ it("returns undefined for invalid JSON", () => {
88
+ expect(extractTopicTag("not json")).toBeUndefined();
89
+ });
90
+
91
+ it("returns undefined for undefined/null metadata", () => {
92
+ expect(extractTopicTag(undefined)).toBeUndefined();
93
+ expect(extractTopicTag("")).toBeUndefined();
94
+ });
95
+ });
96
+
97
+ describe("injectTopicTag", () => {
98
+ it("injects topicTag into metadata JSON", () => {
99
+ const result = injectTopicTag('{"source":"manual"}', "deploy");
100
+ const parsed = JSON.parse(result);
101
+ expect(parsed.topicTag).toBe("deploy");
102
+ expect(parsed.source).toBe("manual");
103
+ });
104
+
105
+ it("overwrites existing topicTag", () => {
106
+ const result = injectTopicTag('{"topicTag":"old","source":"manual"}', "new");
107
+ expect(JSON.parse(result).topicTag).toBe("new");
108
+ });
109
+
110
+ it("returns original on invalid JSON", () => {
111
+ expect(injectTopicTag("broken", "tag")).toBe("broken");
112
+ });
113
+ });