@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,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint 2 — SessionContext unit tests.
|
|
3
|
-
*
|
|
4
|
-
* Tests dedup logic for blast radius, conventions, risks, greeting,
|
|
5
|
-
* and value counter threshold behavior.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
import { SessionContext } from "../intelligence/session-context.js";
|
|
9
|
-
function createEvents(overrides = {}) {
|
|
10
|
-
return {
|
|
11
|
-
conventionViolationsCaught: 0,
|
|
12
|
-
chokepointWarningsIssued: 0,
|
|
13
|
-
circularDepsDetected: 0,
|
|
14
|
-
signaturePreservations: 0,
|
|
15
|
-
deadCodeReferences: 0,
|
|
16
|
-
aiEntitiesModified: 0,
|
|
17
|
-
humanEntitiesModified: 0,
|
|
18
|
-
mixedEntitiesModified: 0,
|
|
19
|
-
...overrides,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
describe("SessionContext", () => {
|
|
23
|
-
// ── Blast radius dedup ──────────────────────────────────────────
|
|
24
|
-
describe("shouldInjectBlastRadius", () => {
|
|
25
|
-
it("returns true for first query of an entity", () => {
|
|
26
|
-
const ctx = new SessionContext();
|
|
27
|
-
expect(ctx.shouldInjectBlastRadius("fn1")).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
it("returns false after entity has been queried", () => {
|
|
30
|
-
const ctx = new SessionContext();
|
|
31
|
-
ctx.recordQuery("fn1");
|
|
32
|
-
expect(ctx.shouldInjectBlastRadius("fn1")).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
it("different entities are independent", () => {
|
|
35
|
-
const ctx = new SessionContext();
|
|
36
|
-
ctx.recordQuery("fn1");
|
|
37
|
-
expect(ctx.shouldInjectBlastRadius("fn2")).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
// ── Convention dedup ────────────────────────────────────────────
|
|
41
|
-
describe("shouldInjectConvention", () => {
|
|
42
|
-
it("returns true for unseen convention", () => {
|
|
43
|
-
const ctx = new SessionContext();
|
|
44
|
-
expect(ctx.shouldInjectConvention("pattern:p1")).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
it("returns false after convention has been surfaced", () => {
|
|
47
|
-
const ctx = new SessionContext();
|
|
48
|
-
ctx.recordConventions(["pattern:p1"]);
|
|
49
|
-
expect(ctx.shouldInjectConvention("pattern:p1")).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
it("tracks multiple conventions independently", () => {
|
|
52
|
-
const ctx = new SessionContext();
|
|
53
|
-
ctx.recordConventions(["pattern:p1"]);
|
|
54
|
-
expect(ctx.shouldInjectConvention("pattern:p2")).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
// ── Risk dedup ──────────────────────────────────────────────────
|
|
58
|
-
describe("shouldInjectRisk", () => {
|
|
59
|
-
it("returns true for unseen entity risk", () => {
|
|
60
|
-
const ctx = new SessionContext();
|
|
61
|
-
expect(ctx.shouldInjectRisk("fn1")).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
it("returns false after risk has been surfaced", () => {
|
|
64
|
-
const ctx = new SessionContext();
|
|
65
|
-
ctx.recordRisk("fn1");
|
|
66
|
-
expect(ctx.shouldInjectRisk("fn1")).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
// ── Session greeting ────────────────────────────────────────────
|
|
70
|
-
describe("isFirstCall / markGreeted", () => {
|
|
71
|
-
it("isFirstCall returns true initially", () => {
|
|
72
|
-
const ctx = new SessionContext();
|
|
73
|
-
expect(ctx.isFirstCall()).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
it("isFirstCall returns false after markGreeted", () => {
|
|
76
|
-
const ctx = new SessionContext();
|
|
77
|
-
ctx.markGreeted();
|
|
78
|
-
expect(ctx.isFirstCall()).toBe(false);
|
|
79
|
-
});
|
|
80
|
-
it("markGreeted is idempotent", () => {
|
|
81
|
-
const ctx = new SessionContext();
|
|
82
|
-
ctx.markGreeted();
|
|
83
|
-
ctx.markGreeted();
|
|
84
|
-
expect(ctx.isFirstCall()).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
// ── Tool call counter ───────────────────────────────────────────
|
|
88
|
-
describe("recordToolCall / getToolCallCount", () => {
|
|
89
|
-
it("starts at zero", () => {
|
|
90
|
-
const ctx = new SessionContext();
|
|
91
|
-
expect(ctx.getToolCallCount()).toBe(0);
|
|
92
|
-
});
|
|
93
|
-
it("increments on each call", () => {
|
|
94
|
-
const ctx = new SessionContext();
|
|
95
|
-
ctx.recordToolCall();
|
|
96
|
-
ctx.recordToolCall();
|
|
97
|
-
ctx.recordToolCall();
|
|
98
|
-
expect(ctx.getToolCallCount()).toBe(3);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
// ── Value counter ───────────────────────────────────────────────
|
|
102
|
-
describe("getValueCounter", () => {
|
|
103
|
-
it("returns undefined when <=10 tool calls", () => {
|
|
104
|
-
const ctx = new SessionContext();
|
|
105
|
-
for (let i = 0; i < 10; i++)
|
|
106
|
-
ctx.recordToolCall();
|
|
107
|
-
const events = createEvents({ conventionViolationsCaught: 3 });
|
|
108
|
-
expect(ctx.getValueCounter(events)).toBeUndefined();
|
|
109
|
-
});
|
|
110
|
-
it("returns undefined when caught events is 0", () => {
|
|
111
|
-
const ctx = new SessionContext();
|
|
112
|
-
for (let i = 0; i < 15; i++)
|
|
113
|
-
ctx.recordToolCall();
|
|
114
|
-
expect(ctx.getValueCounter(createEvents())).toBeUndefined();
|
|
115
|
-
});
|
|
116
|
-
it("returns undefined when caught events not divisible by 3", () => {
|
|
117
|
-
const ctx = new SessionContext();
|
|
118
|
-
for (let i = 0; i < 15; i++)
|
|
119
|
-
ctx.recordToolCall();
|
|
120
|
-
const events = createEvents({ conventionViolationsCaught: 2 });
|
|
121
|
-
expect(ctx.getValueCounter(events)).toBeUndefined();
|
|
122
|
-
});
|
|
123
|
-
it("returns counter string when conditions are met", () => {
|
|
124
|
-
const ctx = new SessionContext();
|
|
125
|
-
for (let i = 0; i < 15; i++)
|
|
126
|
-
ctx.recordToolCall();
|
|
127
|
-
const events = createEvents({ conventionViolationsCaught: 3 });
|
|
128
|
-
const counter = ctx.getValueCounter(events);
|
|
129
|
-
expect(counter).toBe("(unerr has caught 3 issues this session)");
|
|
130
|
-
});
|
|
131
|
-
it("fires only once per threshold crossing", () => {
|
|
132
|
-
const ctx = new SessionContext();
|
|
133
|
-
for (let i = 0; i < 15; i++)
|
|
134
|
-
ctx.recordToolCall();
|
|
135
|
-
const events = createEvents({ conventionViolationsCaught: 3 });
|
|
136
|
-
const first = ctx.getValueCounter(events);
|
|
137
|
-
expect(first).toBeDefined();
|
|
138
|
-
// Same count — should not fire again
|
|
139
|
-
const second = ctx.getValueCounter(events);
|
|
140
|
-
expect(second).toBeUndefined();
|
|
141
|
-
// Advance to 6
|
|
142
|
-
events.conventionViolationsCaught = 6;
|
|
143
|
-
const third = ctx.getValueCounter(events);
|
|
144
|
-
expect(third).toBeDefined();
|
|
145
|
-
expect(third).toContain("6 issues");
|
|
146
|
-
});
|
|
147
|
-
it("counts across all event categories", () => {
|
|
148
|
-
const ctx = new SessionContext();
|
|
149
|
-
for (let i = 0; i < 15; i++)
|
|
150
|
-
ctx.recordToolCall();
|
|
151
|
-
// 1 + 1 + 1 = 3, divisible by 3
|
|
152
|
-
const events = createEvents({
|
|
153
|
-
conventionViolationsCaught: 1,
|
|
154
|
-
chokepointWarningsIssued: 1,
|
|
155
|
-
circularDepsDetected: 1,
|
|
156
|
-
});
|
|
157
|
-
const counter = ctx.getValueCounter(events);
|
|
158
|
-
expect(counter).toBe("(unerr has caught 3 issues this session)");
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
// ── Post-compaction recovery (Task 7.8) ─────────────────────────
|
|
162
|
-
describe("entity history (post-compaction)", () => {
|
|
163
|
-
it("records entity history on first call", () => {
|
|
164
|
-
const ctx = new SessionContext();
|
|
165
|
-
ctx.recordEntityHistory("fn1", 5, "high");
|
|
166
|
-
expect(ctx.hasHistory("fn1")).toBe(true);
|
|
167
|
-
const entry = ctx.getHistory("fn1");
|
|
168
|
-
expect(entry?.blast_radius).toBe(5);
|
|
169
|
-
expect(entry?.risk).toBe("high");
|
|
170
|
-
expect(entry?.queriedAt).toBeDefined();
|
|
171
|
-
});
|
|
172
|
-
it("does not overwrite on subsequent calls", () => {
|
|
173
|
-
const ctx = new SessionContext();
|
|
174
|
-
ctx.recordEntityHistory("fn1", 5, "high");
|
|
175
|
-
ctx.recordEntityHistory("fn1", 10, "low");
|
|
176
|
-
const entry = ctx.getHistory("fn1");
|
|
177
|
-
expect(entry?.blast_radius).toBe(5);
|
|
178
|
-
expect(entry?.risk).toBe("high");
|
|
179
|
-
});
|
|
180
|
-
it("returns undefined for unqueried entity", () => {
|
|
181
|
-
const ctx = new SessionContext();
|
|
182
|
-
expect(ctx.hasHistory("fn1")).toBe(false);
|
|
183
|
-
expect(ctx.getHistory("fn1")).toBeUndefined();
|
|
184
|
-
});
|
|
185
|
-
it("tracks multiple entities independently", () => {
|
|
186
|
-
const ctx = new SessionContext();
|
|
187
|
-
ctx.recordEntityHistory("fn1", 5, "high");
|
|
188
|
-
ctx.recordEntityHistory("fn2", 0, "normal");
|
|
189
|
-
expect(ctx.getHistory("fn1")?.blast_radius).toBe(5);
|
|
190
|
-
expect(ctx.getHistory("fn2")?.blast_radius).toBe(0);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
// ── Diagnostics ─────────────────────────────────────────────────
|
|
194
|
-
describe("diagnostics", () => {
|
|
195
|
-
it("tracks entities queried count", () => {
|
|
196
|
-
const ctx = new SessionContext();
|
|
197
|
-
ctx.recordQuery("fn1");
|
|
198
|
-
ctx.recordQuery("fn2");
|
|
199
|
-
expect(ctx.entitiesQueried).toBe(2);
|
|
200
|
-
});
|
|
201
|
-
it("tracks conventions surfaced count", () => {
|
|
202
|
-
const ctx = new SessionContext();
|
|
203
|
-
ctx.recordConventions(["c1", "c2", "c3"]);
|
|
204
|
-
expect(ctx.conventionsSurfaced).toBe(3);
|
|
205
|
-
});
|
|
206
|
-
it("tracks risks surfaced count", () => {
|
|
207
|
-
const ctx = new SessionContext();
|
|
208
|
-
ctx.recordRisk("fn1");
|
|
209
|
-
ctx.recordRisk("fn2");
|
|
210
|
-
expect(ctx.risksSurfaced).toBe(2);
|
|
211
|
-
});
|
|
212
|
-
it("deduplicates entities and conventions", () => {
|
|
213
|
-
const ctx = new SessionContext();
|
|
214
|
-
ctx.recordQuery("fn1");
|
|
215
|
-
ctx.recordQuery("fn1");
|
|
216
|
-
expect(ctx.entitiesQueried).toBe(1);
|
|
217
|
-
ctx.recordConventions(["c1", "c1"]);
|
|
218
|
-
expect(ctx.conventionsSurfaced).toBe(1);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
});
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BA-1.7: Session Continuity Protocol tests.
|
|
3
|
-
*
|
|
4
|
-
* Verifies:
|
|
5
|
-
* - Incomplete work from session N carried forward to session N+1
|
|
6
|
-
* - Resume context matches ledger state
|
|
7
|
-
* - Agent-as-LLM prompt generated for complex sessions (>50 entries)
|
|
8
|
-
* - Empty ledger returns null (no false resume)
|
|
9
|
-
* - Risk prioritization: high-risk items appear first
|
|
10
|
-
*/
|
|
11
|
-
import { mkdirSync } from "node:fs";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
15
|
-
import { SessionContinuityBehavior } from "../behaviors/session-continuity.js";
|
|
16
|
-
import { ShadowLedger } from "../tracking/shadow-ledger.js";
|
|
17
|
-
function makeTmpDir() {
|
|
18
|
-
const dir = join(tmpdir(), `unerr-test-continuity-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
19
|
-
mkdirSync(dir, { recursive: true });
|
|
20
|
-
return dir;
|
|
21
|
-
}
|
|
22
|
-
function makeCtx(sessionId) {
|
|
23
|
-
return {
|
|
24
|
-
toolName: "get_entity",
|
|
25
|
-
args: {},
|
|
26
|
-
sessionId,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
describe("Session Continuity Protocol (BA-1.2)", () => {
|
|
30
|
-
let tmpDir;
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
tmpDir = makeTmpDir();
|
|
33
|
-
});
|
|
34
|
-
describe("Resume Context", () => {
|
|
35
|
-
it("carries forward incomplete work from previous session", async () => {
|
|
36
|
-
const ledger = new ShadowLedger(tmpDir);
|
|
37
|
-
const prevSessionId = ledger.getSessionId();
|
|
38
|
-
ledger.record("edit_file", { key: "src/payment.ts::processPayment", path: "src/payment.ts" }, { source: "local" }, "main", "abc123");
|
|
39
|
-
ledger.record("edit_file", { key: "src/payment.ts::processPayment", path: "src/payment.ts" }, { source: "local" }, "main", "abc123");
|
|
40
|
-
ledger.record("get_entity", { key: "src/checkout.ts::handleOrder" }, { source: "local", found: true }, "main", "abc123");
|
|
41
|
-
const newLedger = new ShadowLedger(tmpDir);
|
|
42
|
-
const newSessionId = newLedger.getSessionId();
|
|
43
|
-
expect(newSessionId).not.toBe(prevSessionId);
|
|
44
|
-
const behavior = new SessionContinuityBehavior();
|
|
45
|
-
behavior.attachLedger(newLedger);
|
|
46
|
-
const output = await behavior.onSessionStart(makeCtx(newSessionId));
|
|
47
|
-
expect(output).not.toBeNull();
|
|
48
|
-
expect(output?._context?.session_resume).toBeDefined();
|
|
49
|
-
const resume = output?._context?.session_resume;
|
|
50
|
-
expect(resume.last_session.tool_calls).toBe(3);
|
|
51
|
-
});
|
|
52
|
-
it("returns null for empty ledger (no false resume)", async () => {
|
|
53
|
-
const ledger = new ShadowLedger(tmpDir);
|
|
54
|
-
const behavior = new SessionContinuityBehavior();
|
|
55
|
-
behavior.attachLedger(ledger);
|
|
56
|
-
const output = await behavior.onSessionStart(makeCtx(ledger.getSessionId()));
|
|
57
|
-
expect(output).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
it("returns null when no ledger is attached", async () => {
|
|
60
|
-
const behavior = new SessionContinuityBehavior();
|
|
61
|
-
const output = await behavior.onSessionStart(makeCtx("test-session"));
|
|
62
|
-
expect(output).toBeNull();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
describe("Agent-as-LLM Prompt", () => {
|
|
66
|
-
it("generates agent-as-LLM prompt for sessions with >50 entries", async () => {
|
|
67
|
-
const ledger = new ShadowLedger(tmpDir);
|
|
68
|
-
for (let i = 0; i < 55; i++) {
|
|
69
|
-
ledger.record("get_entity", { key: `src/file${i}.ts::func${i}` }, { source: "local", found: true }, "main", "abc123");
|
|
70
|
-
}
|
|
71
|
-
const newLedger = new ShadowLedger(tmpDir);
|
|
72
|
-
const behavior = new SessionContinuityBehavior();
|
|
73
|
-
behavior.attachLedger(newLedger);
|
|
74
|
-
const output = await behavior.onSessionStart(makeCtx(newLedger.getSessionId()));
|
|
75
|
-
expect(output).not.toBeNull();
|
|
76
|
-
const resume = output?._context?.session_resume;
|
|
77
|
-
expect(resume.use_agent_llm).toBe(true);
|
|
78
|
-
expect(resume.agent_llm_prompt).toContain("Summarize");
|
|
79
|
-
});
|
|
80
|
-
it("does NOT generate agent-as-LLM prompt for simple sessions", async () => {
|
|
81
|
-
const ledger = new ShadowLedger(tmpDir);
|
|
82
|
-
for (let i = 0; i < 5; i++) {
|
|
83
|
-
ledger.record("get_entity", { key: `src/file${i}.ts::func${i}` }, { source: "local", found: true }, "main", "abc123");
|
|
84
|
-
}
|
|
85
|
-
const newLedger = new ShadowLedger(tmpDir);
|
|
86
|
-
const behavior = new SessionContinuityBehavior();
|
|
87
|
-
behavior.attachLedger(newLedger);
|
|
88
|
-
const output = await behavior.onSessionStart(makeCtx(newLedger.getSessionId()));
|
|
89
|
-
expect(output).not.toBeNull();
|
|
90
|
-
const resume = output?._context?.session_resume;
|
|
91
|
-
expect(resume.use_agent_llm).toBeUndefined();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
describe("Working State", () => {
|
|
95
|
-
it("includes branch and file count in resume", async () => {
|
|
96
|
-
const ledger = new ShadowLedger(tmpDir);
|
|
97
|
-
ledger.record("edit_file", { key: "src/a.ts::funcA", path: "src/a.ts" }, { source: "local" }, "feature/payments", "abc123");
|
|
98
|
-
ledger.record("edit_file", { key: "src/b.ts::funcB", path: "src/b.ts" }, { source: "local" }, "feature/payments", "def456");
|
|
99
|
-
const newLedger = new ShadowLedger(tmpDir);
|
|
100
|
-
const behavior = new SessionContinuityBehavior();
|
|
101
|
-
behavior.attachLedger(newLedger);
|
|
102
|
-
const output = await behavior.onSessionStart(makeCtx(newLedger.getSessionId()));
|
|
103
|
-
expect(output).not.toBeNull();
|
|
104
|
-
const resume = output?._context?.session_resume;
|
|
105
|
-
expect(resume.working_state.last_branch).toBe("feature/payments");
|
|
106
|
-
expect(resume.working_state.files_modified).toBeGreaterThanOrEqual(2);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
describe("Risk Prioritization", () => {
|
|
110
|
-
it("orders incomplete work by modification count (proxy for risk)", async () => {
|
|
111
|
-
const ledger = new ShadowLedger(tmpDir);
|
|
112
|
-
ledger.record("edit_file", { key: "src/low.ts::low" }, { source: "local" }, "main", "abc123");
|
|
113
|
-
for (let i = 0; i < 5; i++) {
|
|
114
|
-
ledger.record("edit_file", { key: "src/high.ts::high" }, { source: "local" }, "main", "abc123");
|
|
115
|
-
}
|
|
116
|
-
const newLedger = new ShadowLedger(tmpDir);
|
|
117
|
-
const behavior = new SessionContinuityBehavior();
|
|
118
|
-
behavior.attachLedger(newLedger);
|
|
119
|
-
const output = await behavior.onSessionStart(makeCtx(newLedger.getSessionId()));
|
|
120
|
-
expect(output).not.toBeNull();
|
|
121
|
-
const resume = output?._context?.session_resume;
|
|
122
|
-
if (resume.incomplete_work.length >= 2) {
|
|
123
|
-
expect(resume.incomplete_work[0]?.entity).toBe("src/high.ts::high");
|
|
124
|
-
expect(resume.incomplete_work[0]?.risk).toBe("high");
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
describe("Behavior Framework Integration", () => {
|
|
129
|
-
it("has correct id and hooks", () => {
|
|
130
|
-
const behavior = new SessionContinuityBehavior();
|
|
131
|
-
expect(behavior.id).toBe("session_continuity");
|
|
132
|
-
expect(behavior.hooks).toContain("session_start");
|
|
133
|
-
expect(behavior.defaultLevel).toBe("suggestion");
|
|
134
|
-
});
|
|
135
|
-
it("reports confidence correctly", () => {
|
|
136
|
-
const behavior = new SessionContinuityBehavior();
|
|
137
|
-
expect(behavior.getConfidence()).toBe(1.0);
|
|
138
|
-
behavior.recordFeedback("accepted");
|
|
139
|
-
behavior.recordFeedback("accepted");
|
|
140
|
-
behavior.recordFeedback("dismissed");
|
|
141
|
-
expect(behavior.getConfidence()).toBeCloseTo(2 / 3, 2);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { createSessionDedup } from "../proxy/session-dedup.js";
|
|
3
|
-
describe("createSessionDedup", () => {
|
|
4
|
-
it("passes all context on first call for an entity", () => {
|
|
5
|
-
const dedup = createSessionDedup();
|
|
6
|
-
const context = {
|
|
7
|
-
"dev.unerr/blast_radius": { entities: [] },
|
|
8
|
-
"dev.unerr/conventions": [{ name: "test" }],
|
|
9
|
-
};
|
|
10
|
-
const filtered = dedup.filter("entity-a", context);
|
|
11
|
-
expect(Object.keys(filtered)).toHaveLength(2);
|
|
12
|
-
expect(filtered["dev.unerr/blast_radius"]).toBeDefined();
|
|
13
|
-
expect(filtered["dev.unerr/conventions"]).toBeDefined();
|
|
14
|
-
});
|
|
15
|
-
it("removes already-delivered context keys on second call", () => {
|
|
16
|
-
const dedup = createSessionDedup();
|
|
17
|
-
const context = {
|
|
18
|
-
"dev.unerr/blast_radius": { entities: [] },
|
|
19
|
-
"dev.unerr/conventions": [{ name: "test" }],
|
|
20
|
-
};
|
|
21
|
-
dedup.filter("entity-a", context);
|
|
22
|
-
const second = dedup.filter("entity-a", context);
|
|
23
|
-
expect(Object.keys(second)).toHaveLength(0);
|
|
24
|
-
});
|
|
25
|
-
it("delivers context for different entities independently", () => {
|
|
26
|
-
const dedup = createSessionDedup();
|
|
27
|
-
const context = { "dev.unerr/blast_radius": { entities: [] } };
|
|
28
|
-
dedup.filter("entity-a", context);
|
|
29
|
-
const resultB = dedup.filter("entity-b", context);
|
|
30
|
-
expect(Object.keys(resultB)).toHaveLength(1);
|
|
31
|
-
});
|
|
32
|
-
it("delivers new context keys even if entity was seen before", () => {
|
|
33
|
-
const dedup = createSessionDedup();
|
|
34
|
-
dedup.filter("entity-a", { "dev.unerr/blast_radius": {} });
|
|
35
|
-
const result = dedup.filter("entity-a", {
|
|
36
|
-
"dev.unerr/blast_radius": {},
|
|
37
|
-
"dev.unerr/conventions": [],
|
|
38
|
-
});
|
|
39
|
-
expect(Object.keys(result)).toHaveLength(1);
|
|
40
|
-
expect(result["dev.unerr/conventions"]).toBeDefined();
|
|
41
|
-
expect(result["dev.unerr/blast_radius"]).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
it("tracks delivered count", () => {
|
|
44
|
-
const dedup = createSessionDedup();
|
|
45
|
-
expect(dedup.getDeliveredCount()).toBe(0);
|
|
46
|
-
dedup.filter("e1", { a: 1, b: 2 });
|
|
47
|
-
expect(dedup.getDeliveredCount()).toBe(2);
|
|
48
|
-
dedup.filter("e2", { c: 3 });
|
|
49
|
-
expect(dedup.getDeliveredCount()).toBe(3);
|
|
50
|
-
});
|
|
51
|
-
it("hasDelivered returns correct state", () => {
|
|
52
|
-
const dedup = createSessionDedup();
|
|
53
|
-
expect(dedup.hasDelivered("e1", "a")).toBe(false);
|
|
54
|
-
dedup.filter("e1", { a: 1 });
|
|
55
|
-
expect(dedup.hasDelivered("e1", "a")).toBe(true);
|
|
56
|
-
expect(dedup.hasDelivered("e1", "b")).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
it("reset clears all state", () => {
|
|
59
|
-
const dedup = createSessionDedup();
|
|
60
|
-
dedup.filter("e1", { a: 1 });
|
|
61
|
-
expect(dedup.getDeliveredCount()).toBe(1);
|
|
62
|
-
dedup.reset();
|
|
63
|
-
expect(dedup.getDeliveredCount()).toBe(0);
|
|
64
|
-
expect(dedup.hasDelivered("e1", "a")).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
it("evicts oldest entries when exceeding max tracked keys", () => {
|
|
67
|
-
const dedup = createSessionDedup();
|
|
68
|
-
for (let i = 0; i < 11_000; i++) {
|
|
69
|
-
dedup.markDelivered(`entity-${i}`, [`key-${i}`]);
|
|
70
|
-
}
|
|
71
|
-
expect(dedup.getDeliveredCount()).toBeLessThanOrEqual(10_000);
|
|
72
|
-
expect(dedup.hasDelivered("entity-0", "key-0")).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint 4, Task 4.3: Session event wiring tests.
|
|
3
|
-
*
|
|
4
|
-
* Tests that the proxy-level event detection logic correctly identifies
|
|
5
|
-
* when to record violations, circular deps, signature preservations,
|
|
6
|
-
* chokepoint warnings, and dead code references from tool results.
|
|
7
|
-
*/
|
|
8
|
-
import { describe, expect, it } from "vitest";
|
|
9
|
-
import { createSessionStats, recordChokepointWarning, recordCircularDep, recordDeadCodeReference, recordSignaturePreservation, recordViolation, totalCaughtEvents, } from "../proxy/session-stats.js";
|
|
10
|
-
// ── Convention Violation Wiring ──────────────────────────────────────
|
|
11
|
-
describe("Convention violation wiring (check_rules)", () => {
|
|
12
|
-
it("records one violation per check_rules violation entry", () => {
|
|
13
|
-
const stats = createSessionStats();
|
|
14
|
-
// Simulate check_rules result with 3 violations
|
|
15
|
-
const checkResult = {
|
|
16
|
-
violations: [
|
|
17
|
-
{ ruleKey: "naming-1", message: "bad name" },
|
|
18
|
-
{ ruleKey: "naming-2", message: "wrong case" },
|
|
19
|
-
{ ruleKey: "struct-1", message: "missing type" },
|
|
20
|
-
],
|
|
21
|
-
};
|
|
22
|
-
if (checkResult.violations && checkResult.violations.length > 0) {
|
|
23
|
-
for (let i = 0; i < checkResult.violations.length; i++) {
|
|
24
|
-
recordViolation(stats);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
expect(stats.violationsCaught).toBe(3);
|
|
28
|
-
expect(stats.events.conventionViolationsCaught).toBe(3);
|
|
29
|
-
});
|
|
30
|
-
it("does not record violations when check_rules has empty violations", () => {
|
|
31
|
-
const stats = createSessionStats();
|
|
32
|
-
const checkResult = { violations: [] };
|
|
33
|
-
if (checkResult.violations && checkResult.violations.length > 0) {
|
|
34
|
-
for (let i = 0; i < checkResult.violations.length; i++) {
|
|
35
|
-
recordViolation(stats);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
expect(stats.violationsCaught).toBe(0);
|
|
39
|
-
});
|
|
40
|
-
it("does not record violations when check_rules has no violations field", () => {
|
|
41
|
-
const stats = createSessionStats();
|
|
42
|
-
const checkResult = { _meta: { source: "local" } };
|
|
43
|
-
if (checkResult.violations && checkResult.violations.length > 0) {
|
|
44
|
-
for (let i = 0; i < checkResult.violations.length; i++) {
|
|
45
|
-
recordViolation(stats);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
expect(stats.violationsCaught).toBe(0);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
// ── Circular Dependency Detection ────────────────────────────────────
|
|
52
|
-
describe("Circular dependency detection (get_imports)", () => {
|
|
53
|
-
it("detects circular import when file appears in its own imports", () => {
|
|
54
|
-
const stats = createSessionStats();
|
|
55
|
-
// File A imports B, and its own file path — circular
|
|
56
|
-
const imports = [
|
|
57
|
-
{ imported_file: "src/b.ts" },
|
|
58
|
-
{ imported_file: "src/a.ts" },
|
|
59
|
-
];
|
|
60
|
-
const filePath = "src/a.ts";
|
|
61
|
-
const importedFiles = new Set(imports.map((e) => e.imported_file));
|
|
62
|
-
for (const target of importedFiles) {
|
|
63
|
-
if (target === filePath) {
|
|
64
|
-
recordCircularDep(stats);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
expect(stats.events.circularDepsDetected).toBe(1);
|
|
69
|
-
});
|
|
70
|
-
it("does not detect circular when file is not in its own imports", () => {
|
|
71
|
-
const stats = createSessionStats();
|
|
72
|
-
// A imports B and C — no circular
|
|
73
|
-
const imports = [
|
|
74
|
-
{ imported_file: "src/b.ts" },
|
|
75
|
-
{ imported_file: "src/c.ts" },
|
|
76
|
-
];
|
|
77
|
-
const filePath = "src/a.ts";
|
|
78
|
-
const importedFiles = new Set(imports.map((e) => e.imported_file));
|
|
79
|
-
for (const target of importedFiles) {
|
|
80
|
-
if (target === filePath) {
|
|
81
|
-
recordCircularDep(stats);
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
expect(stats.events.circularDepsDetected).toBe(0);
|
|
86
|
-
});
|
|
87
|
-
it("counts only once per call", () => {
|
|
88
|
-
const stats = createSessionStats();
|
|
89
|
-
const imports = [
|
|
90
|
-
{ imported_file: "src/a.ts" },
|
|
91
|
-
{ imported_file: "src/b.ts" },
|
|
92
|
-
];
|
|
93
|
-
const filePath = "src/a.ts";
|
|
94
|
-
const importedFiles = new Set(imports.map((e) => e.imported_file));
|
|
95
|
-
for (const target of importedFiles) {
|
|
96
|
-
if (target === filePath) {
|
|
97
|
-
recordCircularDep(stats);
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
expect(stats.events.circularDepsDetected).toBe(1);
|
|
102
|
-
});
|
|
103
|
-
it("handles empty imports array", () => {
|
|
104
|
-
const stats = createSessionStats();
|
|
105
|
-
const imports = [];
|
|
106
|
-
const importedFiles = new Set(imports.map((e) => e.imported_file));
|
|
107
|
-
for (const target of importedFiles) {
|
|
108
|
-
if (target === "src/a.ts") {
|
|
109
|
-
recordCircularDep(stats);
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
expect(stats.events.circularDepsDetected).toBe(0);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
// ── Signature Preservation ───────────────────────────────────────────
|
|
117
|
-
describe("Signature preservation detection (drift)", () => {
|
|
118
|
-
it("records preservation when drift status is 'modified'", () => {
|
|
119
|
-
const stats = createSessionStats();
|
|
120
|
-
const drift = { entityStatus: "modified" };
|
|
121
|
-
if (drift.entityStatus === "modified") {
|
|
122
|
-
recordSignaturePreservation(stats);
|
|
123
|
-
}
|
|
124
|
-
expect(stats.events.signaturePreservations).toBe(1);
|
|
125
|
-
});
|
|
126
|
-
it("does not record for 'added' drift status", () => {
|
|
127
|
-
const stats = createSessionStats();
|
|
128
|
-
const drift = { entityStatus: "added" };
|
|
129
|
-
if (drift.entityStatus === "modified") {
|
|
130
|
-
recordSignaturePreservation(stats);
|
|
131
|
-
}
|
|
132
|
-
expect(stats.events.signaturePreservations).toBe(0);
|
|
133
|
-
});
|
|
134
|
-
it("does not record when drift is null", () => {
|
|
135
|
-
const stats = createSessionStats();
|
|
136
|
-
const drift = null;
|
|
137
|
-
if (drift?.entityStatus === "modified") {
|
|
138
|
-
recordSignaturePreservation(stats);
|
|
139
|
-
}
|
|
140
|
-
expect(stats.events.signaturePreservations).toBe(0);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
// ── Chokepoint Warning ───────────────────────────────────────────────
|
|
144
|
-
describe("Chokepoint warning detection", () => {
|
|
145
|
-
it("records warning when fan_in > 10 and risk is high", () => {
|
|
146
|
-
const stats = createSessionStats();
|
|
147
|
-
const entityRisk = { fan_in: 15, fan_out: 3, risk_level: "high" };
|
|
148
|
-
if (entityRisk.risk_level === "high" && entityRisk.fan_in > 10) {
|
|
149
|
-
recordChokepointWarning(stats);
|
|
150
|
-
}
|
|
151
|
-
expect(stats.events.chokepointWarningsIssued).toBe(1);
|
|
152
|
-
});
|
|
153
|
-
it("does not record when fan_in <= 10 even with high risk", () => {
|
|
154
|
-
const stats = createSessionStats();
|
|
155
|
-
const entityRisk = { fan_in: 8, fan_out: 3, risk_level: "high" };
|
|
156
|
-
if (entityRisk.risk_level === "high" && entityRisk.fan_in > 10) {
|
|
157
|
-
recordChokepointWarning(stats);
|
|
158
|
-
}
|
|
159
|
-
expect(stats.events.chokepointWarningsIssued).toBe(0);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
// ── Dead Code Reference ──────────────────────────────────────────────
|
|
163
|
-
describe("Dead code reference detection", () => {
|
|
164
|
-
it("records when fan_in is exactly 0", () => {
|
|
165
|
-
const stats = createSessionStats();
|
|
166
|
-
const entityRisk = { fan_in: 0, fan_out: 3, risk_level: "normal" };
|
|
167
|
-
if (entityRisk.fan_in === 0) {
|
|
168
|
-
recordDeadCodeReference(stats);
|
|
169
|
-
}
|
|
170
|
-
expect(stats.events.deadCodeReferences).toBe(1);
|
|
171
|
-
});
|
|
172
|
-
it("does not record when fan_in > 0", () => {
|
|
173
|
-
const stats = createSessionStats();
|
|
174
|
-
const entityRisk = { fan_in: 2, fan_out: 3, risk_level: "normal" };
|
|
175
|
-
if (entityRisk.fan_in === 0) {
|
|
176
|
-
recordDeadCodeReference(stats);
|
|
177
|
-
}
|
|
178
|
-
expect(stats.events.deadCodeReferences).toBe(0);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
// ── Combined Event Tracking ──────────────────────────────────────────
|
|
182
|
-
describe("Combined event tracking across tool calls", () => {
|
|
183
|
-
it("totalCaughtEvents reflects all event types from a session", () => {
|
|
184
|
-
const stats = createSessionStats();
|
|
185
|
-
// Simulate a session with multiple events
|
|
186
|
-
recordViolation(stats); // check_rules found violation
|
|
187
|
-
recordViolation(stats); // check_rules found another
|
|
188
|
-
recordChokepointWarning(stats); // high-risk entity queried
|
|
189
|
-
recordCircularDep(stats); // circular import detected
|
|
190
|
-
recordSignaturePreservation(stats); // modified entity accessed
|
|
191
|
-
recordDeadCodeReference(stats); // dead code flagged
|
|
192
|
-
expect(totalCaughtEvents(stats.events)).toBe(6);
|
|
193
|
-
expect(stats.events.conventionViolationsCaught).toBe(2);
|
|
194
|
-
expect(stats.events.chokepointWarningsIssued).toBe(1);
|
|
195
|
-
expect(stats.events.circularDepsDetected).toBe(1);
|
|
196
|
-
expect(stats.events.signaturePreservations).toBe(1);
|
|
197
|
-
expect(stats.events.deadCodeReferences).toBe(1);
|
|
198
|
-
});
|
|
199
|
-
it("violationsCaught is separate from events.conventionViolationsCaught", () => {
|
|
200
|
-
const stats = createSessionStats();
|
|
201
|
-
recordViolation(stats);
|
|
202
|
-
// Both should track (violationsCaught is the legacy counter)
|
|
203
|
-
expect(stats.violationsCaught).toBe(1);
|
|
204
|
-
expect(stats.events.conventionViolationsCaught).toBe(1);
|
|
205
|
-
});
|
|
206
|
-
});
|