@unerr-ai/unerr 0.1.6 → 0.1.7

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 (218) hide show
  1. package/README.md +70 -194
  2. package/dist/cli.js +39149 -36991
  3. package/package.json +9 -2
  4. package/dist/__tests__/architecture-guard.test.js +0 -122
  5. package/dist/__tests__/arg-validator.test.js +0 -205
  6. package/dist/__tests__/ast-extractor.test.js +0 -203
  7. package/dist/__tests__/auto-bootstrap.test.js +0 -280
  8. package/dist/__tests__/background-indexer.test.js +0 -228
  9. package/dist/__tests__/blast-radius-engine.test.js +0 -200
  10. package/dist/__tests__/bridge-isolation.test.js +0 -37
  11. package/dist/__tests__/budget-enforcer.test.js +0 -53
  12. package/dist/__tests__/cfg-test-detection-perf.test.js +0 -82
  13. package/dist/__tests__/change-narrative.test.js +0 -190
  14. package/dist/__tests__/check-commit.test.js +0 -258
  15. package/dist/__tests__/checksum.test.js +0 -34
  16. package/dist/__tests__/commit-watcher.test.js +0 -154
  17. package/dist/__tests__/community-detection.test.js +0 -179
  18. package/dist/__tests__/community-tools.test.js +0 -299
  19. package/dist/__tests__/components.test.js +0 -449
  20. package/dist/__tests__/compression-log.test.js +0 -174
  21. package/dist/__tests__/compression-quality-monitor.test.js +0 -40
  22. package/dist/__tests__/config-healer.test.js +0 -165
  23. package/dist/__tests__/context-ledger.test.js +0 -58
  24. package/dist/__tests__/convention-detector.test.js +0 -99
  25. package/dist/__tests__/convention-learner.test.js +0 -86
  26. package/dist/__tests__/correction-detector.test.js +0 -330
  27. package/dist/__tests__/daemon-autostart-install.test.js +0 -283
  28. package/dist/__tests__/daemon-bridge.test.js +0 -222
  29. package/dist/__tests__/daemon-dashboard.test.js +0 -202
  30. package/dist/__tests__/daemon-registry.test.js +0 -240
  31. package/dist/__tests__/daemon-supervisor.test.js +0 -318
  32. package/dist/__tests__/daemon-version-check.test.js +0 -275
  33. package/dist/__tests__/decision-point-detector.test.js +0 -98
  34. package/dist/__tests__/deep-link.test.js +0 -143
  35. package/dist/__tests__/disallowed-tools.test.js +0 -115
  36. package/dist/__tests__/drift-tracker.test.js +0 -582
  37. package/dist/__tests__/durability-scorer.test.js +0 -152
  38. package/dist/__tests__/efficiency-tracker.test.js +0 -65
  39. package/dist/__tests__/enrich.test.js +0 -144
  40. package/dist/__tests__/entity-rewind.test.js +0 -248
  41. package/dist/__tests__/ephemeral.test.js +0 -111
  42. package/dist/__tests__/exploration-cost.test.js +0 -93
  43. package/dist/__tests__/fact-generator.test.js +0 -197
  44. package/dist/__tests__/file-l0-graph.test.js +0 -244
  45. package/dist/__tests__/file-logger.test.js +0 -82
  46. package/dist/__tests__/file-outline.test.js +0 -141
  47. package/dist/__tests__/file-read-protocol.test.js +0 -188
  48. package/dist/__tests__/format-encoder.test.js +0 -233
  49. package/dist/__tests__/git-attribution.test.js +0 -259
  50. package/dist/__tests__/graph-temporal-joiner.test.js +0 -219
  51. package/dist/__tests__/health-grade-enhanced.test.js +0 -138
  52. package/dist/__tests__/health-map-data.test.js +0 -173
  53. package/dist/__tests__/helpers/mcp-harness.js +0 -45
  54. package/dist/__tests__/helpers/mcp-harness.test.js +0 -68
  55. package/dist/__tests__/hook-dedup.test.js +0 -112
  56. package/dist/__tests__/hook-runner.test.js +0 -253
  57. package/dist/__tests__/indexer-cfg.test.js +0 -185
  58. package/dist/__tests__/indexer-cross-file.test.js +0 -172
  59. package/dist/__tests__/indexer-extraction.test.js +0 -245
  60. package/dist/__tests__/indexer-incremental.test.js +0 -232
  61. package/dist/__tests__/indexer-language-expansion.test.js +0 -165
  62. package/dist/__tests__/init-push.test.js +0 -131
  63. package/dist/__tests__/instruction-writer.test.js +0 -179
  64. package/dist/__tests__/intelligence-integration.test.js +0 -217
  65. package/dist/__tests__/intent-correlator.test.js +0 -175
  66. package/dist/__tests__/intent-detector.test.js +0 -235
  67. package/dist/__tests__/intent-encoder.test.js +0 -167
  68. package/dist/__tests__/java-build-tool-detection.test.js +0 -174
  69. package/dist/__tests__/layer3-sprint-q.test.js +0 -160
  70. package/dist/__tests__/layer3-sprint-r.test.js +0 -91
  71. package/dist/__tests__/layer3-sprint-s.test.js +0 -183
  72. package/dist/__tests__/layer3-sprint-t.test.js +0 -201
  73. package/dist/__tests__/layer3-sprint-u.test.js +0 -174
  74. package/dist/__tests__/layer4-sprint-ba2.test.js +0 -354
  75. package/dist/__tests__/layer4-sprint-ba4.test.js +0 -84
  76. package/dist/__tests__/layer4-sprint-vs.test.js +0 -105
  77. package/dist/__tests__/ledger-chains.test.js +0 -162
  78. package/dist/__tests__/lifecycle-machine.test.js +0 -226
  79. package/dist/__tests__/local-chat-provider.test.js +0 -170
  80. package/dist/__tests__/local-convention-detector.test.js +0 -308
  81. package/dist/__tests__/local-embeddings.test.js +0 -422
  82. package/dist/__tests__/local-graph.test.js +0 -540
  83. package/dist/__tests__/local-indexer.test.js +0 -228
  84. package/dist/__tests__/local-intelligence-l3.test.js +0 -332
  85. package/dist/__tests__/local-llm.test.js +0 -253
  86. package/dist/__tests__/local-mode-offline.test.js +0 -187
  87. package/dist/__tests__/local-mode-stats.test.js +0 -273
  88. package/dist/__tests__/local-mode-tui.test.js +0 -343
  89. package/dist/__tests__/local-parse.test.js +0 -199
  90. package/dist/__tests__/log-tailer.test.js +0 -208
  91. package/dist/__tests__/loop-breaker.test.js +0 -276
  92. package/dist/__tests__/loop-miner.test.js +0 -226
  93. package/dist/__tests__/mcp-config.test.js +0 -126
  94. package/dist/__tests__/mcp-content-json.test.js +0 -10
  95. package/dist/__tests__/mcp-envelope.test.js +0 -124
  96. package/dist/__tests__/metrics-store.test.js +0 -223
  97. package/dist/__tests__/native-watcher.test.js +0 -191
  98. package/dist/__tests__/navigation-hooks-agent-aware.test.js +0 -145
  99. package/dist/__tests__/negative-knowledge.test.js +0 -116
  100. package/dist/__tests__/network-boundary.test.js +0 -190
  101. package/dist/__tests__/network-firewall.test.js +0 -112
  102. package/dist/__tests__/nudge-invariants.test.js +0 -160
  103. package/dist/__tests__/nudge-v2.test.js +0 -225
  104. package/dist/__tests__/offline-rewind.test.js +0 -251
  105. package/dist/__tests__/open-threads.test.js +0 -89
  106. package/dist/__tests__/output-compressor.test.js +0 -93
  107. package/dist/__tests__/pending-violations.test.js +0 -112
  108. package/dist/__tests__/persistence-effectiveness.test.js +0 -143
  109. package/dist/__tests__/provider-factory.test.js +0 -42
  110. package/dist/__tests__/providers.test.js +0 -24
  111. package/dist/__tests__/proxy.test.js +0 -314
  112. package/dist/__tests__/query-router.test.js +0 -1018
  113. package/dist/__tests__/reasoning-quality-route.test.js +0 -138
  114. package/dist/__tests__/redactor.test.js +0 -120
  115. package/dist/__tests__/resource-monitor.test.js +0 -57
  116. package/dist/__tests__/response-envelope.test.js +0 -100
  117. package/dist/__tests__/risk-classifier.test.js +0 -101
  118. package/dist/__tests__/risk-signal-scope.test.js +0 -75
  119. package/dist/__tests__/rule-evaluator.test.js +0 -280
  120. package/dist/__tests__/scip-decoder.test.js +0 -49
  121. package/dist/__tests__/scip-downloader.test.js +0 -201
  122. package/dist/__tests__/scip-merger.test.js +0 -103
  123. package/dist/__tests__/search-index.test.js +0 -422
  124. package/dist/__tests__/semantic-enrichment.test.js +0 -360
  125. package/dist/__tests__/session-brief-builder.test.js +0 -187
  126. package/dist/__tests__/session-context.test.js +0 -221
  127. package/dist/__tests__/session-continuity.test.js +0 -144
  128. package/dist/__tests__/session-dedup.test.js +0 -74
  129. package/dist/__tests__/session-event-wiring.test.js +0 -206
  130. package/dist/__tests__/session-events.test.js +0 -149
  131. package/dist/__tests__/session-legend.test.js +0 -20
  132. package/dist/__tests__/session-persistence.test.js +0 -131
  133. package/dist/__tests__/session-resume-block.test.js +0 -107
  134. package/dist/__tests__/session-resume.test.js +0 -97
  135. package/dist/__tests__/session-summary-writer.test.js +0 -134
  136. package/dist/__tests__/shadow-ledger.test.js +0 -203
  137. package/dist/__tests__/shell-classifier.test.js +0 -151
  138. package/dist/__tests__/shell-compression-floor.test.js +0 -189
  139. package/dist/__tests__/shell-compression-v2.test.js +0 -339
  140. package/dist/__tests__/shell-compressor.test.js +0 -35
  141. package/dist/__tests__/shell-hooks.test.js +0 -128
  142. package/dist/__tests__/shell-strategies.test.js +0 -644
  143. package/dist/__tests__/shell-tee.test.js +0 -133
  144. package/dist/__tests__/signal-dedup.test.js +0 -158
  145. package/dist/__tests__/signal-reinforcer.test.js +0 -77
  146. package/dist/__tests__/signal-scorer.test.js +0 -251
  147. package/dist/__tests__/signal-show-store.test.js +0 -108
  148. package/dist/__tests__/smart-truncate.test.js +0 -215
  149. package/dist/__tests__/snapshot-v2.test.js +0 -113
  150. package/dist/__tests__/sprint-l1-local-mode.test.js +0 -130
  151. package/dist/__tests__/sprint-l10-boot.test.js +0 -220
  152. package/dist/__tests__/sprint-l9-offline-commands.test.js +0 -189
  153. package/dist/__tests__/sprint-q-persistent-context.test.js +0 -198
  154. package/dist/__tests__/sprint-s1-wiring.test.js +0 -215
  155. package/dist/__tests__/sprint-s2-wiring.test.js +0 -256
  156. package/dist/__tests__/sprint-s3-wiring.test.js +0 -195
  157. package/dist/__tests__/sprint-s4-wiring.test.js +0 -213
  158. package/dist/__tests__/sprint-s6-hooks.test.js +0 -222
  159. package/dist/__tests__/sprint-s7-persistent.test.js +0 -263
  160. package/dist/__tests__/sprint-s8-value.test.js +0 -167
  161. package/dist/__tests__/sprint-s9-behavioral.test.js +0 -179
  162. package/dist/__tests__/sprint3-intelligence.test.js +0 -297
  163. package/dist/__tests__/sprint5-mcp-server.test.js +0 -136
  164. package/dist/__tests__/startup-display.test.js +0 -302
  165. package/dist/__tests__/startup-log-file.test.js +0 -97
  166. package/dist/__tests__/stash-manager.test.js +0 -229
  167. package/dist/__tests__/state-detector.test.js +0 -92
  168. package/dist/__tests__/status-dashboard.test.js +0 -142
  169. package/dist/__tests__/temporal-facts.test.js +0 -292
  170. package/dist/__tests__/temporal-routes.test.js +0 -142
  171. package/dist/__tests__/test-detector.test.js +0 -174
  172. package/dist/__tests__/theme.test.js +0 -72
  173. package/dist/__tests__/timeline-agents.test.js +0 -122
  174. package/dist/__tests__/timeline-bootstrap.test.js +0 -176
  175. package/dist/__tests__/timeline-filters.test.js +0 -193
  176. package/dist/__tests__/timeline-markers.test.js +0 -151
  177. package/dist/__tests__/timeline-routes.test.js +0 -156
  178. package/dist/__tests__/timeline-store.test.js +0 -171
  179. package/dist/__tests__/token-counter.test.js +0 -86
  180. package/dist/__tests__/token-estimator.test.js +0 -96
  181. package/dist/__tests__/token-flow-api.test.js +0 -239
  182. package/dist/__tests__/token-flow-instrumentation.test.js +0 -437
  183. package/dist/__tests__/token-flow-persistence.test.js +0 -356
  184. package/dist/__tests__/token-flow-routes.test.js +0 -199
  185. package/dist/__tests__/token-flow.test.js +0 -695
  186. package/dist/__tests__/tool-clusters.test.js +0 -177
  187. package/dist/__tests__/transport-mux.test.js +0 -283
  188. package/dist/__tests__/turn-segmenter.test.js +0 -166
  189. package/dist/__tests__/uninstall.test.js +0 -141
  190. package/dist/__tests__/warm-start-policy.test.js +0 -271
  191. package/dist/__tests__/wire-cap-nudge.test.js +0 -77
  192. package/dist/__tests__/worker-pool.test.js +0 -101
  193. package/dist/ui/assets/index-7gl3mIuY.css +0 -1
  194. package/dist/ui/assets/index-CX4FCWGT.js +0 -10
  195. package/dist/ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
  196. package/dist/ui/assets/vis-network-NIJHUFI3.js +0 -908
  197. package/dist/ui/fonts/jetbrains-mono-latin-400-normal.woff +0 -0
  198. package/dist/ui/icon-wordmark.png +0 -0
  199. package/dist/ui/icon-wordmark.svg +0 -30
  200. package/dist/ui/icon.png +0 -0
  201. package/dist/ui/icon.svg +0 -25
  202. package/dist/ui/index.html +0 -15
  203. package/dist/ui/prototype-sandbox/index.html +0 -257
  204. package/dist/ui/screenshots/activity.png +0 -0
  205. package/dist/ui/screenshots/code-base-intelligence.png +0 -0
  206. package/dist/ui/screenshots/dashboard.png +0 -0
  207. package/dist/ui/screenshots/project-memory.png +0 -0
  208. package/dist/ui/screenshots/reasoning-quality.png +0 -0
  209. package/dist/ui/screenshots/reasoning-session.png +0 -0
  210. package/dist/ui/screenshots/token-session.png +0 -0
  211. package/dist/ui/screenshots/token-trace-main.png +0 -0
  212. package/dist/ui/screenshots/token-turn.png +0 -0
  213. package/dist/ui/unerr-wordmark.png +0 -0
  214. package/dist/ui/unerr-wordmark.svg +0 -9
  215. package/dist/ui/unerr.png +0 -0
  216. package/dist/ui/unerr.svg +0 -25
  217. package/dist/ui/web-app-manifest-192x192.png +0 -0
  218. package/dist/ui/web-app-manifest-512x512.png +0 -0
