@unerr-ai/unerr 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +70 -194
  2. package/dist/cli.js +39149 -36991
  3. package/package.json +9 -2
  4. package/dist/__tests__/architecture-guard.test.js +0 -122
  5. package/dist/__tests__/arg-validator.test.js +0 -205
  6. package/dist/__tests__/ast-extractor.test.js +0 -203
  7. package/dist/__tests__/auto-bootstrap.test.js +0 -280
  8. package/dist/__tests__/background-indexer.test.js +0 -228
  9. package/dist/__tests__/blast-radius-engine.test.js +0 -200
  10. package/dist/__tests__/bridge-isolation.test.js +0 -37
  11. package/dist/__tests__/budget-enforcer.test.js +0 -53
  12. package/dist/__tests__/cfg-test-detection-perf.test.js +0 -82
  13. package/dist/__tests__/change-narrative.test.js +0 -190
  14. package/dist/__tests__/check-commit.test.js +0 -258
  15. package/dist/__tests__/checksum.test.js +0 -34
  16. package/dist/__tests__/commit-watcher.test.js +0 -154
  17. package/dist/__tests__/community-detection.test.js +0 -179
  18. package/dist/__tests__/community-tools.test.js +0 -299
  19. package/dist/__tests__/components.test.js +0 -449
  20. package/dist/__tests__/compression-log.test.js +0 -174
  21. package/dist/__tests__/compression-quality-monitor.test.js +0 -40
  22. package/dist/__tests__/config-healer.test.js +0 -165
  23. package/dist/__tests__/context-ledger.test.js +0 -58
  24. package/dist/__tests__/convention-detector.test.js +0 -99
  25. package/dist/__tests__/convention-learner.test.js +0 -86
  26. package/dist/__tests__/correction-detector.test.js +0 -330
  27. package/dist/__tests__/daemon-autostart-install.test.js +0 -283
  28. package/dist/__tests__/daemon-bridge.test.js +0 -222
  29. package/dist/__tests__/daemon-dashboard.test.js +0 -202
  30. package/dist/__tests__/daemon-registry.test.js +0 -240
  31. package/dist/__tests__/daemon-supervisor.test.js +0 -318
  32. package/dist/__tests__/daemon-version-check.test.js +0 -275
  33. package/dist/__tests__/decision-point-detector.test.js +0 -98
  34. package/dist/__tests__/deep-link.test.js +0 -143
  35. package/dist/__tests__/disallowed-tools.test.js +0 -115
  36. package/dist/__tests__/drift-tracker.test.js +0 -582
  37. package/dist/__tests__/durability-scorer.test.js +0 -152
  38. package/dist/__tests__/efficiency-tracker.test.js +0 -65
  39. package/dist/__tests__/enrich.test.js +0 -144
  40. package/dist/__tests__/entity-rewind.test.js +0 -248
  41. package/dist/__tests__/ephemeral.test.js +0 -111
  42. package/dist/__tests__/exploration-cost.test.js +0 -93
  43. package/dist/__tests__/fact-generator.test.js +0 -197
  44. package/dist/__tests__/file-l0-graph.test.js +0 -244
  45. package/dist/__tests__/file-logger.test.js +0 -82
  46. package/dist/__tests__/file-outline.test.js +0 -141
  47. package/dist/__tests__/file-read-protocol.test.js +0 -188
  48. package/dist/__tests__/format-encoder.test.js +0 -233
  49. package/dist/__tests__/git-attribution.test.js +0 -259
  50. package/dist/__tests__/graph-temporal-joiner.test.js +0 -219
  51. package/dist/__tests__/health-grade-enhanced.test.js +0 -138
  52. package/dist/__tests__/health-map-data.test.js +0 -173
  53. package/dist/__tests__/helpers/mcp-harness.js +0 -45
  54. package/dist/__tests__/helpers/mcp-harness.test.js +0 -68
  55. package/dist/__tests__/hook-dedup.test.js +0 -112
  56. package/dist/__tests__/hook-runner.test.js +0 -253
  57. package/dist/__tests__/indexer-cfg.test.js +0 -185
  58. package/dist/__tests__/indexer-cross-file.test.js +0 -172
  59. package/dist/__tests__/indexer-extraction.test.js +0 -245
  60. package/dist/__tests__/indexer-incremental.test.js +0 -232
  61. package/dist/__tests__/indexer-language-expansion.test.js +0 -165
  62. package/dist/__tests__/init-push.test.js +0 -131
  63. package/dist/__tests__/instruction-writer.test.js +0 -179
  64. package/dist/__tests__/intelligence-integration.test.js +0 -217
  65. package/dist/__tests__/intent-correlator.test.js +0 -175
  66. package/dist/__tests__/intent-detector.test.js +0 -235
  67. package/dist/__tests__/intent-encoder.test.js +0 -167
  68. package/dist/__tests__/java-build-tool-detection.test.js +0 -174
  69. package/dist/__tests__/layer3-sprint-q.test.js +0 -160
  70. package/dist/__tests__/layer3-sprint-r.test.js +0 -91
  71. package/dist/__tests__/layer3-sprint-s.test.js +0 -183
  72. package/dist/__tests__/layer3-sprint-t.test.js +0 -201
  73. package/dist/__tests__/layer3-sprint-u.test.js +0 -174
  74. package/dist/__tests__/layer4-sprint-ba2.test.js +0 -354
  75. package/dist/__tests__/layer4-sprint-ba4.test.js +0 -84
  76. package/dist/__tests__/layer4-sprint-vs.test.js +0 -105
  77. package/dist/__tests__/ledger-chains.test.js +0 -162
  78. package/dist/__tests__/lifecycle-machine.test.js +0 -226
  79. package/dist/__tests__/local-chat-provider.test.js +0 -170
  80. package/dist/__tests__/local-convention-detector.test.js +0 -308
  81. package/dist/__tests__/local-embeddings.test.js +0 -422
  82. package/dist/__tests__/local-graph.test.js +0 -540
  83. package/dist/__tests__/local-indexer.test.js +0 -228
  84. package/dist/__tests__/local-intelligence-l3.test.js +0 -332
  85. package/dist/__tests__/local-llm.test.js +0 -253
  86. package/dist/__tests__/local-mode-offline.test.js +0 -187
  87. package/dist/__tests__/local-mode-stats.test.js +0 -273
  88. package/dist/__tests__/local-mode-tui.test.js +0 -343
  89. package/dist/__tests__/local-parse.test.js +0 -199
  90. package/dist/__tests__/log-tailer.test.js +0 -208
  91. package/dist/__tests__/loop-breaker.test.js +0 -276
  92. package/dist/__tests__/loop-miner.test.js +0 -226
  93. package/dist/__tests__/mcp-config.test.js +0 -126
  94. package/dist/__tests__/mcp-content-json.test.js +0 -10
  95. package/dist/__tests__/mcp-envelope.test.js +0 -124
  96. package/dist/__tests__/metrics-store.test.js +0 -223
  97. package/dist/__tests__/native-watcher.test.js +0 -191
  98. package/dist/__tests__/navigation-hooks-agent-aware.test.js +0 -145
  99. package/dist/__tests__/negative-knowledge.test.js +0 -116
  100. package/dist/__tests__/network-boundary.test.js +0 -190
  101. package/dist/__tests__/network-firewall.test.js +0 -112
  102. package/dist/__tests__/nudge-invariants.test.js +0 -160
  103. package/dist/__tests__/nudge-v2.test.js +0 -225
  104. package/dist/__tests__/offline-rewind.test.js +0 -251
  105. package/dist/__tests__/open-threads.test.js +0 -89
  106. package/dist/__tests__/output-compressor.test.js +0 -93
  107. package/dist/__tests__/pending-violations.test.js +0 -112
  108. package/dist/__tests__/persistence-effectiveness.test.js +0 -143
  109. package/dist/__tests__/provider-factory.test.js +0 -42
  110. package/dist/__tests__/providers.test.js +0 -24
  111. package/dist/__tests__/proxy.test.js +0 -314
  112. package/dist/__tests__/query-router.test.js +0 -1018
  113. package/dist/__tests__/reasoning-quality-route.test.js +0 -138
  114. package/dist/__tests__/redactor.test.js +0 -120
  115. package/dist/__tests__/resource-monitor.test.js +0 -57
  116. package/dist/__tests__/response-envelope.test.js +0 -100
  117. package/dist/__tests__/risk-classifier.test.js +0 -101
  118. package/dist/__tests__/risk-signal-scope.test.js +0 -75
  119. package/dist/__tests__/rule-evaluator.test.js +0 -280
  120. package/dist/__tests__/scip-decoder.test.js +0 -49
  121. package/dist/__tests__/scip-downloader.test.js +0 -201
  122. package/dist/__tests__/scip-merger.test.js +0 -103
  123. package/dist/__tests__/search-index.test.js +0 -422
  124. package/dist/__tests__/semantic-enrichment.test.js +0 -360
  125. package/dist/__tests__/session-brief-builder.test.js +0 -187
  126. package/dist/__tests__/session-context.test.js +0 -221
  127. package/dist/__tests__/session-continuity.test.js +0 -144
  128. package/dist/__tests__/session-dedup.test.js +0 -74
  129. package/dist/__tests__/session-event-wiring.test.js +0 -206
  130. package/dist/__tests__/session-events.test.js +0 -149
  131. package/dist/__tests__/session-legend.test.js +0 -20
  132. package/dist/__tests__/session-persistence.test.js +0 -131
  133. package/dist/__tests__/session-resume-block.test.js +0 -107
  134. package/dist/__tests__/session-resume.test.js +0 -97
  135. package/dist/__tests__/session-summary-writer.test.js +0 -134
  136. package/dist/__tests__/shadow-ledger.test.js +0 -203
  137. package/dist/__tests__/shell-classifier.test.js +0 -151
  138. package/dist/__tests__/shell-compression-floor.test.js +0 -189
  139. package/dist/__tests__/shell-compression-v2.test.js +0 -339
  140. package/dist/__tests__/shell-compressor.test.js +0 -35
  141. package/dist/__tests__/shell-hooks.test.js +0 -128
  142. package/dist/__tests__/shell-strategies.test.js +0 -644
  143. package/dist/__tests__/shell-tee.test.js +0 -133
  144. package/dist/__tests__/signal-dedup.test.js +0 -158
  145. package/dist/__tests__/signal-reinforcer.test.js +0 -77
  146. package/dist/__tests__/signal-scorer.test.js +0 -251
  147. package/dist/__tests__/signal-show-store.test.js +0 -108
  148. package/dist/__tests__/smart-truncate.test.js +0 -215
  149. package/dist/__tests__/snapshot-v2.test.js +0 -113
  150. package/dist/__tests__/sprint-l1-local-mode.test.js +0 -130
  151. package/dist/__tests__/sprint-l10-boot.test.js +0 -220
  152. package/dist/__tests__/sprint-l9-offline-commands.test.js +0 -189
  153. package/dist/__tests__/sprint-q-persistent-context.test.js +0 -198
  154. package/dist/__tests__/sprint-s1-wiring.test.js +0 -215
  155. package/dist/__tests__/sprint-s2-wiring.test.js +0 -256
  156. package/dist/__tests__/sprint-s3-wiring.test.js +0 -195
  157. package/dist/__tests__/sprint-s4-wiring.test.js +0 -213
  158. package/dist/__tests__/sprint-s6-hooks.test.js +0 -222
  159. package/dist/__tests__/sprint-s7-persistent.test.js +0 -263
  160. package/dist/__tests__/sprint-s8-value.test.js +0 -167
  161. package/dist/__tests__/sprint-s9-behavioral.test.js +0 -179
  162. package/dist/__tests__/sprint3-intelligence.test.js +0 -297
  163. package/dist/__tests__/sprint5-mcp-server.test.js +0 -136
  164. package/dist/__tests__/startup-display.test.js +0 -302
  165. package/dist/__tests__/startup-log-file.test.js +0 -97
  166. package/dist/__tests__/stash-manager.test.js +0 -229
  167. package/dist/__tests__/state-detector.test.js +0 -92
  168. package/dist/__tests__/status-dashboard.test.js +0 -142
  169. package/dist/__tests__/temporal-facts.test.js +0 -292
  170. package/dist/__tests__/temporal-routes.test.js +0 -142
  171. package/dist/__tests__/test-detector.test.js +0 -174
  172. package/dist/__tests__/theme.test.js +0 -72
  173. package/dist/__tests__/timeline-agents.test.js +0 -122
  174. package/dist/__tests__/timeline-bootstrap.test.js +0 -176
  175. package/dist/__tests__/timeline-filters.test.js +0 -193
  176. package/dist/__tests__/timeline-markers.test.js +0 -151
  177. package/dist/__tests__/timeline-routes.test.js +0 -156
  178. package/dist/__tests__/timeline-store.test.js +0 -171
  179. package/dist/__tests__/token-counter.test.js +0 -86
  180. package/dist/__tests__/token-estimator.test.js +0 -96
  181. package/dist/__tests__/token-flow-api.test.js +0 -239
  182. package/dist/__tests__/token-flow-instrumentation.test.js +0 -437
  183. package/dist/__tests__/token-flow-persistence.test.js +0 -356
  184. package/dist/__tests__/token-flow-routes.test.js +0 -199
  185. package/dist/__tests__/token-flow.test.js +0 -695
  186. package/dist/__tests__/tool-clusters.test.js +0 -177
  187. package/dist/__tests__/transport-mux.test.js +0 -283
  188. package/dist/__tests__/turn-segmenter.test.js +0 -166
  189. package/dist/__tests__/uninstall.test.js +0 -141
  190. package/dist/__tests__/warm-start-policy.test.js +0 -271
  191. package/dist/__tests__/wire-cap-nudge.test.js +0 -77
  192. package/dist/__tests__/worker-pool.test.js +0 -101
  193. package/dist/ui/assets/index-7gl3mIuY.css +0 -1
  194. package/dist/ui/assets/index-CX4FCWGT.js +0 -10
  195. package/dist/ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
  196. package/dist/ui/assets/vis-network-NIJHUFI3.js +0 -908
  197. package/dist/ui/fonts/jetbrains-mono-latin-400-normal.woff +0 -0
  198. package/dist/ui/icon-wordmark.png +0 -0
  199. package/dist/ui/icon-wordmark.svg +0 -30
  200. package/dist/ui/icon.png +0 -0
  201. package/dist/ui/icon.svg +0 -25
  202. package/dist/ui/index.html +0 -15
  203. package/dist/ui/prototype-sandbox/index.html +0 -257
  204. package/dist/ui/screenshots/activity.png +0 -0
  205. package/dist/ui/screenshots/code-base-intelligence.png +0 -0
  206. package/dist/ui/screenshots/dashboard.png +0 -0
  207. package/dist/ui/screenshots/project-memory.png +0 -0
  208. package/dist/ui/screenshots/reasoning-quality.png +0 -0
  209. package/dist/ui/screenshots/reasoning-session.png +0 -0
  210. package/dist/ui/screenshots/token-session.png +0 -0
  211. package/dist/ui/screenshots/token-trace-main.png +0 -0
  212. package/dist/ui/screenshots/token-turn.png +0 -0
  213. package/dist/ui/unerr-wordmark.png +0 -0
  214. package/dist/ui/unerr-wordmark.svg +0 -9
  215. package/dist/ui/unerr.png +0 -0
  216. package/dist/ui/unerr.svg +0 -25
  217. package/dist/ui/web-app-manifest-192x192.png +0 -0
  218. package/dist/ui/web-app-manifest-512x512.png +0 -0
@@ -1,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
- });