@unerr-ai/unerr 0.1.5 → 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 +39151 -36992
  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-BsMTQdhX.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,1018 +0,0 @@
1
- /**
2
- * Phase 10b TEST-07 + Sprint 3 TEST-05: Query router + drift overlay tests.
3
- */
4
- import { describe, expect, it, vi } from "vitest";
5
- import { QueryRouter, orderContextFields, } from "../intelligence/query-router.js";
6
- /** Helper: find a signal by content substring in _context.signals */
7
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
- function findSignal(result, contentSubstr) {
9
- const signals = result._context?.signals;
10
- if (!signals)
11
- return undefined;
12
- return signals.find((s) => s.content.includes(contentSubstr));
13
- }
14
- /** Helper: check if any signal contains the substring */
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
- function hasSignalContaining(result, contentSubstr) {
17
- return findSignal(result, contentSubstr) !== undefined;
18
- }
19
- function createMockLocalGraph(opts = {}) {
20
- const { hasRules = true, hasJustifications = false, driftEntities = [], } = opts;
21
- const driftMap = new Map();
22
- for (const d of driftEntities)
23
- driftMap.set(d.key, d);
24
- // Build a mock db for direct drift_overlay queries
25
- const mockDb = {
26
- run: vi.fn(async (query, params) => {
27
- if (query.includes("drift_overlay") && params?.key) {
28
- const d = driftMap.get(params.key);
29
- if (d) {
30
- return {
31
- rows: [
32
- [
33
- d.key,
34
- d.name,
35
- d.kind,
36
- d.signature,
37
- d.body,
38
- d.file_path,
39
- d.line_start,
40
- d.line_end,
41
- d.content_hash,
42
- d.drift_status,
43
- d.intent_id,
44
- d.modified_at,
45
- d.origin ?? "human",
46
- d.previous_body ?? "",
47
- d.previous_signature ?? "",
48
- ],
49
- ],
50
- };
51
- }
52
- return { rows: [] };
53
- }
54
- // Default: count query for drift summary
55
- if (query.includes("drift_overlay") && query.includes("count")) {
56
- return { rows: [] };
57
- }
58
- return { rows: [] };
59
- }),
60
- };
61
- return {
62
- db: mockDb,
63
- getEntity: vi.fn().mockReturnValue({
64
- key: "fn1",
65
- kind: "function",
66
- name: "doStuff",
67
- file_path: "src/index.ts",
68
- start_line: 10,
69
- signature: "()",
70
- body: "function doStuff() {}",
71
- fan_in: 0,
72
- fan_out: 0,
73
- risk_level: "normal",
74
- }),
75
- getCallersOf: vi.fn().mockReturnValue([]),
76
- getCalleesOf: vi.fn().mockReturnValue([]),
77
- getEntitiesByFile: vi.fn().mockReturnValue([]),
78
- searchEntities: vi.fn().mockReturnValue([]),
79
- getImports: vi.fn().mockReturnValue([]),
80
- healthCheck: vi.fn().mockReturnValue({ status: "up", latencyMs: 0 }),
81
- isLoaded: vi.fn().mockReturnValue(true),
82
- loadSnapshot: vi.fn(),
83
- hasRules: vi.fn().mockReturnValue(hasRules),
84
- getRules: vi.fn().mockReturnValue([
85
- {
86
- key: "r1",
87
- name: "Test",
88
- scope: "repo",
89
- severity: "warn",
90
- engine: "naming",
91
- query: "^_",
92
- message: "No underscore",
93
- file_glob: "",
94
- enabled: true,
95
- repo_id: "repo-1",
96
- },
97
- ]),
98
- getPatterns: vi.fn().mockReturnValue([
99
- {
100
- key: "p1",
101
- name: "Error handler",
102
- kind: "error-handling",
103
- frequency: 5,
104
- confidence: 0.8,
105
- exemplar_keys: [],
106
- promoted_rule_key: "",
107
- },
108
- ]),
109
- loadRules: vi.fn(),
110
- loadPatterns: vi.fn(),
111
- hasJustifications: vi.fn().mockReturnValue(hasJustifications),
112
- getBusinessContext: vi.fn().mockReturnValue(hasJustifications
113
- ? {
114
- purpose: "Handles payments",
115
- role: "logic",
116
- feature_area: "billing",
117
- confidence: 0.9,
118
- entity: null,
119
- }
120
- : null),
121
- getConventions: vi.fn().mockReturnValue([
122
- {
123
- name: "Error handler",
124
- kind: "error-handling",
125
- frequency: 5,
126
- confidence: 0.8,
127
- adherence_rate: 0.1,
128
- },
129
- ]),
130
- loadJustifications: vi.fn(),
131
- getBlastRadius: vi.fn().mockReturnValue({
132
- direct_callers: 3,
133
- direct_callees: 2,
134
- transitive_count: 8,
135
- transitive_depth: 2,
136
- is_chokepoint: false,
137
- summary: "3 direct callers, 2 direct callees, 8 transitive dependents (depth 2)",
138
- }),
139
- getBlastRadiusEntities: vi.fn().mockReturnValue([]),
140
- getConventionsForEntity: vi.fn().mockReturnValue([
141
- {
142
- id: "pattern:p1",
143
- name: "Error handler",
144
- adherence_pct: 10,
145
- rule: "error-handling pattern (5 occurrences, 80% confidence)",
146
- },
147
- ]),
148
- getDriftEntitiesForFile: vi.fn().mockReturnValue([]),
149
- upsertDriftEntity: vi.fn(),
150
- removeDriftEntity: vi.fn(),
151
- clearDriftOverlay: vi.fn(),
152
- getDriftSummary: vi.fn().mockReturnValue({
153
- added: driftEntities.filter((d) => d.drift_status === "added").length,
154
- modified: driftEntities.filter((d) => d.drift_status === "modified")
155
- .length,
156
- deleted: driftEntities.filter((d) => d.drift_status === "deleted").length,
157
- dependency_changed: driftEntities.filter((d) => d.drift_status === "dependency_changed").length,
158
- total: driftEntities.length,
159
- }),
160
- };
161
- }
162
- // ── Routing Tests ──────────────────────────────────────────────
163
- describe("QueryRouter", () => {
164
- describe("isKnownTool", () => {
165
- it("recognizes all local tools", () => {
166
- const router = new QueryRouter(createMockLocalGraph());
167
- expect(router.isKnownTool("get_function")).toBe(true);
168
- expect(router.isKnownTool("get_class")).toBe(true);
169
- expect(router.isKnownTool("get_file")).toBe(true);
170
- expect(router.isKnownTool("get_callers")).toBe(true);
171
- expect(router.isKnownTool("get_callees")).toBe(true);
172
- expect(router.isKnownTool("get_imports")).toBe(true);
173
- expect(router.isKnownTool("search_code")).toBe(true);
174
- // Disabled: get_rules, check_rules, get_business_context — not wired/no data
175
- // expect(router.isKnownTool("get_rules")).toBe(true);
176
- // expect(router.isKnownTool("check_rules")).toBe(true);
177
- // expect(router.isKnownTool("get_business_context")).toBe(true);
178
- expect(router.isKnownTool("get_conventions")).toBe(true);
179
- // Disabled: semantic_search, find_similar — embedding store never wired
180
- // expect(router.isKnownTool("semantic_search")).toBe(true);
181
- // expect(router.isKnownTool("find_similar")).toBe(true);
182
- expect(router.isKnownTool("get_project_stats")).toBe(true);
183
- });
184
- it("returns false for unknown tools", () => {
185
- const router = new QueryRouter(createMockLocalGraph());
186
- expect(router.isKnownTool("unknown_tool")).toBe(false);
187
- });
188
- });
189
- describe("execute", () => {
190
- it("executes local tools with local source in meta", async () => {
191
- const localGraph = createMockLocalGraph();
192
- const router = new QueryRouter(localGraph);
193
- const result = await router.execute("get_function", { key: "fn1" });
194
- expect(result._meta.source).toBe("local");
195
- expect(localGraph.getEntity).toHaveBeenCalledWith("fn1");
196
- });
197
- it("returns error when local fails (no cloud fallback)", async () => {
198
- const localGraph = createMockLocalGraph();
199
- localGraph.getEntity.mockImplementation(() => {
200
- throw new Error("Not found");
201
- });
202
- const router = new QueryRouter(localGraph);
203
- const result = await router.execute("get_function", { key: "missing" });
204
- expect(result._meta.source).toBe("local");
205
- expect(result.content).toHaveProperty("error");
206
- });
207
- // Disabled: get_rules, check_rules — no rules detected/stored yet, always returns empty.
208
- // it("executes get_rules locally", async () => { ... });
209
- // it("executes check_rules locally with evaluator", async () => { ... });
210
- // it("returns error for check_rules without evaluator", async () => { ... });
211
- it("returns empty rules when no local rules", async () => {
212
- const localGraph = createMockLocalGraph({ hasRules: false });
213
- const router = new QueryRouter(localGraph);
214
- const result = await router.execute("get_rules", {});
215
- expect(result._meta.source).toBe("local");
216
- });
217
- });
218
- // ── Sprint 3: Business Context / Conventions Tests ────────────
219
- // Disabled: get_business_context — not properly wired, produces no useful data.
220
- // describe("get_business_context", () => {
221
- // it("resolves locally when justifications exist", async () => { ... });
222
- // it("returns null content when no justifications", async () => { ... });
223
- // });
224
- describe("get_conventions", () => {
225
- it("resolves locally and returns adherence rates", async () => {
226
- const localGraph = createMockLocalGraph();
227
- const router = new QueryRouter(localGraph);
228
- const result = await router.execute("get_conventions", {});
229
- expect(result._meta.source).toBe("local");
230
- expect(localGraph.getConventions).toHaveBeenCalled();
231
- // Conventions are emitted as `_fmt:multi` (sections by kind) — the
232
- // format-encoder reuses the "columnar" meta flag for multi-section
233
- // responses since the legend covers both shapes.
234
- expect(result._meta.format).toBe("columnar");
235
- // Response is the encoded `_fmt:multi` string; assert structural markers.
236
- const text = result.content;
237
- expect(typeof text).toBe("string");
238
- expect(text.startsWith("_fmt:multi")).toBe(true);
239
- expect(text).toContain("@meta");
240
- expect(text).toMatch(/@(naming|import_direction|structure|guidance)\[/);
241
- expect(text).toContain("summary=");
242
- });
243
- });
244
- // ── Sprint 3: Drift Overlay Tests (P10-TEST-05) ───────────────
245
- describe("drift overlay merge", () => {
246
- it("base entity only (no overlay) — no _meta.drift", async () => {
247
- const localGraph = createMockLocalGraph();
248
- const router = new QueryRouter(localGraph);
249
- const result = await router.execute("get_function", { key: "fn1" });
250
- expect(result._meta.source).toBe("local");
251
- expect(result._meta.drift).toBeUndefined();
252
- });
253
- it("modified entity — overlay body replaces base body, _meta.drift present", async () => {
254
- const driftEntities = [
255
- {
256
- key: "fn1",
257
- name: "doStuff",
258
- kind: "function",
259
- signature: "()",
260
- body: "function doStuff() { return 'modified' }",
261
- file_path: "src/index.ts",
262
- line_start: 10,
263
- line_end: 12,
264
- content_hash: "abc123",
265
- drift_status: "modified",
266
- intent_id: "intent_001",
267
- modified_at: "2026-03-09T10:00:00Z",
268
- origin: "human",
269
- previous_body: "",
270
- previous_signature: "",
271
- },
272
- ];
273
- const localGraph = createMockLocalGraph({ driftEntities });
274
- const router = new QueryRouter(localGraph);
275
- router.setBranchContext({
276
- currentBranch: "feature/auth",
277
- baseBranch: "main",
278
- baseCommit: "abc",
279
- commitsAhead: 3,
280
- commitsBehind: 0,
281
- headSha: "def",
282
- computedAt: "2026-03-09",
283
- });
284
- const result = await router.execute("get_function", { key: "fn1" });
285
- expect(result._meta.source).toBe("local");
286
- expect(result._meta.drift).toBeDefined();
287
- expect(result._meta.drift?.entityStatus).toBe("modified");
288
- expect(result._meta.drift?.branch).toBe("feature/auth");
289
- expect(result._meta.drift?.commitsAhead).toBe(3);
290
- expect(result._meta.drift?.lastModifiedBy).toBe("intent_001");
291
- // Body should be replaced with overlay body
292
- const content = result.content;
293
- expect(content.body).toContain("modified");
294
- });
295
- it("added entity — included in results with drift status", async () => {
296
- const driftEntities = [
297
- {
298
- key: "fn_new",
299
- name: "newFeature",
300
- kind: "function",
301
- signature: "(x: number)",
302
- body: "function newFeature(x: number) { return x }",
303
- file_path: "src/new.ts",
304
- line_start: 1,
305
- line_end: 3,
306
- content_hash: "xyz789",
307
- drift_status: "added",
308
- intent_id: "intent_002",
309
- modified_at: "2026-03-09T11:00:00Z",
310
- origin: "ai",
311
- previous_body: "",
312
- previous_signature: "",
313
- },
314
- ];
315
- const localGraph = createMockLocalGraph({ driftEntities });
316
- // Mock getEntity to return null for the new entity (not in base)
317
- localGraph.getEntity.mockReturnValue(null);
318
- const router = new QueryRouter(localGraph);
319
- router.setBranchContext({
320
- currentBranch: "feature/x",
321
- baseBranch: "main",
322
- baseCommit: null,
323
- commitsAhead: 1,
324
- commitsBehind: null,
325
- headSha: "abc",
326
- computedAt: "2026-03-09",
327
- });
328
- const result = await router.execute("get_function", { key: "fn_new" });
329
- expect(result._meta.source).toBe("local");
330
- expect(result._meta.drift).toBeDefined();
331
- expect(result._meta.drift?.entityStatus).toBe("added");
332
- const content = result.content;
333
- expect(content.name).toBe("newFeature");
334
- expect(content.kind).toBe("function");
335
- });
336
- it("deleted entity — excluded from results, drift annotated", async () => {
337
- const driftEntities = [
338
- {
339
- key: "fn1",
340
- name: "doStuff",
341
- kind: "function",
342
- signature: "()",
343
- body: "",
344
- file_path: "src/index.ts",
345
- line_start: 10,
346
- line_end: 10,
347
- content_hash: "",
348
- drift_status: "deleted",
349
- intent_id: "intent_003",
350
- modified_at: "2026-03-09T12:00:00Z",
351
- origin: "human",
352
- previous_body: "",
353
- previous_signature: "",
354
- },
355
- ];
356
- const localGraph = createMockLocalGraph({ driftEntities });
357
- const router = new QueryRouter(localGraph);
358
- router.setBranchContext({
359
- currentBranch: "main",
360
- baseBranch: "main",
361
- baseCommit: null,
362
- commitsAhead: 0,
363
- commitsBehind: null,
364
- headSha: "abc",
365
- computedAt: "2026-03-09",
366
- });
367
- const result = await router.execute("get_function", { key: "fn1" });
368
- // Deleted entity returns null content
369
- expect(result.content).toBeNull();
370
- });
371
- it("local failure returns error with local source", async () => {
372
- const localGraph = createMockLocalGraph();
373
- localGraph.getEntity.mockImplementation(() => {
374
- throw new Error("CozoDB corrupted");
375
- });
376
- const router = new QueryRouter(localGraph);
377
- const result = await router.execute("get_function", { key: "fn1" });
378
- expect(result._meta.source).toBe("local");
379
- expect(result.content).toHaveProperty("error");
380
- });
381
- it("no drift overlay — pure CozoDB results (no performance degradation)", async () => {
382
- const localGraph = createMockLocalGraph();
383
- const router = new QueryRouter(localGraph);
384
- const start = Date.now();
385
- await router.execute("get_function", { key: "fn1" });
386
- const elapsed = Date.now() - start;
387
- // Should be well under 100ms for a mock
388
- expect(elapsed).toBeLessThan(100);
389
- });
390
- });
391
- // ── Task 8.6: Mode-Aware Response Tests ──────────────────────────
392
- describe("mode-aware responses (8.6)", () => {
393
- it("local mode includes mode in _meta", async () => {
394
- const router = new QueryRouter(createMockLocalGraph());
395
- router.setMode("local");
396
- const result = await router.execute("get_function", { key: "fn1" });
397
- expect(result._meta.mode).toBe("local");
398
- expect(result._meta.tools_degraded).toBeUndefined();
399
- });
400
- it("setup mode returns informational response (not an error)", async () => {
401
- const router = new QueryRouter(createMockLocalGraph());
402
- router.setMode("setup", "Repository not configured");
403
- const result = await router.execute("get_function", { key: "fn1" });
404
- expect(result._meta.mode).toBe("setup");
405
- expect(result._meta.mode_reason).toBe("Repository not configured");
406
- expect(result._meta.tools_degraded).toBeDefined();
407
- expect(result._meta.tools_degraded?.length).toBeGreaterThan(0);
408
- const content = result.content;
409
- expect(content.available).toBe(false);
410
- expect(content.message).toContain("unerr is not yet configured");
411
- });
412
- it("setup mode returns informational for all tools", async () => {
413
- const router = new QueryRouter(createMockLocalGraph());
414
- router.setMode("setup");
415
- const result = await router.execute("semantic_search", { query: "auth" });
416
- expect(result._meta.mode).toBe("setup");
417
- const content = result.content;
418
- expect(content.tool).toBe("semantic_search");
419
- expect(content.available).toBe(false);
420
- });
421
- it("local mode serves local tools normally", async () => {
422
- const router = new QueryRouter(createMockLocalGraph());
423
- router.setMode("local", "Cloud unavailable");
424
- const result = await router.execute("get_function", { key: "fn1" });
425
- expect(result._meta.mode).toBe("local");
426
- expect(result._meta.source).toBe("local");
427
- expect(result._meta.mode_reason).toBe("Cloud unavailable");
428
- // Content should be the actual entity, not an error
429
- expect(result.content.key).toBe("fn1");
430
- });
431
- it("local mode has no degraded tools", async () => {
432
- const router = new QueryRouter(createMockLocalGraph());
433
- router.setMode("local", "Local only");
434
- const result = await router.execute("get_function", { key: "fn1" });
435
- expect(result._meta.mode).toBe("local");
436
- expect(result._meta.tools_degraded).toBeUndefined();
437
- });
438
- it("parse mode degrades get_conventions", async () => {
439
- const router = new QueryRouter(createMockLocalGraph());
440
- router.setMode("parse", "No repo configured");
441
- const result = await router.execute("get_function", { key: "fn1" });
442
- expect(result._meta.mode).toBe("parse");
443
- // check_rules and get_business_context removed — disabled tools
444
- expect(result._meta.tools_degraded).toContain("get_conventions");
445
- });
446
- it("parse mode serves local tools", async () => {
447
- const router = new QueryRouter(createMockLocalGraph());
448
- router.setMode("parse");
449
- const result = await router.execute("get_function", { key: "fn1" });
450
- expect(result._meta.source).toBe("local");
451
- expect(result._meta.mode).toBe("parse");
452
- });
453
- it("local mode returns error on local failure (no cloud fallback)", async () => {
454
- const localGraph = createMockLocalGraph();
455
- localGraph.getEntity.mockImplementation(() => {
456
- throw new Error("CozoDB error");
457
- });
458
- const router = new QueryRouter(localGraph);
459
- router.setMode("local");
460
- const result = await router.execute("get_function", { key: "fn1" });
461
- expect(result._meta.mode).toBe("local");
462
- expect(result._meta.source).toBe("local");
463
- expect(result.content).toHaveProperty("error");
464
- });
465
- it("getMode returns current mode", () => {
466
- const router = new QueryRouter(createMockLocalGraph());
467
- expect(router.getMode()).toBe("local");
468
- router.setMode("parse");
469
- expect(router.getMode()).toBe("parse");
470
- });
471
- it("getDegradedTools lists all tools for setup mode", async () => {
472
- const router = new QueryRouter(createMockLocalGraph());
473
- router.setMode("setup");
474
- const result = await router.execute("get_function", { key: "fn1" });
475
- // All tools should be degraded in setup mode (17 after disabling get_rules, check_rules, get_business_context, semantic_search, find_similar, unerr_revert_entity, 8 blueprint tools)
476
- expect(result._meta.tools_degraded?.length).toBe(17);
477
- });
478
- });
479
- // ── Sprint 2: Response Enrichment Tests ──────────────────────────
480
- describe("Sprint 2: Response Enrichment", () => {
481
- // ── 2.1: Response Envelope ──────────────────────────────────────
482
- describe("2.1: Response envelope", () => {
483
- it("every response includes _meta with source and latency_ms", async () => {
484
- const router = new QueryRouter(createMockLocalGraph());
485
- const result = await router.execute("get_function", { key: "fn1" });
486
- expect(result._meta).toBeDefined();
487
- expect(result._meta.source).toBeDefined();
488
- expect(result._meta.latency_ms).toBeGreaterThanOrEqual(0);
489
- });
490
- it("_meta includes blast_radius for entity tools", async () => {
491
- const router = new QueryRouter(createMockLocalGraph());
492
- const result = await router.execute("get_function", { key: "fn1" });
493
- expect(result._meta.blast_radius).toBeDefined();
494
- expect(result._meta.blast_radius?.direct_callers).toBe(3);
495
- expect(result._meta.blast_radius?.direct_callees).toBe(2);
496
- expect(result._meta.blast_radius?.transitive_depth2).toBe(8);
497
- expect(result._meta.blast_radius?.is_chokepoint).toBe(false);
498
- });
499
- it("_meta includes conventions for entity tools", async () => {
500
- const router = new QueryRouter(createMockLocalGraph());
501
- const result = await router.execute("get_function", { key: "fn1" });
502
- expect(result._meta.conventions).toBeDefined();
503
- expect(result._meta.conventions?.length).toBeGreaterThan(0);
504
- expect(result._meta.conventions?.[0]?.name).toBe("Error handler");
505
- expect(result._meta.conventions?.[0]?.adherence_pct).toBe(10);
506
- });
507
- });
508
- // ── 2.2: Blast Radius Injection ────────────────────────────────
509
- describe("2.2: Blast radius injection", () => {
510
- it("injects blast_radius in _context for first entity query", async () => {
511
- const router = new QueryRouter(createMockLocalGraph());
512
- const result = await router.execute("get_function", { key: "fn1" });
513
- expect(result._context?.signals).toBeDefined();
514
- expect(findSignal(result, "direct caller")).toBeDefined();
515
- });
516
- it("does not inject blast_radius for non-entity tools", async () => {
517
- const router = new QueryRouter(createMockLocalGraph());
518
- const result = await router.execute("search_code", {
519
- query: "something",
520
- });
521
- expect(result._meta.blast_radius).toBeUndefined();
522
- });
523
- it("marks chokepoints when detected", async () => {
524
- const localGraph = createMockLocalGraph();
525
- localGraph.getBlastRadius.mockReturnValue({
526
- direct_callers: 10,
527
- direct_callees: 8,
528
- transitive_count: 45,
529
- transitive_depth: 2,
530
- is_chokepoint: true,
531
- summary: "10 direct callers, 8 direct callees, CHOKEPOINT",
532
- });
533
- // Also need entity with high fan_in/fan_out
534
- localGraph.getEntity.mockReturnValue({
535
- key: "fn1",
536
- kind: "function",
537
- name: "doStuff",
538
- file_path: "src/index.ts",
539
- start_line: 10,
540
- signature: "()",
541
- body: "function doStuff() {}",
542
- fan_in: 10,
543
- fan_out: 8,
544
- risk_level: "high",
545
- });
546
- const router = new QueryRouter(localGraph);
547
- const result = await router.execute("get_function", { key: "fn1" });
548
- expect(result._meta.blast_radius?.is_chokepoint).toBe(true);
549
- expect(hasSignalContaining(result, "CHOKEPOINT")).toBe(true);
550
- });
551
- });
552
- // ── 2.3: Convention Context Injection ──────────────────────────
553
- describe("2.3: Convention context injection", () => {
554
- it("injects conventions in _context with adherence rates", async () => {
555
- const router = new QueryRouter(createMockLocalGraph());
556
- const result = await router.execute("get_function", { key: "fn1" });
557
- expect(result._context?.signals).toBeDefined();
558
- const conventionSignal = findSignal(result, "Error handler");
559
- expect(conventionSignal).toBeDefined();
560
- expect(conventionSignal?.type).toBe("guidance");
561
- });
562
- it("skips convention injection for non-entity tools", async () => {
563
- const router = new QueryRouter(createMockLocalGraph());
564
- const result = await router.execute("get_rules", {});
565
- expect(hasSignalContaining(result, "Error handler")).toBe(false);
566
- });
567
- });
568
- // ── 2.4: Session Deduplication ─────────────────────────────────
569
- describe("2.4: Session deduplication", () => {
570
- it("first query includes blast radius, second omits it", async () => {
571
- const router = new QueryRouter(createMockLocalGraph());
572
- const r1 = await router.execute("get_function", { key: "fn1" });
573
- expect(findSignal(r1, "direct caller")).toBeDefined();
574
- expect(r1._meta.blast_radius).toBeDefined();
575
- const r2 = await router.execute("get_function", { key: "fn1" });
576
- expect(r2._meta.blast_radius).toBeUndefined();
577
- });
578
- it("different entities each get their own blast radius", async () => {
579
- const router = new QueryRouter(createMockLocalGraph());
580
- const r1 = await router.execute("get_function", { key: "fn1" });
581
- expect(findSignal(r1, "direct caller")).toBeDefined();
582
- const r2 = await router.execute("get_function", { key: "fn2" });
583
- expect(findSignal(r2, "direct caller")).toBeDefined();
584
- });
585
- it("conventions are deduped across entities", async () => {
586
- const router = new QueryRouter(createMockLocalGraph());
587
- const r1 = await router.execute("get_function", { key: "fn1" });
588
- expect(findSignal(r1, "Error handler")).toBeDefined();
589
- // Same conventions should be deduped on second entity
590
- const r2 = await router.execute("get_function", { key: "fn2" });
591
- expect(r2._meta.conventions).toBeUndefined();
592
- });
593
- });
594
- // ── 2.5: Proactive Drift Alerts ────────────────────────────────
595
- describe("2.5: Proactive drift alerts", () => {
596
- it("injects drift_alert when entity is modified", async () => {
597
- const driftEntities = [
598
- {
599
- key: "fn1",
600
- name: "doStuff",
601
- kind: "function",
602
- signature: "()",
603
- body: "function doStuff() { return 'modified' }",
604
- file_path: "src/index.ts",
605
- line_start: 10,
606
- line_end: 12,
607
- content_hash: "abc123",
608
- drift_status: "modified",
609
- intent_id: "intent_001",
610
- modified_at: "2026-03-09T10:00:00Z",
611
- origin: "human",
612
- previous_body: "",
613
- previous_signature: "",
614
- },
615
- ];
616
- const localGraph = createMockLocalGraph({ driftEntities });
617
- const router = new QueryRouter(localGraph);
618
- router.setBranchContext({
619
- currentBranch: "feature/auth",
620
- baseBranch: "main",
621
- baseCommit: "abc",
622
- commitsAhead: 1,
623
- commitsBehind: 0,
624
- headSha: "def",
625
- computedAt: "2026-03-09",
626
- });
627
- const result = await router.execute("get_function", { key: "fn1" });
628
- expect(result._context?.signals).toBeDefined();
629
- const driftSignal = findSignal(result, "modified");
630
- expect(driftSignal).toBeDefined();
631
- expect(driftSignal?.type).toBe("warning");
632
- });
633
- it("drift alert fires only once per entity per session", async () => {
634
- const driftEntities = [
635
- {
636
- key: "fn1",
637
- name: "doStuff",
638
- kind: "function",
639
- signature: "()",
640
- body: "modified body",
641
- file_path: "src/index.ts",
642
- line_start: 10,
643
- line_end: 12,
644
- content_hash: "abc",
645
- drift_status: "modified",
646
- intent_id: "i1",
647
- modified_at: "2026-03-09T10:00:00Z",
648
- origin: "mixed",
649
- previous_body: "",
650
- previous_signature: "",
651
- },
652
- ];
653
- const localGraph = createMockLocalGraph({ driftEntities });
654
- const router = new QueryRouter(localGraph);
655
- router.setBranchContext({
656
- currentBranch: "main",
657
- baseBranch: "main",
658
- baseCommit: null,
659
- commitsAhead: 0,
660
- commitsBehind: null,
661
- headSha: "abc",
662
- computedAt: "2026-03-09",
663
- });
664
- const r1 = await router.execute("get_function", { key: "fn1" });
665
- expect(findSignal(r1, "modified")).toBeDefined();
666
- const r2 = await router.execute("get_function", { key: "fn1" });
667
- expect(findSignal(r2, "modified")).toBeUndefined();
668
- });
669
- it("no drift_alert when entity has no drift", async () => {
670
- const router = new QueryRouter(createMockLocalGraph());
671
- const result = await router.execute("get_function", { key: "fn1" });
672
- // No drift entities, so no drift warning signal
673
- expect(findSignal(result, "WARNING")).toBeUndefined();
674
- });
675
- });
676
- // ── 2.6: Session Greeting ──────────────────────────────────────
677
- // DEPRECATED: `_context.session_greeting` was replaced by the structured
678
- // `session_brief` (see src/intelligence/session-brief-builder.ts and its
679
- // dedicated tests in session-brief-builder.test.ts). Keep these as `.skip`
680
- // so the history is visible without blocking CI.
681
- describe.skip("2.6: Session greeting (deprecated — see session-brief-builder.test.ts)", () => {
682
- it("first MCP response includes session_greeting", async () => {
683
- const router = new QueryRouter(createMockLocalGraph());
684
- router.setHealthInfo("B", { entities: 500, edges: 1200, rules: 10 });
685
- const result = await router.execute("get_function", { key: "fn1" });
686
- expect(result._context?.session_greeting).toBeDefined();
687
- expect(result._context?.session_greeting).toContain("B");
688
- });
689
- it("greeting fires exactly once per session", async () => {
690
- const router = new QueryRouter(createMockLocalGraph());
691
- router.setHealthInfo("A", { entities: 100, edges: 300, rules: 5 });
692
- const r1 = await router.execute("get_function", { key: "fn1" });
693
- expect(r1._context?.session_greeting).toBeDefined();
694
- const r2 = await router.execute("get_function", { key: "fn2" });
695
- expect(r2._context?.session_greeting).toBeUndefined();
696
- });
697
- it("greeting varies by health grade — A+ is healthy", async () => {
698
- const router = new QueryRouter(createMockLocalGraph());
699
- router.setHealthInfo("A+", { entities: 200, edges: 400, rules: 8 });
700
- const result = await router.execute("get_function", { key: "fn1" });
701
- expect(result._context?.session_greeting).toContain("healthy");
702
- });
703
- it("greeting varies by health grade — C shows concern", async () => {
704
- const router = new QueryRouter(createMockLocalGraph());
705
- router.setHealthInfo("C", { entities: 800, edges: 200, rules: 3 });
706
- const result = await router.execute("get_function", { key: "fn1" });
707
- expect(result._context?.session_greeting).toContain("C");
708
- expect(result._context?.session_greeting).toContain("structural issues");
709
- });
710
- it("greeting varies by health grade — D/F shows warning", async () => {
711
- const router = new QueryRouter(createMockLocalGraph());
712
- router.setHealthInfo("D", { entities: 1000, edges: 100, rules: 1 });
713
- const result = await router.execute("get_function", { key: "fn1" });
714
- expect(result._context?.session_greeting).toContain("Warning");
715
- });
716
- it("PARSE mode gets lite greeting", async () => {
717
- const router = new QueryRouter(createMockLocalGraph());
718
- router.setMode("parse");
719
- const result = await router.execute("get_function", { key: "fn1" });
720
- expect(result._context?.session_greeting).toContain("parse mode");
721
- });
722
- it("no health info still produces a default greeting", async () => {
723
- const router = new QueryRouter(createMockLocalGraph());
724
- const result = await router.execute("get_function", { key: "fn1" });
725
- expect(result._context?.session_greeting).toContain("proxy ready");
726
- });
727
- it("greeting on non-entity tools too", async () => {
728
- const router = new QueryRouter(createMockLocalGraph());
729
- router.setHealthInfo("B", { entities: 500, edges: 1000, rules: 5 });
730
- const result = await router.execute("search_code", {
731
- query: "auth",
732
- });
733
- expect(result._context?.session_greeting).toBeDefined();
734
- });
735
- });
736
- // ── 2.7: Value Counter ─────────────────────────────────────────
737
- describe("2.7: Value counter", () => {
738
- it("injects value_counter every 3rd caught event after 10 tool calls", async () => {
739
- const router = new QueryRouter(createMockLocalGraph());
740
- const events = {
741
- conventionViolationsCaught: 0,
742
- chokepointWarningsIssued: 0,
743
- circularDepsDetected: 0,
744
- signaturePreservations: 0,
745
- deadCodeReferences: 0,
746
- aiEntitiesModified: 0,
747
- humanEntitiesModified: 0,
748
- mixedEntitiesModified: 0,
749
- };
750
- router.setSessionEvents(events);
751
- // Make 11 tool calls (need >10 for counter to fire)
752
- for (let i = 0; i < 11; i++) {
753
- await router.execute("search_code", { query: "x" });
754
- }
755
- // Set caught events to 3 (divisible by 3)
756
- events.conventionViolationsCaught = 3;
757
- const result = await router.execute("search_code", { query: "y" });
758
- expect(result._context?.value_counter).toBeDefined();
759
- expect(result._context?.value_counter).toContain("3 issues");
760
- });
761
- it("does not inject value_counter when <=10 tool calls", async () => {
762
- const router = new QueryRouter(createMockLocalGraph());
763
- const events = {
764
- conventionViolationsCaught: 3,
765
- chokepointWarningsIssued: 0,
766
- circularDepsDetected: 0,
767
- signaturePreservations: 0,
768
- deadCodeReferences: 0,
769
- aiEntitiesModified: 0,
770
- humanEntitiesModified: 0,
771
- mixedEntitiesModified: 0,
772
- };
773
- router.setSessionEvents(events);
774
- // Only 5 calls — not enough
775
- for (let i = 0; i < 5; i++) {
776
- await router.execute("search_code", { query: "x" });
777
- }
778
- const result = await router.execute("search_code", { query: "y" });
779
- expect(result._context?.value_counter).toBeUndefined();
780
- });
781
- it("does not inject when caught events not divisible by 3", async () => {
782
- const router = new QueryRouter(createMockLocalGraph());
783
- const events = {
784
- conventionViolationsCaught: 2,
785
- chokepointWarningsIssued: 0,
786
- circularDepsDetected: 0,
787
- signaturePreservations: 0,
788
- deadCodeReferences: 0,
789
- aiEntitiesModified: 0,
790
- humanEntitiesModified: 0,
791
- mixedEntitiesModified: 0,
792
- };
793
- router.setSessionEvents(events);
794
- for (let i = 0; i < 12; i++) {
795
- await router.execute("search_code", { query: "x" });
796
- }
797
- const result = await router.execute("search_code", { query: "y" });
798
- expect(result._context?.value_counter).toBeUndefined();
799
- });
800
- it("counter fires only once per threshold crossing", async () => {
801
- const router = new QueryRouter(createMockLocalGraph());
802
- const events = {
803
- conventionViolationsCaught: 0,
804
- chokepointWarningsIssued: 0,
805
- circularDepsDetected: 0,
806
- signaturePreservations: 0,
807
- deadCodeReferences: 0,
808
- aiEntitiesModified: 0,
809
- humanEntitiesModified: 0,
810
- mixedEntitiesModified: 0,
811
- };
812
- router.setSessionEvents(events);
813
- // 11 calls
814
- for (let i = 0; i < 11; i++) {
815
- await router.execute("search_code", { query: "x" });
816
- }
817
- events.conventionViolationsCaught = 3;
818
- const r1 = await router.execute("search_code", { query: "a" });
819
- expect(r1._context?.value_counter).toBeDefined();
820
- // Same count — should not fire again
821
- const r2 = await router.execute("search_code", { query: "b" });
822
- expect(r2._context?.value_counter).toBeUndefined();
823
- // Advance to 6
824
- events.conventionViolationsCaught = 6;
825
- const r3 = await router.execute("search_code", { query: "c" });
826
- expect(r3._context?.value_counter).toBeDefined();
827
- expect(r3._context?.value_counter).toContain("6 issues");
828
- });
829
- });
830
- // ── Task 7.8: Post-compaction context recovery ─────────────────
831
- // DEPRECATED: `_context.reminder = "Previously queried: ..."` was removed
832
- // (query-router.ts:1618 comment: "redundant noise — showed up twice as
833
- // ur|ctx + ur|fct"). Re-query of seen entities is now signaled via
834
- // `_meta.context_complete = true` + the `ur|ctx already delivered` prefix.
835
- describe.skip("post-compaction reminder (deprecated — see _meta.context_complete)", () => {
836
- it("injects _context reminder signal on re-query of previously-seen entity", async () => {
837
- const router = new QueryRouter(createMockLocalGraph());
838
- // First query — records history, no reminder
839
- const r1 = await router.execute("get_function", { key: "fn1" });
840
- expect(findSignal(r1, "Previously queried")).toBeUndefined();
841
- // Second query — should inject reminder as a context signal
842
- const r2 = await router.execute("get_function", { key: "fn1" });
843
- const reminderSignal = findSignal(r2, "Previously queried");
844
- expect(reminderSignal).toBeDefined();
845
- expect(reminderSignal?.type).toBe("context");
846
- });
847
- it("reminder includes blast radius count from first query", async () => {
848
- const localGraph = createMockLocalGraph();
849
- localGraph.getBlastRadius.mockReturnValue({
850
- direct_callers: 7,
851
- direct_callees: 3,
852
- transitive_count: 15,
853
- is_chokepoint: false,
854
- summary: "7 callers, 15 transitive dependents",
855
- });
856
- const router = new QueryRouter(localGraph);
857
- // First query — records blast radius
858
- await router.execute("get_function", { key: "fn1" });
859
- // Second query — reminder reflects blast radius from first query
860
- const r2 = await router.execute("get_function", { key: "fn1" });
861
- expect(hasSignalContaining(r2, "7 callers")).toBe(true);
862
- });
863
- it("different entities get independent reminders", async () => {
864
- const router = new QueryRouter(createMockLocalGraph());
865
- // Query fn1 first
866
- await router.execute("get_function", { key: "fn1" });
867
- // Query fn2 first time — no reminder
868
- const r2 = await router.execute("get_function", { key: "fn2" });
869
- expect(findSignal(r2, "Previously queried")).toBeUndefined();
870
- // Re-query fn1 — should get reminder
871
- const r3 = await router.execute("get_function", { key: "fn1" });
872
- expect(findSignal(r3, "Previously queried")).toBeDefined();
873
- });
874
- it("reminder is short (<50 tokens)", async () => {
875
- const router = new QueryRouter(createMockLocalGraph());
876
- await router.execute("get_function", { key: "fn1" });
877
- const r2 = await router.execute("get_function", { key: "fn1" });
878
- const reminderSignal = findSignal(r2, "Previously queried");
879
- const reminder = reminderSignal?.content ?? "";
880
- // Rough token estimate: words + punctuation < 50
881
- const wordCount = reminder.split(/\s+/).length;
882
- expect(wordCount).toBeLessThan(50);
883
- });
884
- });
885
- // ── Task 7.3: Push-based pending violations ───────────────────
886
- describe("pending violations injection", () => {
887
- it("injects pending_violations from store into _context", async () => {
888
- const { PendingViolationStore } = await import("../tracking/pending-violations.js");
889
- const store = new PendingViolationStore();
890
- const router = new QueryRouter(createMockLocalGraph());
891
- router.setPendingViolations(store);
892
- // Simulate file-watcher adding violations
893
- store.addViolations("src/a.ts", [
894
- {
895
- ruleKey: "rule-1",
896
- ruleName: "naming-convention",
897
- severity: "warning",
898
- message: "Function should use camelCase",
899
- filePath: "src/a.ts",
900
- line: 10,
901
- },
902
- ]);
903
- const result = await router.execute("get_function", { key: "fn1" });
904
- expect(result._context?.signals).toBeDefined();
905
- const violationSignal = findSignal(result, "Function should use camelCase");
906
- expect(violationSignal).toBeDefined();
907
- expect(violationSignal?.type).toBe("warning");
908
- });
909
- it("clears violations after draining", async () => {
910
- const { PendingViolationStore } = await import("../tracking/pending-violations.js");
911
- const store = new PendingViolationStore();
912
- const router = new QueryRouter(createMockLocalGraph());
913
- router.setPendingViolations(store);
914
- store.addViolations("src/a.ts", [
915
- {
916
- ruleKey: "rule-1",
917
- ruleName: "naming",
918
- severity: "warning",
919
- message: "bad name",
920
- filePath: "src/a.ts",
921
- },
922
- ]);
923
- // First call drains
924
- const r1 = await router.execute("get_function", { key: "fn1" });
925
- expect(findSignal(r1, "bad name")).toBeDefined();
926
- // Second call — no violations pending
927
- const r2 = await router.execute("get_function", { key: "fn2" });
928
- expect(findSignal(r2, "Rule violation")).toBeUndefined();
929
- });
930
- it("does not inject when no violations pending", async () => {
931
- const { PendingViolationStore } = await import("../tracking/pending-violations.js");
932
- const store = new PendingViolationStore();
933
- const router = new QueryRouter(createMockLocalGraph());
934
- router.setPendingViolations(store);
935
- const result = await router.execute("get_function", { key: "fn1" });
936
- expect(findSignal(result, "Rule violation")).toBeUndefined();
937
- });
938
- });
939
- // ── Cross-cutting: mode degradation ────────────────────────────
940
- describe("enrichment graceful degradation", () => {
941
- it("setup mode still tracks tool calls but skips enrichment", async () => {
942
- const router = new QueryRouter(createMockLocalGraph());
943
- router.setMode("setup");
944
- const result = await router.execute("get_function", { key: "fn1" });
945
- expect(result._context).toBeUndefined();
946
- expect(router.sessionContext.getToolCallCount()).toBe(1);
947
- });
948
- it("enrichment does not break when localGraph methods throw", async () => {
949
- const localGraph = createMockLocalGraph();
950
- localGraph.getBlastRadius.mockImplementation(() => {
951
- throw new Error("CozoDB error");
952
- });
953
- localGraph.getConventionsForEntity.mockImplementation(() => {
954
- throw new Error("Convention error");
955
- });
956
- const router = new QueryRouter(localGraph);
957
- // Should not throw — enrichment failures are caught
958
- const result = await router.execute("get_function", { key: "fn1" });
959
- expect(result._meta.source).toBe("local");
960
- expect(result.content).toBeDefined();
961
- });
962
- });
963
- });
964
- // ── Task 6.8: Context Placement Strategy ─────────────────────────
965
- describe("orderContextFields — Lost in the Middle priority", () => {
966
- it("orders critical fields before informational fields", () => {
967
- const input = {
968
- conventions: ["naming: camelCase (92%)"],
969
- blast_radius: "14 callers, 38 transitive dependents",
970
- value_counter: "unerr caught 3 issues",
971
- drift_alert: "WARNING: modified locally",
972
- session_greeting: "Welcome to unerr",
973
- reminder: "Previously queried: 5 callers, risk=high",
974
- related_issues: ["Chokepoint detected"],
975
- pending_violations: [
976
- { file: "a.ts", rule: "naming", message: "Bad name" },
977
- ],
978
- };
979
- const ordered = orderContextFields(input);
980
- const keys = Object.keys(ordered);
981
- // Critical fields come first
982
- expect(keys[0]).toBe("blast_radius");
983
- expect(keys[1]).toBe("pending_violations");
984
- expect(keys[2]).toBe("drift_alert");
985
- expect(keys[3]).toBe("reminder");
986
- // Informational fields come after
987
- expect(keys[4]).toBe("conventions");
988
- expect(keys[5]).toBe("related_issues");
989
- expect(keys[6]).toBe("session_greeting");
990
- expect(keys[7]).toBe("value_counter");
991
- });
992
- it("preserves field values during reordering", () => {
993
- const input = {
994
- conventions: ["use snake_case"],
995
- blast_radius: "3 callers",
996
- drift_alert: "modified 2 min ago",
997
- };
998
- const ordered = orderContextFields(input);
999
- expect(ordered.blast_radius).toBe("3 callers");
1000
- expect(ordered.drift_alert).toBe("modified 2 min ago");
1001
- expect(ordered.conventions).toEqual(["use snake_case"]);
1002
- });
1003
- it("omits undefined fields (sparse context)", () => {
1004
- const ordered = orderContextFields({
1005
- blast_radius: "5 callers",
1006
- value_counter: "caught 2 issues",
1007
- });
1008
- const keys = Object.keys(ordered);
1009
- expect(keys).toEqual(["blast_radius", "value_counter"]);
1010
- expect(keys).toHaveLength(2);
1011
- });
1012
- it("handles empty context", () => {
1013
- const ordered = orderContextFields({});
1014
- expect(Object.keys(ordered)).toHaveLength(0);
1015
- });
1016
- });
1017
- // L8.3: Deferred Embedding Status Tests — disabled (semantic_search/find_similar removed from LOCAL_TOOLS)
1018
- });