@@ -1,215 +0,0 @@
1
- /**
2
- * Sprint S1: Output Compression & Quality Loop — Integration Tests.
3
- *
4
- * Verifies:
5
- * - Large text output (>2K tokens) is automatically compressed
6
- * - Session dedup filters repeated _context keys for same entity
7
- * - Quality monitor adapts retention on retry spikes
8
- * - Budget enforcer caps total response size
9
- * - Retry detection feeds into quality monitor
10
- */
11
- import { describe, expect, it, vi } from "vitest";
12
- import { QueryRouter } from "../intelligence/query-router.js";
13
- import { createCompressionQualityMonitor } from "../proxy/compression-quality-monitor.js";
14
- import { createSessionDedup } from "../proxy/session-dedup.js";
15
- function createMockGraph(overrides = {}) {
16
- return {
17
- getEntity: vi.fn().mockReturnValue({
18
- key: "fn1",
19
- name: "fn1",
20
- kind: "function",
21
- file_path: "src/main.ts",
22
- risk_level: "normal",
23
- }),
24
- getBlastRadius: vi.fn().mockReturnValue({
25
- direct_callers: 5,
26
- direct_callees: 2,
27
- transitive_count: 10,
28
- is_chokepoint: false,
29
- summary: "5 callers, 10 transitive dependents",
30
- }),
31
- getBlastRadiusEntities: vi.fn().mockReturnValue(["caller1", "caller2"]),
32
- getConventionsForEntity: vi.fn().mockReturnValue([]),
33
- getCorrections: vi.fn().mockReturnValue([]),
34
- getCommunityForEntity: vi.fn().mockReturnValue(null),
35
- getCrossCommunityEdges: vi.fn().mockReturnValue([]),
36
- getEntitiesByFile: vi.fn().mockReturnValue([]),
37
- queryEntities: vi.fn().mockReturnValue([]),
38
- searchEntities: vi.fn().mockReturnValue([]),
39
- getDriftOverlayEntity: vi.fn().mockReturnValue(null),
40
- getDriftSummary: vi
41
- .fn()
42
- .mockReturnValue({ added: 0, modified: 0, removed: 0, total: 0 }),
43
- getCriticalNodes: vi.fn().mockReturnValue([]),
44
- getCrossBoundaryLinks: vi.fn().mockReturnValue([]),
45
- getCallers: vi.fn().mockReturnValue([]),
46
- getCallersOf: vi.fn().mockReturnValue([]),
47
- getCalleesOf: vi.fn().mockReturnValue([]),
48
- getCallees: vi.fn().mockReturnValue([]),
49
- getImports: vi.fn().mockReturnValue([]),
50
- getRules: vi.fn().mockReturnValue([]),
51
- getJustificationsForEntity: vi.fn().mockReturnValue([]),
52
- getDriftEntitiesForFile: vi.fn().mockReturnValue([]),
53
- close: vi.fn(),
54
- ...overrides,
55
- };
56
- }
57
- describe("Sprint S1: Output Compression Wiring", () => {
58
- describe("S1.5: Compression triggered on large text output", () => {
59
- it("compresses large string results exceeding 2K tokens", async () => {
60
- // search_code returning a large string simulates a tool that returns raw text
61
- const largeText = "line of code here\n".repeat(2000); // ~8K tokens
62
- const graph = createMockGraph({
63
- searchEntities: vi.fn().mockReturnValue(largeText),
64
- });
65
- const router = new QueryRouter(graph);
66
- const monitor = createCompressionQualityMonitor();
67
- router.setCompressionMonitor(monitor);
68
- const result = await router.execute("search_code", { query: "test" });
69
- // The content should be compressed (shorter than original)
70
- const contentStr = typeof result.content === "string"
71
- ? result.content
72
- : JSON.stringify(result.content);
73
- expect(contentStr.length).toBeLessThan(largeText.length);
74
- });
75
- it("does not compress output under 2K tokens", async () => {
76
- const shortText = "function foo() { return 42; }";
77
- const graph = createMockGraph({
78
- searchEntities: vi.fn().mockReturnValue(shortText),
79
- });
80
- const router = new QueryRouter(graph);
81
- const result = await router.execute("search_code", { query: "foo" });
82
- // Short output passes through unchanged
83
- expect(result.content).toBe(shortText);
84
- });
85
- it("does not compress structured object results", async () => {
86
- const graph = createMockGraph();
87
- // get_function returns an entity object via resolveEntityWithOverlay
88
- const router = new QueryRouter(graph);
89
- const result = await router.execute("get_function", { key: "fn1" });
90
- // Structured objects pass through — no string compression
91
- expect(typeof result.content).toBe("object");
92
- });
93
- });
94
- // S1.4 checked `_context.blast_radius` as a direct field. After the
95
- // Three-Layer Experience System rollout (Sprint 8/9), that field was
96
- // replaced by `_context.signals[]`. Session dedup still works — it now
97
- // dedups individual signals — but the wire shape changed. Coverage
98
- // continues in signal-scorer.test.ts (dedup branch).
99
- describe.skip("S1.4: Session dedup filters repeated _context (deprecated — see signal-scorer.test.ts)", () => {
100
- it("first call for entity delivers blast_radius in _context", async () => {
101
- const graph = createMockGraph();
102
- const router = new QueryRouter(graph);
103
- const dedup = createSessionDedup();
104
- router.setSessionDedup(dedup);
105
- const r1 = await router.execute("get_function", { key: "fn1" });
106
- // First call should have blast_radius in _context
107
- expect(r1._context?.blast_radius).toBeDefined();
108
- });
109
- it("second call for same entity does not repeat blast_radius", async () => {
110
- const graph = createMockGraph();
111
- const router = new QueryRouter(graph);
112
- const dedup = createSessionDedup();
113
- router.setSessionDedup(dedup);
114
- // First call delivers blast_radius
115
- await router.execute("get_function", { key: "fn1" });
116
- // Second call — SessionContext already deduplicates via shouldInjectBlastRadius
117
- // AND session dedup filters any remaining repeated keys
118
- const r2 = await router.execute("get_function", { key: "fn1" });
119
- if (r2._context) {
120
- expect(r2._context.blast_radius).toBeUndefined();
121
- }
122
- });
123
- it("different entities get independent context delivery", async () => {
124
- const graph = createMockGraph();
125
- const router = new QueryRouter(graph);
126
- const dedup = createSessionDedup();
127
- router.setSessionDedup(dedup);
128
- await router.execute("get_function", { key: "fn1" });
129
- const r2 = await router.execute("get_function", { key: "fn2" });
130
- // fn2 is a fresh entity — should get blast_radius
131
- expect(r2._context?.blast_radius).toBeDefined();
132
- });
133
- });
134
- describe("S1.7-S1.9: Quality monitor integration", () => {
135
- it("feeds compression events into quality monitor on large output", async () => {
136
- const largeText = "error: test failure\n".repeat(2000);
137
- const graph = createMockGraph({
138
- searchEntities: vi.fn().mockReturnValue(largeText),
139
- });
140
- const router = new QueryRouter(graph);
141
- const monitor = createCompressionQualityMonitor();
142
- router.setCompressionMonitor(monitor);
143
- await router.execute("search_code", { query: "test" });
144
- // Adaptive config reflects the compression event was recorded
145
- const config = monitor.getAdaptiveConfig();
146
- expect(config).toBeDefined();
147
- expect(config.confidence).toBeGreaterThanOrEqual(0);
148
- });
149
- it("increases retention after repeated over-compression signals", () => {
150
- const monitor = createCompressionQualityMonitor();
151
- const initialRetention = monitor.getRetention("generic");
152
- // Simulate 3 compression + retry cycles (over-compression)
153
- for (let i = 0; i < 3; i++) {
154
- monitor.recordCompression(`c${i}`, "generic", 0.5);
155
- monitor.recordAgentAction(`entity-${i}`, true, false);
156
- }
157
- const adaptedRetention = monitor.getRetention("generic");
158
- expect(adaptedRetention).toBeGreaterThan(initialRetention);
159
- });
160
- it("reduces retention cautiously after 10+ successful compressions", () => {
161
- const monitor = createCompressionQualityMonitor();
162
- // First force retention up
163
- for (let i = 0; i < 3; i++) {
164
- monitor.recordCompression(`up${i}`, "generic", 0.5);
165
- monitor.recordAgentAction(`e${i}`, true, false);
166
- }
167
- const highRetention = monitor.getRetention("generic");
168
- // Then 10 good compressions
169
- for (let i = 0; i < 10; i++) {
170
- monitor.recordCompression(`good${i}`, "generic", 0.7);
171
- monitor.recordAgentAction(`g${i}`, false, false);
172
- }
173
- const loweredRetention = monitor.getRetention("generic");
174
- expect(loweredRetention).toBeLessThanOrEqual(highRetention);
175
- });
176
- });
177
- describe("S1.8: Retry detection in enrichResult", () => {
178
- it("detects retry when same entity queried within 60s", async () => {
179
- const graph = createMockGraph();
180
- const router = new QueryRouter(graph);
181
- const monitor = createCompressionQualityMonitor();
182
- router.setCompressionMonitor(monitor);
183
- // We need a compression event first for the monitor to register retries
184
- // Simulate by querying a tool that returns large text (triggers compression)
185
- const largeText = "content\n".repeat(2000);
186
- graph.searchEntities = vi.fn().mockReturnValue(largeText);
187
- await router.execute("search_code", { query: "a" });
188
- // Now query an enrichable tool twice within 60s
189
- await router.execute("get_function", { key: "fn1" });
190
- await router.execute("get_function", { key: "fn1" });
191
- // The second query should trigger retry detection
192
- // which feeds into the monitor (if there was a recent compression)
193
- expect(monitor.getSignalCount()).toBeGreaterThan(0);
194
- });
195
- });
196
- describe("S1.10: Budget enforcer as final cap", () => {
197
- it("caps extremely large multi-line output to within budget", async () => {
198
- // Real-world content has newlines — budget enforcer splits by line
199
- const hugeText = Array.from({ length: 5000 }, (_, i) => `line ${i}: some content that takes up space in the output buffer`).join("\n"); // ~5000 lines, ~300K chars, ~75K tokens
200
- const graph = createMockGraph({
201
- searchEntities: vi.fn().mockReturnValue(hugeText),
202
- });
203
- const router = new QueryRouter(graph);
204
- const monitor = createCompressionQualityMonitor();
205
- router.setCompressionMonitor(monitor);
206
- const result = await router.execute("search_code", { query: "line" });
207
- const contentStr = typeof result.content === "string"
208
- ? result.content
209
- : JSON.stringify(result.content);
210
- // Budget enforcer ensures content stays well under original size
211
- // 4K tokens ≈ 16K chars — allow some overhead from markers
212
- expect(contentStr.length).toBeLessThan(hugeText.length * 0.3);
213
- });
214
- });
215
- });
@@ -1,256 +0,0 @@
1
- /**
2
- * Sprint S2: Session Health & Exploration Cost — Integration Tests.
3
- *
4
- * Verifies:
5
- * - Every graph tool response includes _meta.tokens_saved (number > 0)
6
- * - After 3+ queries to same entity → health drops below 0.8
7
- * - After 10+ tool calls/min → tool_call_acceleration signal fires
8
- * - Session health warning injected when health < 0.6
9
- * - Exploration cost accumulator tracks cumulative savings
10
- * - Health monitor feeds from blast radius and convention violations
11
- */
12
- import { describe, expect, it, vi } from "vitest";
13
- import { createExplorationAccumulator } from "../intelligence/exploration-cost.js";
14
- import { QueryRouter } from "../intelligence/query-router.js";
15
- import { createSessionHealthMonitor } from "../intelligence/session-health-monitor.js";
16
- function createMockGraph(overrides = {}) {
17
- return {
18
- getEntity: vi.fn().mockReturnValue({
19
- key: "fn1",
20
- name: "fn1",
21
- kind: "function",
22
- file_path: "src/main.ts",
23
- risk_level: "normal",
24
- }),
25
- getBlastRadius: vi.fn().mockReturnValue({
26
- direct_callers: 5,
27
- direct_callees: 2,
28
- transitive_count: 10,
29
- is_chokepoint: false,
30
- summary: "5 callers, 10 transitive dependents",
31
- }),
32
- getBlastRadiusEntities: vi.fn().mockReturnValue(["caller1", "caller2"]),
33
- getConventionsForEntity: vi.fn().mockReturnValue([]),
34
- getCorrections: vi.fn().mockReturnValue([]),
35
- getCommunityForEntity: vi.fn().mockReturnValue(null),
36
- getCrossCommunityEdges: vi.fn().mockReturnValue([]),
37
- getEntitiesByFile: vi.fn().mockReturnValue([]),
38
- queryEntities: vi.fn().mockReturnValue([]),
39
- searchEntities: vi.fn().mockReturnValue([]),
40
- getDriftOverlayEntity: vi.fn().mockReturnValue(null),
41
- getDriftSummary: vi
42
- .fn()
43
- .mockReturnValue({ added: 0, modified: 0, removed: 0, total: 0 }),
44
- getCriticalNodes: vi.fn().mockReturnValue([]),
45
- getCrossBoundaryLinks: vi.fn().mockReturnValue([]),
46
- getCallers: vi.fn().mockReturnValue([]),
47
- getCallersOf: vi.fn().mockReturnValue([]),
48
- getCalleesOf: vi.fn().mockReturnValue([]),
49
- getCallees: vi.fn().mockReturnValue([]),
50
- getImports: vi.fn().mockReturnValue([]),
51
- getRules: vi.fn().mockReturnValue([]),
52
- getJustificationsForEntity: vi.fn().mockReturnValue([]),
53
- getDriftEntitiesForFile: vi.fn().mockReturnValue([]),
54
- close: vi.fn(),
55
- ...overrides,
56
- };
57
- }
58
- describe("Sprint S2: Session Health & Exploration Cost Wiring", () => {
59
- describe("S2.7-S2.8: Exploration cost on every graph response", () => {
60
- it("tracks tokens saved internally for graph tool responses", async () => {
61
- const graph = createMockGraph();
62
- const router = new QueryRouter(graph);
63
- const accumulator = createExplorationAccumulator();
64
- router.setExplorationAccumulator(accumulator);
65
- await router.execute("get_function", { key: "fn1" });
66
- // Vanity fields stripped from wire; verify via internal accumulator instead.
67
- const savings = router.getExplorationSavings();
68
- expect(savings).not.toBeNull();
69
- expect(savings.saved).toBeGreaterThan(0);
70
- });
71
- it("accumulates savings across multiple tool calls", async () => {
72
- const graph = createMockGraph();
73
- const router = new QueryRouter(graph);
74
- const accumulator = createExplorationAccumulator();
75
- router.setExplorationAccumulator(accumulator);
76
- await router.execute("get_function", { key: "fn1" });
77
- await router.execute("get_function", { key: "fn2" });
78
- await router.execute("search_code", { query: "test" });
79
- const savings = router.getExplorationSavings();
80
- expect(savings).not.toBeNull();
81
- expect(savings.saved).toBeGreaterThan(0);
82
- expect(savings.without).toBeGreaterThan(savings.saved);
83
- });
84
- it("different tool types produce different cost estimates", async () => {
85
- const graph = createMockGraph({
86
- getBlastRadius: vi.fn().mockReturnValue({
87
- direct_callers: 15,
88
- direct_callees: 3,
89
- transitive_count: 30,
90
- is_chokepoint: true,
91
- summary: "15 callers, 30 transitive",
92
- }),
93
- getBlastRadiusEntities: vi
94
- .fn()
95
- .mockReturnValue(Array.from({ length: 15 }, (_, i) => `caller${i}`)),
96
- });
97
- const router = new QueryRouter(graph);
98
- const accumulator = createExplorationAccumulator();
99
- router.setExplorationAccumulator(accumulator);
100
- await router.execute("get_function", { key: "fn1" });
101
- const beforeSecond = router.getExplorationSavings().saved;
102
- await router.execute("search_code", { query: "test" });
103
- const afterSecond = router.getExplorationSavings().saved;
104
- // Both calls should add to the internal savings accumulator.
105
- expect(beforeSecond).toBeGreaterThan(0);
106
- expect(afterSecond).toBeGreaterThan(beforeSecond);
107
- });
108
- });
109
- describe("S2.4-S2.5: Health monitor feeds from tool calls and blast radius", () => {
110
- it("records tool calls into health monitor", async () => {
111
- const graph = createMockGraph();
112
- const router = new QueryRouter(graph);
113
- const monitor = createSessionHealthMonitor();
114
- router.setHealthMonitor(monitor);
115
- await router.execute("get_function", { key: "fn1" });
116
- await router.execute("get_function", { key: "fn1" });
117
- await router.execute("get_function", { key: "fn1" });
118
- const health = monitor.getHealth();
119
- // After 3 queries to same entity, repeated_query signal should fire
120
- expect(health.signals.some((s) => s.type === "repeated_query")).toBe(true);
121
- });
122
- it("health drops below 0.8 after 3+ queries to same entity", async () => {
123
- const graph = createMockGraph();
124
- const router = new QueryRouter(graph);
125
- const monitor = createSessionHealthMonitor();
126
- router.setHealthMonitor(monitor);
127
- await router.execute("get_function", { key: "fn1" });
128
- await router.execute("get_function", { key: "fn1" });
129
- await router.execute("get_function", { key: "fn1" });
130
- const health = monitor.getHealth();
131
- expect(health.health).toBeLessThan(1.0);
132
- });
133
- it("feeds blast radius results into health monitor", async () => {
134
- const graph = createMockGraph({
135
- getBlastRadius: vi.fn().mockReturnValue({
136
- direct_callers: 20,
137
- direct_callees: 5,
138
- transitive_count: 50,
139
- is_chokepoint: true,
140
- summary: "20 callers, 50 transitive",
141
- }),
142
- getBlastRadiusEntities: vi
143
- .fn()
144
- .mockReturnValue(Array.from({ length: 20 }, (_, i) => `caller${i}`)),
145
- });
146
- const router = new QueryRouter(graph);
147
- const monitor = createSessionHealthMonitor();
148
- router.setHealthMonitor(monitor);
149
- // First call injects blast radius
150
- await router.execute("get_function", { key: "fn1" });
151
- const health = monitor.getHealth();
152
- // Health monitor should have recorded the blast radius
153
- expect(health).toBeDefined();
154
- });
155
- });
156
- describe("S2.6: Convention violations feed into health monitor", () => {
157
- it("low-adherence conventions feed as violations", async () => {
158
- const graph = createMockGraph({
159
- getConventionsForEntity: vi.fn().mockReturnValue([
160
- {
161
- id: "conv1",
162
- name: "camelCase",
163
- adherence_pct: 45,
164
- rule: "Use camelCase",
165
- },
166
- {
167
- id: "conv2",
168
- name: "noAny",
169
- adherence_pct: 30,
170
- rule: "Avoid any type",
171
- },
172
- {
173
- id: "conv3",
174
- name: "imports",
175
- adherence_pct: 90,
176
- rule: "Use named imports",
177
- },
178
- ]),
179
- });
180
- const router = new QueryRouter(graph);
181
- const monitor = createSessionHealthMonitor();
182
- router.setHealthMonitor(monitor);
183
- await router.execute("get_function", { key: "fn1" });
184
- // Should have recorded 2 violations (adherence < 70%)
185
- const health = monitor.getHealth();
186
- expect(health).toBeDefined();
187
- });
188
- });
189
- describe("S2.9: Session health warning in _meta", () => {
190
- it("no session_health warning when health is good", async () => {
191
- const graph = createMockGraph();
192
- const router = new QueryRouter(graph);
193
- const monitor = createSessionHealthMonitor();
194
- router.setHealthMonitor(monitor);
195
- const result = await router.execute("get_function", { key: "fn1" });
196
- // Single query — health should be fine (no warning)
197
- expect(result._meta.session_health).toBeUndefined();
198
- });
199
- it("injects session_health when health drops below 0.6", async () => {
200
- const graph = createMockGraph();
201
- const router = new QueryRouter(graph);
202
- const monitor = createSessionHealthMonitor();
203
- router.setHealthMonitor(monitor);
204
- // Hammer the same entity many times to degrade health
205
- for (let i = 0; i < 12; i++) {
206
- await router.execute("get_function", { key: "fn1" });
207
- }
208
- // Get the last result
209
- const result = await router.execute("get_function", { key: "fn1" });
210
- // After 13 queries to same entity, health should be degraded
211
- const health = monitor.getHealth();
212
- if (health.health < 0.6) {
213
- expect(result._meta.session_health).toBeDefined();
214
- expect(result._meta.session_health.health).toBeLessThan(0.6);
215
- expect(result._meta.session_health.recommendation).toBeDefined();
216
- expect(result._meta.session_health.signals.length).toBeGreaterThan(0);
217
- }
218
- });
219
- });
220
- describe("S2.10: Cumulative savings accessible for session summary", () => {
221
- it("getExplorationSavings returns null when no accumulator set", () => {
222
- const graph = createMockGraph();
223
- const router = new QueryRouter(graph);
224
- expect(router.getExplorationSavings()).toBeNull();
225
- });
226
- it("getExplorationSavings returns cumulative data after queries", async () => {
227
- const graph = createMockGraph();
228
- const router = new QueryRouter(graph);
229
- const accumulator = createExplorationAccumulator();
230
- router.setExplorationAccumulator(accumulator);
231
- await router.execute("get_function", { key: "fn1" });
232
- await router.execute("get_function", { key: "fn2" });
233
- const savings = router.getExplorationSavings();
234
- expect(savings).not.toBeNull();
235
- expect(savings.saved).toBeGreaterThan(0);
236
- expect(savings.ratio).toBeGreaterThan(0);
237
- expect(savings.ratio).toBeLessThan(1);
238
- });
239
- });
240
- describe("Tool call acceleration detection", () => {
241
- it("rapid tool calls trigger acceleration signal in health monitor", async () => {
242
- const graph = createMockGraph();
243
- const router = new QueryRouter(graph);
244
- const monitor = createSessionHealthMonitor();
245
- router.setHealthMonitor(monitor);
246
- // Fire 12 tool calls rapidly (simulates acceleration > 10/min)
247
- for (let i = 0; i < 12; i++) {
248
- await router.execute("get_function", { key: `fn${i}` });
249
- }
250
- const health = monitor.getHealth();
251
- // With 12 rapid calls, tool_call_acceleration should fire
252
- const hasAcceleration = health.signals.some((s) => s.type === "tool_call_acceleration");
253
- expect(hasAcceleration).toBe(true);
254
- });
255
- });
256
- });
@@ -1,195 +0,0 @@
1
- /**
2
- * Sprint S3: Context Rot Detector — Integration Tests.
3
- *
4
- * Verifies:
5
- * - After 200K+ estimated tokens → depth_threshold signal fires
6
- * - After 3+ queries to same entity → repeated_exploration signal
7
- * - inject_refresh action → _context.session_warning with actionable summary
8
- * - suggest_new_session → _meta.session_health includes recommendation
9
- * - Errors feed into context rot detector
10
- */
11
- import { describe, expect, it, vi } from "vitest";
12
- import { QueryRouter } from "../intelligence/query-router.js";
13
- import { createContextRotDetector } from "../proxy/context-rot-detector.js";
14
- function createMockGraph(overrides = {}) {
15
- return {
16
- getEntity: vi.fn().mockReturnValue({
17
- key: "fn1",
18
- name: "fn1",
19
- kind: "function",
20
- file_path: "src/main.ts",
21
- risk_level: "normal",
22
- }),
23
- getBlastRadius: vi.fn().mockReturnValue({
24
- direct_callers: 5,
25
- direct_callees: 2,
26
- transitive_count: 10,
27
- is_chokepoint: false,
28
- summary: "5 callers, 10 transitive dependents",
29
- }),
30
- getBlastRadiusEntities: vi.fn().mockReturnValue(["caller1", "caller2"]),
31
- getConventionsForEntity: vi.fn().mockReturnValue([]),
32
- getCorrections: vi.fn().mockReturnValue([]),
33
- getCommunityForEntity: vi.fn().mockReturnValue(null),
34
- getCrossCommunityEdges: vi.fn().mockReturnValue([]),
35
- getEntitiesByFile: vi.fn().mockReturnValue([]),
36
- queryEntities: vi.fn().mockReturnValue([]),
37
- searchEntities: vi.fn().mockReturnValue([]),
38
- getDriftOverlayEntity: vi.fn().mockReturnValue(null),
39
- getDriftSummary: vi
40
- .fn()
41
- .mockReturnValue({ added: 0, modified: 0, removed: 0, total: 0 }),
42
- getCriticalNodes: vi.fn().mockReturnValue([]),
43
- getCrossBoundaryLinks: vi.fn().mockReturnValue([]),
44
- getCallers: vi.fn().mockReturnValue([]),
45
- getCallersOf: vi.fn().mockReturnValue([]),
46
- getCalleesOf: vi.fn().mockReturnValue([]),
47
- getCallees: vi.fn().mockReturnValue([]),
48
- getImports: vi.fn().mockReturnValue([]),
49
- getRules: vi.fn().mockReturnValue([]),
50
- getJustificationsForEntity: vi.fn().mockReturnValue([]),
51
- getDriftEntitiesForFile: vi.fn().mockReturnValue([]),
52
- close: vi.fn(),
53
- ...overrides,
54
- };
55
- }
56
- describe("Sprint S3: Context Rot Detector Wiring", () => {
57
- describe("S3.3: Token depth tracking", () => {
58
- it("accumulates token depth from tool call responses", async () => {
59
- const graph = createMockGraph();
60
- const router = new QueryRouter(graph);
61
- const detector = createContextRotDetector();
62
- router.setContextRotDetector(detector);
63
- // Make several calls to accumulate token depth
64
- for (let i = 0; i < 5; i++) {
65
- await router.execute("get_function", { key: `fn${i}` });
66
- }
67
- // Evaluate — should have accumulated some tokens (small, but non-zero)
68
- const signal = detector.evaluate();
69
- expect(signal.estimatedDepth).toBeGreaterThan(0);
70
- });
71
- it("fires depth_threshold after 200K+ tokens", () => {
72
- const detector = createContextRotDetector();
73
- // Simulate large token accumulation
74
- detector.recordToolCallTokens(210_000);
75
- const signal = detector.evaluate();
76
- expect(signal.signals.some((s) => s.type === "depth_threshold")).toBe(true);
77
- expect(signal.rotConfidence).toBeGreaterThan(0);
78
- });
79
- });
80
- describe("S3.4: Repeated query detection via sessionContext", () => {
81
- it("detects repeated queries to same entity", async () => {
82
- const graph = createMockGraph();
83
- const router = new QueryRouter(graph);
84
- const detector = createContextRotDetector();
85
- router.setContextRotDetector(detector);
86
- // First call establishes history
87
- await router.execute("get_function", { key: "fn1" });
88
- // Subsequent calls should trigger repeated query detection
89
- await router.execute("get_function", { key: "fn1" });
90
- await router.execute("get_function", { key: "fn1" });
91
- await router.execute("get_function", { key: "fn1" });
92
- const signal = detector.evaluate();
93
- // After 3+ re-queries to an entity already in history, repeated_exploration fires
94
- expect(signal.signals.some((s) => s.type === "repeated_exploration")).toBe(true);
95
- });
96
- });
97
- describe("S3.5: Error feeding", () => {
98
- it("feeds errors into context rot detector on tool failure", async () => {
99
- const graph = createMockGraph({
100
- searchEntities: vi.fn().mockImplementation(() => {
101
- throw new Error("simulated failure");
102
- }),
103
- });
104
- const router = new QueryRouter(graph);
105
- const detector = createContextRotDetector();
106
- router.setContextRotDetector(detector);
107
- // Trigger multiple failures
108
- for (let i = 0; i < 6; i++) {
109
- await router.execute("search_code", { query: "test" });
110
- }
111
- const signal = detector.evaluate();
112
- // After 5+ errors, declining_precision should fire
113
- expect(signal.signals.some((s) => s.type === "declining_precision")).toBe(true);
114
- });
115
- });
116
- describe("S3.6-S3.7: Rot evaluation and inject_refresh", () => {
117
- it("injects session_warning in _context when rot triggers inject_refresh", async () => {
118
- const graph = createMockGraph();
119
- const router = new QueryRouter(graph);
120
- const detector = createContextRotDetector();
121
- router.setContextRotDetector(detector);
122
- // Push token depth past warning threshold (200K)
123
- detector.recordToolCallTokens(220_000);
124
- // Add repeated queries to push rot confidence into inject_refresh range (0.4-0.7)
125
- detector.recordRepeatedQuery("fn1");
126
- detector.recordRepeatedQuery("fn1");
127
- detector.recordRepeatedQuery("fn1");
128
- // Now execute enough calls to hit the 10th-call evaluation boundary
129
- // We need toolCallCount to be divisible by 10
130
- for (let i = 0; i < 9; i++) {
131
- await router.execute("get_function", { key: `entity${i}` });
132
- }
133
- // 10th call triggers evaluation
134
- const result = await router.execute("get_function", { key: "entity9" });
135
- // Should have session_warning in _context
136
- const ctx = result._context;
137
- if (ctx?.session_warning) {
138
- const warning = ctx.session_warning;
139
- expect(warning.reason).toBeDefined();
140
- expect(warning.rot_confidence).toBeGreaterThan(0);
141
- }
142
- });
143
- });
144
- describe("S3.8: Severe rot triggers suggest_new_session in _meta", () => {
145
- it("injects _meta.session_health with suggest_new_session on critical rot", async () => {
146
- const graph = createMockGraph();
147
- const router = new QueryRouter(graph);
148
- const detector = createContextRotDetector();
149
- router.setContextRotDetector(detector);
150
- // Push past critical threshold (300K tokens → 0.4 rot)
151
- detector.recordToolCallTokens(310_000);
152
- // Add repeated queries (0.15 each for 3+ queries per entity)
153
- detector.recordRepeatedQuery("fn1");
154
- detector.recordRepeatedQuery("fn1");
155
- detector.recordRepeatedQuery("fn1");
156
- detector.recordRepeatedQuery("fn2");
157
- detector.recordRepeatedQuery("fn2");
158
- detector.recordRepeatedQuery("fn2");
159
- // Add errors (0.2 for 5+ errors)
160
- for (let i = 0; i < 6; i++) {
161
- detector.recordError();
162
- }
163
- // Total rot: 0.4 (depth) + 0.15 (fn1) + 0.15 (fn2) + 0.2 (errors) = 0.9 → suggest_new_session
164
- // Execute 10 calls to trigger evaluation
165
- for (let i = 0; i < 9; i++) {
166
- await router.execute("get_function", { key: `x${i}` });
167
- }
168
- const result = await router.execute("get_function", { key: "x9" });
169
- expect(result._meta.session_health).toBeDefined();
170
- expect(result._meta.session_health?.recommendation).toBe("suggest_new_session");
171
- expect(result._meta.session_health?.signals.length).toBeGreaterThan(0);
172
- });
173
- });
174
- describe("S3.9: getRefreshContext provides recovery data", () => {
175
- it("builds refresh context when rot action is triggered", () => {
176
- const detector = createContextRotDetector();
177
- // Push into inject_refresh range (0.2 depth + 0.15 fn1 + 0.15 fn2 = 0.5)
178
- detector.recordToolCallTokens(220_000);
179
- detector.recordRepeatedQuery("fn1");
180
- detector.recordRepeatedQuery("fn1");
181
- detector.recordRepeatedQuery("fn1");
182
- detector.recordRepeatedQuery("fn2");
183
- detector.recordRepeatedQuery("fn2");
184
- detector.recordRepeatedQuery("fn2");
185
- const signal = detector.evaluate();
186
- expect(signal.action).toBe("inject_refresh");
187
- const refresh = detector.getRefreshContext();
188
- expect(refresh).not.toBeNull();
189
- expect(refresh?.reason).toBe("context_degradation");
190
- expect(refresh?.estimated_depth).toBe(220_000);
191
- expect(refresh?.repeated_entities).toContain("fn1");
192
- expect(refresh?.repeated_entities).toContain("fn2");
193
- });
194
- });
195
- });