@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,263 +0,0 @@
1
- /**
2
- * Sprint S7: Wire Persistent Context (Layer 2) — Integration Tests.
3
- *
4
- * Verifies:
5
- * - S7.1: Auto-snapshot triggers fire on test_pass and pre_critical_change
6
- * - S7.2+S7.3: Session resume generates structured greeting from ledger
7
- * - S7.4: Resume context injected as _context.session_resume on first response
8
- * - S7.5: Durability scorer wired into entity responses (< 0.5 warning)
9
- * - S7.6: Negative knowledge anti-patterns injected before agent touches code
10
- * - S7.7: Timeline fork created on rewind
11
- * - S7.8: Durability data feeds into session health monitor
12
- */
13
- import { describe, expect, it } from "vitest";
14
- import { createDurabilityScorer } from "../intelligence/durability-scorer.js";
15
- import { detectInstableEntities } from "../intelligence/negative-knowledge.js";
16
- import { orderContextFields, } from "../intelligence/query-router.js";
17
- import { generateSessionResume } from "../proxy/session-resume.js";
18
- import { shouldAutoSnapshot } from "../tracking/auto-snapshot-triggers.js";
19
- import { createTimelineFork } from "../tracking/timeline-fork.js";
20
- function makeLedgerEntry(overrides) {
21
- return {
22
- id: overrides.id ?? `e${Math.random().toString(36).slice(2, 10)}`,
23
- ts: overrides.ts ?? new Date().toISOString(),
24
- tool: overrides.tool ?? "get_function",
25
- args_summary: overrides.args_summary ?? {},
26
- result_summary: overrides.result_summary ?? { found: true },
27
- branch: overrides.branch ?? "main",
28
- session_id: overrides.session_id ?? "sess-1",
29
- };
30
- }
31
- describe("Sprint S7: Wire Persistent Context", () => {
32
- describe("S7.1: Auto-snapshot triggers in proxy lifecycle", () => {
33
- it("triggers on test_pass result", () => {
34
- const result = shouldAutoSnapshot("run_command", { command: "npm test" }, { exitCode: 0 });
35
- expect(result).toBeTruthy();
36
- expect(result.type).toBe("test_pass");
37
- });
38
- it("triggers on high fan_in threshold (pre-critical change)", () => {
39
- const result = shouldAutoSnapshot("sync_local_diff", { file: "src/core.ts", entity_key: "src/core.ts::processPayment" }, { fan_in: 50 }, 50);
40
- expect(result).toBeTruthy();
41
- expect(result.type).toBe("pre_critical_change");
42
- });
43
- it("does not trigger on normal tool call", () => {
44
- const result = shouldAutoSnapshot("search_code", { query: "hello" }, { count: 5 });
45
- expect(result).toBeFalsy();
46
- });
47
- });
48
- describe("S7.2+S7.3: Session resume from ledger", () => {
49
- it("generates resume context from ledger entries", () => {
50
- const entries = [
51
- makeLedgerEntry({
52
- tool: "get_function",
53
- args_summary: { key: "src/auth.ts::login" },
54
- ts: "2026-05-01T10:00:00Z",
55
- }),
56
- makeLedgerEntry({
57
- tool: "sync_local_diff",
58
- args_summary: { files: ["src/auth.ts"] },
59
- ts: "2026-05-01T10:01:00Z",
60
- }),
61
- makeLedgerEntry({
62
- tool: "get_callers",
63
- args_summary: { key: "src/auth.ts::login" },
64
- ts: "2026-05-01T10:02:00Z",
65
- }),
66
- makeLedgerEntry({
67
- tool: "sync_local_diff",
68
- args_summary: { files: ["src/db.ts"] },
69
- ts: "2026-05-01T10:03:00Z",
70
- }),
71
- ];
72
- const resume = generateSessionResume(entries);
73
- expect(resume).not.toBeNull();
74
- expect(resume.summary).toBeDefined();
75
- expect(resume.filesModified.length).toBeGreaterThan(0);
76
- });
77
- it("returns null for empty entries", () => {
78
- const resume = generateSessionResume([]);
79
- expect(resume).toBeNull();
80
- });
81
- });
82
- describe("S7.4: Context ordering includes session_resume", () => {
83
- it("orders session_resume between corrections and reminder", () => {
84
- const ctx = {
85
- session_resume: {
86
- summary: "Last session modified auth.ts",
87
- filesModified: ["src/auth.ts"],
88
- incompleteEntities: ["login"],
89
- },
90
- blast_radius: "14 callers",
91
- conventions: ["naming: camelCase"],
92
- };
93
- const ordered = orderContextFields(ctx);
94
- const keys = Object.keys(ordered);
95
- expect(keys.indexOf("blast_radius")).toBeLessThan(keys.indexOf("session_resume"));
96
- expect(keys.indexOf("session_resume")).toBeLessThan(keys.indexOf("conventions"));
97
- });
98
- });
99
- describe("S7.5: Durability scorer wired into entity responses", () => {
100
- it("computes durability scores from ledger entries", () => {
101
- const baseTime = Date.now();
102
- // Fragile: modified 4 times rapidly, stable: modified once early
103
- const entries = [
104
- makeLedgerEntry({
105
- tool: "sync_local_diff",
106
- args_summary: { files: ["src/stable.ts"] },
107
- ts: new Date(baseTime).toISOString(),
108
- }),
109
- makeLedgerEntry({
110
- tool: "sync_local_diff",
111
- args_summary: { files: ["src/fragile.ts"] },
112
- ts: new Date(baseTime + 10000).toISOString(),
113
- }),
114
- makeLedgerEntry({
115
- tool: "sync_local_diff",
116
- args_summary: { files: ["src/fragile.ts"] },
117
- ts: new Date(baseTime + 20000).toISOString(),
118
- }),
119
- makeLedgerEntry({
120
- tool: "sync_local_diff",
121
- args_summary: { files: ["src/fragile.ts"] },
122
- ts: new Date(baseTime + 30000).toISOString(),
123
- }),
124
- makeLedgerEntry({
125
- tool: "sync_local_diff",
126
- args_summary: { files: ["src/fragile.ts"] },
127
- ts: new Date(baseTime + 600000).toISOString(),
128
- }),
129
- ];
130
- const scorer = createDurabilityScorer();
131
- scorer.computeScores(entries);
132
- const fragile = scorer.getScore("src/fragile.ts");
133
- expect(fragile).not.toBeNull();
134
- expect(fragile.modificationCount).toBe(4);
135
- const stable = scorer.getScore("src/stable.ts");
136
- expect(stable).not.toBeNull();
137
- expect(stable.modificationCount).toBe(1);
138
- // Fragile should have lower or equal durability (more churn = less stable)
139
- expect(fragile.score).toBeLessThanOrEqual(stable.score);
140
- });
141
- it("getTopUnstable returns most fragile entities", () => {
142
- const baseTime = Date.now();
143
- const entries = [
144
- makeLedgerEntry({
145
- tool: "sync_local_diff",
146
- args_summary: { files: ["src/a.ts"] },
147
- ts: new Date(baseTime).toISOString(),
148
- }),
149
- makeLedgerEntry({
150
- tool: "sync_local_diff",
151
- args_summary: { files: ["src/a.ts"] },
152
- ts: new Date(baseTime + 1000).toISOString(),
153
- }),
154
- makeLedgerEntry({
155
- tool: "sync_local_diff",
156
- args_summary: { files: ["src/a.ts"] },
157
- ts: new Date(baseTime + 2000).toISOString(),
158
- }),
159
- makeLedgerEntry({
160
- tool: "sync_local_diff",
161
- args_summary: { files: ["src/b.ts"] },
162
- ts: new Date(baseTime).toISOString(),
163
- }),
164
- ];
165
- const scorer = createDurabilityScorer();
166
- scorer.computeScores(entries);
167
- const unstable = scorer.getTopUnstable(2);
168
- expect(unstable.length).toBeGreaterThan(0);
169
- expect(unstable[0].entityKey).toBe("src/a.ts");
170
- });
171
- });
172
- describe("S7.6: Negative knowledge anti-pattern injection", () => {
173
- it("detects instable entities from rapid modifications", () => {
174
- const baseTime = Date.now();
175
- const entries = [
176
- makeLedgerEntry({
177
- tool: "sync_local_diff",
178
- args_summary: { files: ["src/flaky.ts"] },
179
- ts: new Date(baseTime).toISOString(),
180
- }),
181
- makeLedgerEntry({
182
- tool: "sync_local_diff",
183
- args_summary: { files: ["src/flaky.ts"] },
184
- ts: new Date(baseTime + 30000).toISOString(),
185
- }),
186
- makeLedgerEntry({
187
- tool: "sync_local_diff",
188
- args_summary: { files: ["src/flaky.ts"] },
189
- ts: new Date(baseTime + 60000).toISOString(),
190
- }),
191
- makeLedgerEntry({
192
- tool: "sync_local_diff",
193
- args_summary: { files: ["src/flaky.ts"] },
194
- ts: new Date(baseTime + 90000).toISOString(),
195
- }),
196
- makeLedgerEntry({
197
- tool: "sync_local_diff",
198
- args_summary: { files: ["src/flaky.ts"] },
199
- ts: new Date(baseTime + 120000).toISOString(),
200
- }),
201
- ];
202
- const corrections = detectInstableEntities(entries, 10 * 60 * 1000);
203
- expect(corrections.length).toBeGreaterThan(0);
204
- expect(corrections[0].entityKey).toBe("src/flaky.ts");
205
- expect(corrections[0].pattern).toBeDefined();
206
- expect(corrections[0].reason).toBeDefined();
207
- });
208
- it("does not flag entities modified only once", () => {
209
- const entries = [
210
- makeLedgerEntry({
211
- tool: "sync_local_diff",
212
- args_summary: { files: ["src/once.ts"] },
213
- ts: new Date().toISOString(),
214
- }),
215
- ];
216
- const corrections = detectInstableEntities(entries);
217
- const forOnce = corrections.filter((c) => c.entityKey === "src/once.ts");
218
- expect(forOnce.length).toBe(0);
219
- });
220
- });
221
- describe("S7.7: Timeline fork on rewind", () => {
222
- it("creates timeline fork with abandoned entities", () => {
223
- const fork = createTimelineFork("snap-123", ["processPayment", "validateOrder"], ["fix the payment flow"], "Approach failed — infinite loop");
224
- expect(fork.forkPoint).toBe("snap-123");
225
- expect(fork.abandonedBranch.entityChanges).toContain("processPayment");
226
- expect(fork.abandonedBranch.entityChanges).toContain("validateOrder");
227
- expect(fork.abandonedBranch.promptsTried).toContain("fix the payment flow");
228
- expect(fork.abandonedBranch.failureReason).toBe("Approach failed — infinite loop");
229
- expect(fork.newBranch.timelineId).toBeGreaterThan(fork.abandonedBranch.timelineId);
230
- });
231
- it("deduplicates abandoned entities", () => {
232
- const fork = createTimelineFork("snap-456", ["entity1", "entity1", "entity2", "entity2"], []);
233
- expect(fork.abandonedBranch.entityChanges).toEqual([
234
- "entity1",
235
- "entity2",
236
- ]);
237
- });
238
- });
239
- describe("S7.8: Durability feeds into session health monitor", () => {
240
- it("context ordering places durability_warning with high priority", () => {
241
- const ctx = {
242
- durability_warning: "FRAGILE: entity has durability 0.3",
243
- conventions: ["naming: camelCase"],
244
- blast_radius: "14 callers",
245
- };
246
- const ordered = orderContextFields(ctx);
247
- const keys = Object.keys(ordered);
248
- // durability_warning should come after blast_radius but before conventions
249
- expect(keys.indexOf("blast_radius")).toBeLessThan(keys.indexOf("durability_warning"));
250
- expect(keys.indexOf("durability_warning")).toBeLessThan(keys.indexOf("conventions"));
251
- });
252
- it("anti_patterns ordered with high priority", () => {
253
- const ctx = {
254
- anti_patterns: ["ANTI-PATTERN: repeated modification without test"],
255
- conventions: ["naming: camelCase"],
256
- blast_radius: "14 callers",
257
- };
258
- const ordered = orderContextFields(ctx);
259
- const keys = Object.keys(ordered);
260
- expect(keys.indexOf("anti_patterns")).toBeLessThan(keys.indexOf("conventions"));
261
- });
262
- });
263
- });
@@ -1,167 +0,0 @@
1
- /**
2
- * Sprint S8: Value Surfacing — Tests
3
- *
4
- * S8.1: Guard fires once when threshold crossed
5
- * S8.2: Per-response _meta has optimization + powered_by
6
- * S8.3: Scorecard formats correctly
7
- * S8.4: Weekly accumulator persists and resets
8
- * S8.7: Counterfactual explanation format
9
- */
10
- import { describe, expect, it } from "vitest";
11
- import { assembleValueMeta, createValueGuard, formatCounterfactual, formatScorecard, } from "../config/value-surfacing.js";
12
- import { formatStatsReport, loadStats, } from "../tracking/weekly-accumulator.js";
13
- // ── S8.1: Value Guard ──────────────────────────────────────────────
14
- describe("S8.1: Value Guard", () => {
15
- it("does not fire below threshold", () => {
16
- const guard = createValueGuard(1.0);
17
- expect(guard.check(0.49)).toBeNull();
18
- expect(guard.hasFired()).toBe(false);
19
- });
20
- it("fires once when threshold crossed", () => {
21
- const guard = createValueGuard(0.5);
22
- const msg = guard.check(0.75);
23
- expect(msg).toContain("$0.75");
24
- expect(guard.hasFired()).toBe(true);
25
- });
26
- it("does not fire a second time", () => {
27
- const guard = createValueGuard(0.5);
28
- guard.check(0.6); // fires
29
- expect(guard.check(1.0)).toBeNull(); // already fired
30
- });
31
- it("resets correctly", () => {
32
- const guard = createValueGuard(0.5);
33
- guard.check(0.6);
34
- guard.reset();
35
- expect(guard.hasFired()).toBe(false);
36
- const msg = guard.check(0.8);
37
- expect(msg).toContain("$0.80");
38
- });
39
- });
40
- // ── S8.2: Per-response _meta ───────────────────────────────────────
41
- describe("S8.2: Value Meta Assembly", () => {
42
- it("assembles basic meta fields", () => {
43
- const meta = assembleValueMeta(1500, 0.009);
44
- expect(meta.tokens_saved).toBe(1500);
45
- expect(meta.dollar_savings).toBe(0.009);
46
- expect(meta.powered_by).toContain("unerr");
47
- expect(meta.optimization).toBeUndefined();
48
- });
49
- it("includes optimization description when provided", () => {
50
- const meta = assembleValueMeta(3000, 0.018, "blast_radius served 7 files");
51
- expect(meta.optimization).toBe("blast_radius served 7 files");
52
- });
53
- it("rounds dollar savings to 6 decimal places", () => {
54
- const meta = assembleValueMeta(100, 0.0001234567);
55
- expect(meta.dollar_savings).toBe(0.000123);
56
- });
57
- });
58
- // ── S8.3: Session Scorecard ────────────────────────────────────────
59
- describe("S8.3: Session Scorecard", () => {
60
- it("formats basic scorecard", () => {
61
- const input = {
62
- toolCalls: 47,
63
- tokensSaved: 45200,
64
- dollarsSaved: 0.27,
65
- efficiency: 73,
66
- durationMs: 12 * 60_000,
67
- blastRadiusComputed: 4,
68
- conventionsInjected: 3,
69
- outputsCompressed: 12,
70
- correctionsApplied: 2,
71
- wrongApproachesPrevented: 0,
72
- };
73
- const sc = formatScorecard(input);
74
- expect(sc.toolCalls).toBe(47);
75
- expect(sc.tokensSaved).toBe("45.2K");
76
- expect(sc.dollarsSaved).toBe("$0.27");
77
- expect(sc.efficiency).toBe("73%");
78
- expect(sc.duration).toBe("12 min");
79
- expect(sc.intelligenceApplied).toContain("4 blast radius computations");
80
- expect(sc.intelligenceApplied).toContain("3 convention injections");
81
- expect(sc.intelligenceApplied).toContain("12 outputs compressed");
82
- expect(sc.intelligenceApplied).toContain("2 corrections applied");
83
- });
84
- it("handles zero duration", () => {
85
- const input = {
86
- toolCalls: 3,
87
- tokensSaved: 500,
88
- dollarsSaved: 0.003,
89
- efficiency: 50,
90
- durationMs: 20_000,
91
- blastRadiusComputed: 0,
92
- conventionsInjected: 0,
93
- outputsCompressed: 0,
94
- correctionsApplied: 0,
95
- wrongApproachesPrevented: 0,
96
- };
97
- const sc = formatScorecard(input);
98
- expect(sc.duration).toBe("<1 min");
99
- expect(sc.intelligenceApplied).toHaveLength(0);
100
- });
101
- it("formats million tokens", () => {
102
- const input = {
103
- toolCalls: 200,
104
- tokensSaved: 1_500_000,
105
- dollarsSaved: 9.0,
106
- efficiency: 85,
107
- durationMs: 45 * 60_000,
108
- blastRadiusComputed: 10,
109
- conventionsInjected: 5,
110
- outputsCompressed: 30,
111
- correctionsApplied: 4,
112
- wrongApproachesPrevented: 2,
113
- };
114
- const sc = formatScorecard(input);
115
- expect(sc.tokensSaved).toBe("1.5M");
116
- expect(sc.intelligenceApplied).toContain("2 wrong approaches prevented");
117
- });
118
- });
119
- // ── S8.4: Weekly Accumulator ───────────────────────────────────────
120
- describe("S8.4: Weekly Accumulator", () => {
121
- it("loadStats returns valid empty structure", () => {
122
- const stats = loadStats();
123
- expect(stats.version).toBe(1);
124
- expect(stats.weekly.sessions).toBeGreaterThanOrEqual(0);
125
- expect(stats.allTime.totalSessions).toBeGreaterThanOrEqual(0);
126
- expect(stats.lastUpdated).toBeDefined();
127
- });
128
- it("formatStatsReport produces readable output", () => {
129
- const stats = loadStats();
130
- // Seed some data for formatting
131
- stats.weekly.sessions = 5;
132
- stats.weekly.tokensSaved = 280_000;
133
- stats.weekly.dollarsSaved = 1.68;
134
- stats.weekly.avgEfficiency = 73;
135
- stats.weekly.violationsCaught = 12;
136
- stats.allTime.totalSessions = 20;
137
- stats.allTime.totalTokensSaved = 1_200_000;
138
- stats.allTime.totalDollarsSaved = 7.2;
139
- stats.allTime.totalViolationsCaught = 45;
140
- const report = formatStatsReport(stats);
141
- expect(report).toContain("This week:");
142
- expect(report).toContain("All time:");
143
- expect(report).toContain("$1.68");
144
- expect(report).toContain("280.0K");
145
- expect(report).toContain("73%");
146
- expect(report).toContain("1.2M");
147
- });
148
- });
149
- // ── S8.7: Counterfactual Explanation ───────────────────────────────
150
- describe("S8.7: Counterfactual Explanation", () => {
151
- it("formats basic counterfactual", () => {
152
- const result = formatCounterfactual(100_000, 35_000);
153
- expect(result).toContain("Without unerr: ~100.0K tokens");
154
- expect(result).toContain("with unerr: 35.0K tokens");
155
- expect(result).toContain("65% reduction");
156
- });
157
- it("handles zero tokens without", () => {
158
- const result = formatCounterfactual(0, 0);
159
- expect(result).toContain("0% reduction");
160
- });
161
- it("formats millions", () => {
162
- const result = formatCounterfactual(2_000_000, 600_000);
163
- expect(result).toContain("2.0M");
164
- expect(result).toContain("600.0K");
165
- expect(result).toContain("70% reduction");
166
- });
167
- });
@@ -1,179 +0,0 @@
1
- /**
2
- * Sprint S9: Day 1 Behavioral Hooks — Tests
3
- *
4
- * S9.1: Circuit breaker fires after 4 retries on same entity
5
- * S9.2: Health-triggered circuit break (health < 0.3)
6
- * S9.3: Caught event counter increments
7
- * S9.4: Halt message format
8
- * S9.5: Convention violations wire into caught counter
9
- * S9.6: Caught counter feeds value counter (every 3rd event)
10
- */
11
- import { beforeEach, describe, expect, it } from "vitest";
12
- import { SessionContext } from "../intelligence/session-context.js";
13
- import { createSessionEvents, totalCaughtEvents, } from "../proxy/session-stats.js";
14
- import { LedgerCircuitBreaker, formatHaltMessage, } from "../tracking/circuit-breaker.js";
15
- // ── S9.1: Circuit Breaker fires after 4 retries ───────────────────
16
- describe("S9.1: Circuit Breaker — entity retry detection", () => {
17
- let breaker;
18
- beforeEach(() => {
19
- breaker = new LedgerCircuitBreaker();
20
- });
21
- it("does not fire with fewer than 4 violation attempts", () => {
22
- breaker.recordAttempt("src/core.ts::processPayment", true);
23
- breaker.recordAttempt("src/core.ts::processPayment", true);
24
- breaker.recordAttempt("src/core.ts::processPayment", true);
25
- const result = breaker.check(["src/core.ts::processPayment"]);
26
- expect(result).toBeNull();
27
- });
28
- it("fires after 4 consecutive violation attempts", () => {
29
- const entity = "src/core.ts::processPayment";
30
- breaker.recordAttempt(entity, true);
31
- breaker.recordAttempt(entity, true);
32
- breaker.recordAttempt(entity, true);
33
- breaker.recordAttempt(entity, true);
34
- const result = breaker.check([entity]);
35
- expect(result).not.toBeNull();
36
- expect(result?.triggered).toBe(true);
37
- expect(result?.entity).toBe(entity);
38
- expect(result?.attempts).toBe(4);
39
- });
40
- it("does not fire if attempts have no violations", () => {
41
- const entity = "src/utils.ts::formatDate";
42
- breaker.recordAttempt(entity, false);
43
- breaker.recordAttempt(entity, false);
44
- breaker.recordAttempt(entity, false);
45
- breaker.recordAttempt(entity, false);
46
- const result = breaker.check([entity]);
47
- expect(result).toBeNull();
48
- });
49
- it("does not fire if violations are interspersed with successes", () => {
50
- const entity = "src/api.ts::handleRequest";
51
- breaker.recordAttempt(entity, true);
52
- breaker.recordAttempt(entity, true);
53
- breaker.recordAttempt(entity, false); // success breaks the streak
54
- breaker.recordAttempt(entity, true);
55
- breaker.recordAttempt(entity, true);
56
- const result = breaker.check([entity]);
57
- expect(result).toBeNull();
58
- });
59
- it("remains halted on subsequent checks", () => {
60
- const entity = "src/core.ts::processPayment";
61
- for (let i = 0; i < 4; i++)
62
- breaker.recordAttempt(entity, true);
63
- breaker.check([entity]); // trips it
64
- // Subsequent check should still show halted
65
- const result = breaker.check([entity]);
66
- expect(result).not.toBeNull();
67
- expect(result?.triggered).toBe(true);
68
- });
69
- it("resets correctly for a specific entity", () => {
70
- const entity = "src/core.ts::processPayment";
71
- for (let i = 0; i < 4; i++)
72
- breaker.recordAttempt(entity, true);
73
- breaker.check([entity]);
74
- breaker.reset(entity);
75
- expect(breaker.isHalted(entity)).toBe(false);
76
- const result = breaker.check([entity]);
77
- expect(result).toBeNull();
78
- });
79
- it("tracks multiple entities independently", () => {
80
- const entity1 = "src/a.ts::foo";
81
- const entity2 = "src/b.ts::bar";
82
- for (let i = 0; i < 4; i++)
83
- breaker.recordAttempt(entity1, true);
84
- breaker.recordAttempt(entity2, true);
85
- breaker.recordAttempt(entity2, true);
86
- const result1 = breaker.check([entity1]);
87
- const result2 = breaker.check([entity2]);
88
- expect(result1).not.toBeNull();
89
- expect(result2).toBeNull();
90
- });
91
- });
92
- // ── S9.3: Caught Event Counter ─────────────────────────────────────
93
- describe("S9.3: Caught event counter", () => {
94
- it("increments convention violations", () => {
95
- const events = createSessionEvents();
96
- events.conventionViolationsCaught += 3;
97
- expect(totalCaughtEvents(events)).toBe(3);
98
- });
99
- it("increments chokepoint warnings", () => {
100
- const events = createSessionEvents();
101
- events.chokepointWarningsIssued += 2;
102
- expect(totalCaughtEvents(events)).toBe(2);
103
- });
104
- it("increments circular deps", () => {
105
- const events = createSessionEvents();
106
- events.circularDepsDetected += 1;
107
- expect(totalCaughtEvents(events)).toBe(1);
108
- });
109
- it("sums all caught categories", () => {
110
- const events = createSessionEvents();
111
- events.conventionViolationsCaught = 2;
112
- events.chokepointWarningsIssued = 3;
113
- events.circularDepsDetected = 1;
114
- events.signaturePreservations = 1;
115
- events.deadCodeReferences = 2;
116
- expect(totalCaughtEvents(events)).toBe(9);
117
- });
118
- });
119
- // ── S9.4: Halt Message Formatter ───────────────────────────────────
120
- describe("S9.4: Halt message formatter", () => {
121
- it("includes entity name", () => {
122
- const msg = formatHaltMessage("src/core.ts::processPayment", 4);
123
- expect(msg).toContain("src/core.ts::processPayment");
124
- });
125
- it("includes attempt count", () => {
126
- const msg = formatHaltMessage("src/api.ts::handleAuth", 6);
127
- expect(msg).toContain("6 times");
128
- });
129
- it("starts with Stop", () => {
130
- const msg = formatHaltMessage("entity", 4);
131
- expect(msg).toMatch(/^Stop\./);
132
- });
133
- it("suggests different approach", () => {
134
- const msg = formatHaltMessage("entity", 5);
135
- expect(msg).toContain("different approach");
136
- });
137
- });
138
- // ── S9.6: Value Counter integration ────────────────────────────────
139
- describe("S9.6: Value counter fires on caught events", () => {
140
- it("fires at every 3rd caught event after 10 tool calls", () => {
141
- const ctx = new SessionContext();
142
- const events = createSessionEvents();
143
- // Record 11 tool calls (value counter only fires after 10)
144
- for (let i = 0; i < 11; i++)
145
- ctx.recordToolCall();
146
- // 1st and 2nd caught event — no fire
147
- events.conventionViolationsCaught = 1;
148
- expect(ctx.getValueCounter(events)).toBeUndefined();
149
- events.conventionViolationsCaught = 2;
150
- expect(ctx.getValueCounter(events)).toBeUndefined();
151
- // 3rd caught event — fires
152
- events.conventionViolationsCaught = 3;
153
- const msg = ctx.getValueCounter(events);
154
- expect(msg).toContain("3 issues");
155
- });
156
- it("does not fire before 10 tool calls", () => {
157
- const ctx = new SessionContext();
158
- const events = createSessionEvents();
159
- for (let i = 0; i < 5; i++)
160
- ctx.recordToolCall();
161
- events.conventionViolationsCaught = 3;
162
- expect(ctx.getValueCounter(events)).toBeUndefined();
163
- });
164
- it("fires again at 6th caught event", () => {
165
- const ctx = new SessionContext();
166
- const events = createSessionEvents();
167
- for (let i = 0; i < 11; i++)
168
- ctx.recordToolCall();
169
- events.conventionViolationsCaught = 3;
170
- ctx.getValueCounter(events); // fires at 3
171
- events.conventionViolationsCaught = 4;
172
- expect(ctx.getValueCounter(events)).toBeUndefined();
173
- events.conventionViolationsCaught = 5;
174
- expect(ctx.getValueCounter(events)).toBeUndefined();
175
- events.conventionViolationsCaught = 6;
176
- const msg = ctx.getValueCounter(events);
177
- expect(msg).toContain("6 issues");
178
- });
179
- });