@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.
- package/README.md +70 -194
- package/dist/cli.js +39149 -36991
- package/package.json +9 -2
- package/dist/__tests__/architecture-guard.test.js +0 -122
- package/dist/__tests__/arg-validator.test.js +0 -205
- package/dist/__tests__/ast-extractor.test.js +0 -203
- package/dist/__tests__/auto-bootstrap.test.js +0 -280
- package/dist/__tests__/background-indexer.test.js +0 -228
- package/dist/__tests__/blast-radius-engine.test.js +0 -200
- package/dist/__tests__/bridge-isolation.test.js +0 -37
- package/dist/__tests__/budget-enforcer.test.js +0 -53
- package/dist/__tests__/cfg-test-detection-perf.test.js +0 -82
- package/dist/__tests__/change-narrative.test.js +0 -190
- package/dist/__tests__/check-commit.test.js +0 -258
- package/dist/__tests__/checksum.test.js +0 -34
- package/dist/__tests__/commit-watcher.test.js +0 -154
- package/dist/__tests__/community-detection.test.js +0 -179
- package/dist/__tests__/community-tools.test.js +0 -299
- package/dist/__tests__/components.test.js +0 -449
- package/dist/__tests__/compression-log.test.js +0 -174
- package/dist/__tests__/compression-quality-monitor.test.js +0 -40
- package/dist/__tests__/config-healer.test.js +0 -165
- package/dist/__tests__/context-ledger.test.js +0 -58
- package/dist/__tests__/convention-detector.test.js +0 -99
- package/dist/__tests__/convention-learner.test.js +0 -86
- package/dist/__tests__/correction-detector.test.js +0 -330
- package/dist/__tests__/daemon-autostart-install.test.js +0 -283
- package/dist/__tests__/daemon-bridge.test.js +0 -222
- package/dist/__tests__/daemon-dashboard.test.js +0 -202
- package/dist/__tests__/daemon-registry.test.js +0 -240
- package/dist/__tests__/daemon-supervisor.test.js +0 -318
- package/dist/__tests__/daemon-version-check.test.js +0 -275
- package/dist/__tests__/decision-point-detector.test.js +0 -98
- package/dist/__tests__/deep-link.test.js +0 -143
- package/dist/__tests__/disallowed-tools.test.js +0 -115
- package/dist/__tests__/drift-tracker.test.js +0 -582
- package/dist/__tests__/durability-scorer.test.js +0 -152
- package/dist/__tests__/efficiency-tracker.test.js +0 -65
- package/dist/__tests__/enrich.test.js +0 -144
- package/dist/__tests__/entity-rewind.test.js +0 -248
- package/dist/__tests__/ephemeral.test.js +0 -111
- package/dist/__tests__/exploration-cost.test.js +0 -93
- package/dist/__tests__/fact-generator.test.js +0 -197
- package/dist/__tests__/file-l0-graph.test.js +0 -244
- package/dist/__tests__/file-logger.test.js +0 -82
- package/dist/__tests__/file-outline.test.js +0 -141
- package/dist/__tests__/file-read-protocol.test.js +0 -188
- package/dist/__tests__/format-encoder.test.js +0 -233
- package/dist/__tests__/git-attribution.test.js +0 -259
- package/dist/__tests__/graph-temporal-joiner.test.js +0 -219
- package/dist/__tests__/health-grade-enhanced.test.js +0 -138
- package/dist/__tests__/health-map-data.test.js +0 -173
- package/dist/__tests__/helpers/mcp-harness.js +0 -45
- package/dist/__tests__/helpers/mcp-harness.test.js +0 -68
- package/dist/__tests__/hook-dedup.test.js +0 -112
- package/dist/__tests__/hook-runner.test.js +0 -253
- package/dist/__tests__/indexer-cfg.test.js +0 -185
- package/dist/__tests__/indexer-cross-file.test.js +0 -172
- package/dist/__tests__/indexer-extraction.test.js +0 -245
- package/dist/__tests__/indexer-incremental.test.js +0 -232
- package/dist/__tests__/indexer-language-expansion.test.js +0 -165
- package/dist/__tests__/init-push.test.js +0 -131
- package/dist/__tests__/instruction-writer.test.js +0 -179
- package/dist/__tests__/intelligence-integration.test.js +0 -217
- package/dist/__tests__/intent-correlator.test.js +0 -175
- package/dist/__tests__/intent-detector.test.js +0 -235
- package/dist/__tests__/intent-encoder.test.js +0 -167
- package/dist/__tests__/java-build-tool-detection.test.js +0 -174
- package/dist/__tests__/layer3-sprint-q.test.js +0 -160
- package/dist/__tests__/layer3-sprint-r.test.js +0 -91
- package/dist/__tests__/layer3-sprint-s.test.js +0 -183
- package/dist/__tests__/layer3-sprint-t.test.js +0 -201
- package/dist/__tests__/layer3-sprint-u.test.js +0 -174
- package/dist/__tests__/layer4-sprint-ba2.test.js +0 -354
- package/dist/__tests__/layer4-sprint-ba4.test.js +0 -84
- package/dist/__tests__/layer4-sprint-vs.test.js +0 -105
- package/dist/__tests__/ledger-chains.test.js +0 -162
- package/dist/__tests__/lifecycle-machine.test.js +0 -226
- package/dist/__tests__/local-chat-provider.test.js +0 -170
- package/dist/__tests__/local-convention-detector.test.js +0 -308
- package/dist/__tests__/local-embeddings.test.js +0 -422
- package/dist/__tests__/local-graph.test.js +0 -540
- package/dist/__tests__/local-indexer.test.js +0 -228
- package/dist/__tests__/local-intelligence-l3.test.js +0 -332
- package/dist/__tests__/local-llm.test.js +0 -253
- package/dist/__tests__/local-mode-offline.test.js +0 -187
- package/dist/__tests__/local-mode-stats.test.js +0 -273
- package/dist/__tests__/local-mode-tui.test.js +0 -343
- package/dist/__tests__/local-parse.test.js +0 -199
- package/dist/__tests__/log-tailer.test.js +0 -208
- package/dist/__tests__/loop-breaker.test.js +0 -276
- package/dist/__tests__/loop-miner.test.js +0 -226
- package/dist/__tests__/mcp-config.test.js +0 -126
- package/dist/__tests__/mcp-content-json.test.js +0 -10
- package/dist/__tests__/mcp-envelope.test.js +0 -124
- package/dist/__tests__/metrics-store.test.js +0 -223
- package/dist/__tests__/native-watcher.test.js +0 -191
- package/dist/__tests__/navigation-hooks-agent-aware.test.js +0 -145
- package/dist/__tests__/negative-knowledge.test.js +0 -116
- package/dist/__tests__/network-boundary.test.js +0 -190
- package/dist/__tests__/network-firewall.test.js +0 -112
- package/dist/__tests__/nudge-invariants.test.js +0 -160
- package/dist/__tests__/nudge-v2.test.js +0 -225
- package/dist/__tests__/offline-rewind.test.js +0 -251
- package/dist/__tests__/open-threads.test.js +0 -89
- package/dist/__tests__/output-compressor.test.js +0 -93
- package/dist/__tests__/pending-violations.test.js +0 -112
- package/dist/__tests__/persistence-effectiveness.test.js +0 -143
- package/dist/__tests__/provider-factory.test.js +0 -42
- package/dist/__tests__/providers.test.js +0 -24
- package/dist/__tests__/proxy.test.js +0 -314
- package/dist/__tests__/query-router.test.js +0 -1018
- package/dist/__tests__/reasoning-quality-route.test.js +0 -138
- package/dist/__tests__/redactor.test.js +0 -120
- package/dist/__tests__/resource-monitor.test.js +0 -57
- package/dist/__tests__/response-envelope.test.js +0 -100
- package/dist/__tests__/risk-classifier.test.js +0 -101
- package/dist/__tests__/risk-signal-scope.test.js +0 -75
- package/dist/__tests__/rule-evaluator.test.js +0 -280
- package/dist/__tests__/scip-decoder.test.js +0 -49
- package/dist/__tests__/scip-downloader.test.js +0 -201
- package/dist/__tests__/scip-merger.test.js +0 -103
- package/dist/__tests__/search-index.test.js +0 -422
- package/dist/__tests__/semantic-enrichment.test.js +0 -360
- package/dist/__tests__/session-brief-builder.test.js +0 -187
- package/dist/__tests__/session-context.test.js +0 -221
- package/dist/__tests__/session-continuity.test.js +0 -144
- package/dist/__tests__/session-dedup.test.js +0 -74
- package/dist/__tests__/session-event-wiring.test.js +0 -206
- package/dist/__tests__/session-events.test.js +0 -149
- package/dist/__tests__/session-legend.test.js +0 -20
- package/dist/__tests__/session-persistence.test.js +0 -131
- package/dist/__tests__/session-resume-block.test.js +0 -107
- package/dist/__tests__/session-resume.test.js +0 -97
- package/dist/__tests__/session-summary-writer.test.js +0 -134
- package/dist/__tests__/shadow-ledger.test.js +0 -203
- package/dist/__tests__/shell-classifier.test.js +0 -151
- package/dist/__tests__/shell-compression-floor.test.js +0 -189
- package/dist/__tests__/shell-compression-v2.test.js +0 -339
- package/dist/__tests__/shell-compressor.test.js +0 -35
- package/dist/__tests__/shell-hooks.test.js +0 -128
- package/dist/__tests__/shell-strategies.test.js +0 -644
- package/dist/__tests__/shell-tee.test.js +0 -133
- package/dist/__tests__/signal-dedup.test.js +0 -158
- package/dist/__tests__/signal-reinforcer.test.js +0 -77
- package/dist/__tests__/signal-scorer.test.js +0 -251
- package/dist/__tests__/signal-show-store.test.js +0 -108
- package/dist/__tests__/smart-truncate.test.js +0 -215
- package/dist/__tests__/snapshot-v2.test.js +0 -113
- package/dist/__tests__/sprint-l1-local-mode.test.js +0 -130
- package/dist/__tests__/sprint-l10-boot.test.js +0 -220
- package/dist/__tests__/sprint-l9-offline-commands.test.js +0 -189
- package/dist/__tests__/sprint-q-persistent-context.test.js +0 -198
- package/dist/__tests__/sprint-s1-wiring.test.js +0 -215
- package/dist/__tests__/sprint-s2-wiring.test.js +0 -256
- package/dist/__tests__/sprint-s3-wiring.test.js +0 -195
- package/dist/__tests__/sprint-s4-wiring.test.js +0 -213
- package/dist/__tests__/sprint-s6-hooks.test.js +0 -222
- package/dist/__tests__/sprint-s7-persistent.test.js +0 -263
- package/dist/__tests__/sprint-s8-value.test.js +0 -167
- package/dist/__tests__/sprint-s9-behavioral.test.js +0 -179
- package/dist/__tests__/sprint3-intelligence.test.js +0 -297
- package/dist/__tests__/sprint5-mcp-server.test.js +0 -136
- package/dist/__tests__/startup-display.test.js +0 -302
- package/dist/__tests__/startup-log-file.test.js +0 -97
- package/dist/__tests__/stash-manager.test.js +0 -229
- package/dist/__tests__/state-detector.test.js +0 -92
- package/dist/__tests__/status-dashboard.test.js +0 -142
- package/dist/__tests__/temporal-facts.test.js +0 -292
- package/dist/__tests__/temporal-routes.test.js +0 -142
- package/dist/__tests__/test-detector.test.js +0 -174
- package/dist/__tests__/theme.test.js +0 -72
- package/dist/__tests__/timeline-agents.test.js +0 -122
- package/dist/__tests__/timeline-bootstrap.test.js +0 -176
- package/dist/__tests__/timeline-filters.test.js +0 -193
- package/dist/__tests__/timeline-markers.test.js +0 -151
- package/dist/__tests__/timeline-routes.test.js +0 -156
- package/dist/__tests__/timeline-store.test.js +0 -171
- package/dist/__tests__/token-counter.test.js +0 -86
- package/dist/__tests__/token-estimator.test.js +0 -96
- package/dist/__tests__/token-flow-api.test.js +0 -239
- package/dist/__tests__/token-flow-instrumentation.test.js +0 -437
- package/dist/__tests__/token-flow-persistence.test.js +0 -356
- package/dist/__tests__/token-flow-routes.test.js +0 -199
- package/dist/__tests__/token-flow.test.js +0 -695
- package/dist/__tests__/tool-clusters.test.js +0 -177
- package/dist/__tests__/transport-mux.test.js +0 -283
- package/dist/__tests__/turn-segmenter.test.js +0 -166
- package/dist/__tests__/uninstall.test.js +0 -141
- package/dist/__tests__/warm-start-policy.test.js +0 -271
- package/dist/__tests__/wire-cap-nudge.test.js +0 -77
- package/dist/__tests__/worker-pool.test.js +0 -101
- package/dist/ui/assets/index-7gl3mIuY.css +0 -1
- package/dist/ui/assets/index-CX4FCWGT.js +0 -10
- package/dist/ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
- package/dist/ui/assets/vis-network-NIJHUFI3.js +0 -908
- package/dist/ui/fonts/jetbrains-mono-latin-400-normal.woff +0 -0
- package/dist/ui/icon-wordmark.png +0 -0
- package/dist/ui/icon-wordmark.svg +0 -30
- package/dist/ui/icon.png +0 -0
- package/dist/ui/icon.svg +0 -25
- package/dist/ui/index.html +0 -15
- package/dist/ui/prototype-sandbox/index.html +0 -257
- package/dist/ui/screenshots/activity.png +0 -0
- package/dist/ui/screenshots/code-base-intelligence.png +0 -0
- package/dist/ui/screenshots/dashboard.png +0 -0
- package/dist/ui/screenshots/project-memory.png +0 -0
- package/dist/ui/screenshots/reasoning-quality.png +0 -0
- package/dist/ui/screenshots/reasoning-session.png +0 -0
- package/dist/ui/screenshots/token-session.png +0 -0
- package/dist/ui/screenshots/token-trace-main.png +0 -0
- package/dist/ui/screenshots/token-turn.png +0 -0
- package/dist/ui/unerr-wordmark.png +0 -0
- package/dist/ui/unerr-wordmark.svg +0 -9
- package/dist/ui/unerr.png +0 -0
- package/dist/ui/unerr.svg +0 -25
- package/dist/ui/web-app-manifest-192x192.png +0 -0
- 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
|
-
});
|