@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,152 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createDurabilityScorer } from "../intelligence/durability-scorer.js";
3
- describe("createDurabilityScorer", () => {
4
- it("computes durability for entities", () => {
5
- const scorer = createDurabilityScorer();
6
- const entries = [
7
- {
8
- id: "e1",
9
- ts: "2026-04-28T10:00:00Z",
10
- tool: "sync_local_diff",
11
- args_summary: { files: ["src/auth.ts"] },
12
- },
13
- {
14
- id: "e2",
15
- ts: "2026-04-29T10:00:00Z",
16
- tool: "sync_local_diff",
17
- args_summary: { files: ["src/auth.ts"] },
18
- },
19
- {
20
- id: "e3",
21
- ts: "2026-04-30T10:00:00Z",
22
- tool: "sync_local_diff",
23
- args_summary: { files: ["src/auth.ts"] },
24
- },
25
- ];
26
- const scores = scorer.computeScores(entries);
27
- const score = scores.get("src/auth.ts");
28
- expect(score).toBeDefined();
29
- expect(score?.modificationCount).toBe(3);
30
- expect(score?.score).toBeGreaterThan(0);
31
- expect(score?.score).toBeLessThanOrEqual(1);
32
- });
33
- it("entity modified 3x in 3 sessions with 1 day survival has low durability", () => {
34
- const scorer = createDurabilityScorer();
35
- const day = 24 * 60 * 60 * 1000;
36
- const now = Date.now();
37
- const entries = [
38
- {
39
- id: "e1",
40
- ts: new Date(now - 3 * day).toISOString(),
41
- tool: "sync_local_diff",
42
- args_summary: { files: ["src/flaky.ts"] },
43
- },
44
- {
45
- id: "e2",
46
- ts: new Date(now - 2 * day).toISOString(),
47
- tool: "sync_local_diff",
48
- args_summary: { files: ["src/flaky.ts"] },
49
- },
50
- {
51
- id: "e3",
52
- ts: new Date(now - 1 * day).toISOString(),
53
- tool: "sync_local_diff",
54
- args_summary: { files: ["src/flaky.ts"] },
55
- },
56
- ];
57
- scorer.computeScores(entries);
58
- const score = scorer.getScore("src/flaky.ts");
59
- expect(score).toBeDefined();
60
- expect(score?.score).toBeLessThan(0.7);
61
- });
62
- it("stable entity has high durability", () => {
63
- const scorer = createDurabilityScorer();
64
- const day = 24 * 60 * 60 * 1000;
65
- const now = Date.now();
66
- const entries = [
67
- {
68
- id: "e1",
69
- ts: new Date(now - 30 * day).toISOString(),
70
- tool: "sync_local_diff",
71
- args_summary: { files: ["src/stable.ts"] },
72
- },
73
- {
74
- id: "e2",
75
- ts: new Date(now - 1 * day).toISOString(),
76
- tool: "get_function",
77
- args_summary: { key: "src/other.ts" },
78
- },
79
- ];
80
- scorer.computeScores(entries);
81
- const score = scorer.getScore("src/stable.ts");
82
- expect(score).toBeDefined();
83
- expect(score?.score).toBeGreaterThanOrEqual(0.9);
84
- });
85
- it("getTopUnstable returns lowest scores", () => {
86
- const scorer = createDurabilityScorer();
87
- const now = Date.now();
88
- const day = 24 * 60 * 60 * 1000;
89
- const entries = [
90
- {
91
- id: "e1",
92
- ts: new Date(now - 10 * day).toISOString(),
93
- tool: "sync_local_diff",
94
- args_summary: { files: ["src/stable.ts"] },
95
- },
96
- {
97
- id: "e2",
98
- ts: new Date(now - 3 * day).toISOString(),
99
- tool: "sync_local_diff",
100
- args_summary: { files: ["src/flaky.ts"] },
101
- },
102
- {
103
- id: "e3",
104
- ts: new Date(now - 2 * day).toISOString(),
105
- tool: "sync_local_diff",
106
- args_summary: { files: ["src/flaky.ts"] },
107
- },
108
- {
109
- id: "e4",
110
- ts: new Date(now - 1 * day).toISOString(),
111
- tool: "sync_local_diff",
112
- args_summary: { files: ["src/flaky.ts"] },
113
- },
114
- ];
115
- scorer.computeScores(entries);
116
- const unstable = scorer.getTopUnstable(1);
117
- expect(unstable[0]?.entityKey).toBe("src/flaky.ts");
118
- });
119
- it("getTopDurable returns highest scores", () => {
120
- const scorer = createDurabilityScorer();
121
- const now = Date.now();
122
- const day = 24 * 60 * 60 * 1000;
123
- const entries = [
124
- {
125
- id: "e1",
126
- ts: new Date(now - 10 * day).toISOString(),
127
- tool: "sync_local_diff",
128
- args_summary: { files: ["src/stable.ts"] },
129
- },
130
- {
131
- id: "e2",
132
- ts: new Date(now - 1 * day).toISOString(),
133
- tool: "sync_local_diff",
134
- args_summary: { files: ["src/flaky.ts"] },
135
- },
136
- {
137
- id: "e3",
138
- ts: new Date(now - 0.5 * day).toISOString(),
139
- tool: "sync_local_diff",
140
- args_summary: { files: ["src/flaky.ts"] },
141
- },
142
- ];
143
- scorer.computeScores(entries);
144
- const durable = scorer.getTopDurable(1);
145
- expect(durable[0]?.entityKey).toBe("src/stable.ts");
146
- });
147
- it("handles empty entries", () => {
148
- const scorer = createDurabilityScorer();
149
- const scores = scorer.computeScores([]);
150
- expect(scores.size).toBe(0);
151
- });
152
- });
@@ -1,65 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createEfficiencyTracker } from "../proxy/efficiency-tracker.js";
3
- describe("createEfficiencyTracker", () => {
4
- it("starts with zero state", () => {
5
- const tracker = createEfficiencyTracker();
6
- const snap = tracker.getSnapshot();
7
- expect(snap.totalCalls).toBe(0);
8
- expect(snap.originalTokens).toBe(0);
9
- expect(snap.deliveredTokens).toBe(0);
10
- expect(snap.efficiency).toBe(0);
11
- });
12
- it("records token pairs and computes efficiency", () => {
13
- const tracker = createEfficiencyTracker();
14
- tracker.record(1000, 600);
15
- expect(tracker.getEfficiency()).toBe(40);
16
- expect(tracker.getSavedTokens()).toBe(400);
17
- });
18
- it("accumulates across multiple calls", () => {
19
- const tracker = createEfficiencyTracker();
20
- tracker.record(1000, 700);
21
- tracker.record(2000, 800);
22
- tracker.record(500, 500);
23
- const snap = tracker.getSnapshot();
24
- expect(snap.totalCalls).toBe(3);
25
- expect(snap.originalTokens).toBe(3500);
26
- expect(snap.deliveredTokens).toBe(2000);
27
- expect(snap.savedTokens).toBe(1500);
28
- expect(snap.efficiency).toBe(43);
29
- });
30
- it("computes average savings per call", () => {
31
- const tracker = createEfficiencyTracker();
32
- tracker.record(1000, 500);
33
- tracker.record(2000, 1000);
34
- const snap = tracker.getSnapshot();
35
- expect(snap.avgSavingsPerCall).toBe(750);
36
- });
37
- it("handles 10 calls with known values", () => {
38
- const tracker = createEfficiencyTracker();
39
- for (let i = 0; i < 10; i++) {
40
- tracker.record(1000, 300);
41
- }
42
- expect(tracker.getEfficiency()).toBe(70);
43
- expect(tracker.getSavedTokens()).toBe(7000);
44
- expect(tracker.getSnapshot().totalCalls).toBe(10);
45
- });
46
- it("handles zero original tokens gracefully", () => {
47
- const tracker = createEfficiencyTracker();
48
- tracker.record(0, 0);
49
- expect(tracker.getEfficiency()).toBe(0);
50
- });
51
- it("never returns negative savings", () => {
52
- const tracker = createEfficiencyTracker();
53
- tracker.record(100, 200);
54
- expect(tracker.getSavedTokens()).toBe(0);
55
- });
56
- it("reset clears all state", () => {
57
- const tracker = createEfficiencyTracker();
58
- tracker.record(5000, 2000);
59
- tracker.reset();
60
- const snap = tracker.getSnapshot();
61
- expect(snap.totalCalls).toBe(0);
62
- expect(snap.originalTokens).toBe(0);
63
- expect(snap.efficiency).toBe(0);
64
- });
65
- });
@@ -1,144 +0,0 @@
1
- /**
2
- * Tests for Sprint L8.2 — `unerr enrich` enrichment logic.
3
- *
4
- * Tests the prompt builder and response parser without requiring
5
- * a real LLM or CozoDB instance.
6
- */
7
- import { describe, expect, it } from "vitest";
8
- // Re-export internal functions for testing by importing the module
9
- // and testing the prompt/parse logic through the public API patterns.
10
- // Since buildEnrichmentPrompt and parseEnrichmentResponse are module-private,
11
- // we test them indirectly through their behavior. We can also test them
12
- // by extracting them — but for now, test the patterns they implement.
13
- describe("Enrichment prompt builder (L8.2)", () => {
14
- it("builds a structured prompt for entity batch", () => {
15
- // Verify the prompt format expected by parseEnrichmentResponse
16
- const entities = [
17
- {
18
- key: "fn:auth:login",
19
- kind: "function",
20
- name: "login",
21
- filePath: "src/auth.ts",
22
- signature: "login(user: User): Promise<Token>",
23
- },
24
- {
25
- key: "cls:auth:AuthService",
26
- kind: "class",
27
- name: "AuthService",
28
- filePath: "src/auth.ts",
29
- signature: "class AuthService",
30
- },
31
- ];
32
- // The prompt should contain entity info
33
- const entityBlock = entities
34
- .map((e, i) => `[${i + 1}] ${e.kind} "${e.name}" in ${e.filePath}\n Signature: ${e.signature || "(none)"}`)
35
- .join("\n");
36
- expect(entityBlock).toContain('[1] function "login"');
37
- expect(entityBlock).toContain('[2] class "AuthService"');
38
- expect(entityBlock).toContain("src/auth.ts");
39
- });
40
- });
41
- describe("Enrichment response parser (L8.2)", () => {
42
- // Mirror the parseEnrichmentResponse logic
43
- function parseEnrichmentResponse(text, count) {
44
- const jsonMatch = text.match(/\[[\s\S]*\]/);
45
- if (!jsonMatch) {
46
- return Array.from({ length: count }, () => ({
47
- purpose: "",
48
- taxonomy: "unknown",
49
- feature_area: "unknown",
50
- }));
51
- }
52
- try {
53
- const parsed = JSON.parse(jsonMatch[0]);
54
- const results = [];
55
- for (let i = 0; i < count; i++) {
56
- const entry = parsed.find((p) => p.index === i + 1) ?? parsed[i] ?? {};
57
- results.push({
58
- purpose: entry.purpose ?? "",
59
- taxonomy: entry.taxonomy ?? "unknown",
60
- feature_area: entry.feature_area ?? "unknown",
61
- });
62
- }
63
- return results;
64
- }
65
- catch {
66
- return Array.from({ length: count }, () => ({
67
- purpose: "",
68
- taxonomy: "unknown",
69
- feature_area: "unknown",
70
- }));
71
- }
72
- }
73
- it("parses well-formed JSON array response", () => {
74
- const text = JSON.stringify([
75
- {
76
- index: 1,
77
- purpose: "Handles user login",
78
- taxonomy: "authentication",
79
- feature_area: "user-auth",
80
- },
81
- {
82
- index: 2,
83
- purpose: "Manages auth state",
84
- taxonomy: "authentication",
85
- feature_area: "user-auth",
86
- },
87
- ]);
88
- const results = parseEnrichmentResponse(text, 2);
89
- expect(results).toHaveLength(2);
90
- expect(results[0]?.purpose).toBe("Handles user login");
91
- expect(results[0]?.taxonomy).toBe("authentication");
92
- expect(results[1]?.feature_area).toBe("user-auth");
93
- });
94
- it("handles markdown-wrapped JSON", () => {
95
- const text = '```json\n[{"index": 1, "purpose": "Test function", "taxonomy": "testing", "feature_area": "unit-tests"}]\n```';
96
- const results = parseEnrichmentResponse(text, 1);
97
- expect(results).toHaveLength(1);
98
- expect(results[0]?.purpose).toBe("Test function");
99
- expect(results[0]?.taxonomy).toBe("testing");
100
- });
101
- it("returns defaults for unparseable response", () => {
102
- const results = parseEnrichmentResponse("Sorry, I cannot help with that.", 3);
103
- expect(results).toHaveLength(3);
104
- expect(results[0]?.purpose).toBe("");
105
- expect(results[0]?.taxonomy).toBe("unknown");
106
- expect(results[0]?.feature_area).toBe("unknown");
107
- });
108
- it("handles partial responses with missing fields", () => {
109
- const text = JSON.stringify([
110
- { index: 1, purpose: "Does something" },
111
- { index: 2, taxonomy: "data-access" },
112
- ]);
113
- const results = parseEnrichmentResponse(text, 2);
114
- expect(results[0]?.purpose).toBe("Does something");
115
- expect(results[0]?.taxonomy).toBe("unknown"); // missing → default
116
- expect(results[1]?.purpose).toBe(""); // missing → default
117
- expect(results[1]?.taxonomy).toBe("data-access");
118
- });
119
- it("handles fewer results than requested", () => {
120
- const text = JSON.stringify([
121
- {
122
- index: 1,
123
- purpose: "Only one result",
124
- taxonomy: "misc",
125
- feature_area: "core",
126
- },
127
- ]);
128
- const results = parseEnrichmentResponse(text, 3);
129
- expect(results).toHaveLength(3);
130
- expect(results[0]?.purpose).toBe("Only one result");
131
- expect(results[1]?.purpose).toBe(""); // fallback
132
- expect(results[2]?.purpose).toBe(""); // fallback
133
- });
134
- it("handles index-based matching", () => {
135
- // Response with out-of-order indices
136
- const text = JSON.stringify([
137
- { index: 2, purpose: "Second", taxonomy: "b", feature_area: "b" },
138
- { index: 1, purpose: "First", taxonomy: "a", feature_area: "a" },
139
- ]);
140
- const results = parseEnrichmentResponse(text, 2);
141
- expect(results[0]?.purpose).toBe("First");
142
- expect(results[1]?.purpose).toBe("Second");
143
- });
144
- });
@@ -1,248 +0,0 @@
1
- /**
2
- * Sprint 7.4: Entity-level rewind tests.
3
- */
4
- import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import { describe, expect, it } from "vitest";
8
- import { revertEntity } from "../tracking/entity-rewind.js";
9
- /** Minimal mock CozoGraphStore with drift overlay backed by a Map. */
10
- function createMockGraph(driftEntities = []) {
11
- const overlay = new Map();
12
- for (const e of driftEntities) {
13
- overlay.set(e.key, e);
14
- }
15
- const removed = [];
16
- return {
17
- removed,
18
- db: {
19
- run: async (query, params) => {
20
- // Simulate drift_overlay lookup by name (+ optional file_path filter)
21
- const name = params?.name;
22
- const fp = params?.fp;
23
- const matches = [];
24
- for (const [, e] of overlay) {
25
- if (name && e.name !== name)
26
- continue;
27
- if (fp && e.file_path !== fp)
28
- continue;
29
- matches.push(e);
30
- }
31
- return {
32
- rows: matches.map((e) => [
33
- e.key,
34
- e.name,
35
- e.kind,
36
- e.signature,
37
- e.body,
38
- e.file_path,
39
- e.line_start,
40
- e.line_end,
41
- e.content_hash,
42
- e.drift_status,
43
- e.intent_id,
44
- e.modified_at,
45
- e.origin,
46
- e.previous_body,
47
- e.previous_signature,
48
- ]),
49
- };
50
- },
51
- },
52
- removeDriftEntity: (key) => {
53
- overlay.delete(key);
54
- removed.push(key);
55
- },
56
- };
57
- }
58
- function makeDrift(overrides) {
59
- return {
60
- kind: "function",
61
- signature: "()",
62
- body: "",
63
- file_path: "src/test.ts",
64
- line_start: 1,
65
- line_end: 3,
66
- content_hash: "abc",
67
- drift_status: "modified",
68
- intent_id: "",
69
- modified_at: new Date().toISOString(),
70
- origin: "human",
71
- previous_body: "",
72
- previous_signature: "",
73
- ...overrides,
74
- };
75
- }
76
- describe("Entity-level rewind", () => {
77
- it("returns error when entity not found in drift overlay", async () => {
78
- const graph = createMockGraph();
79
- const result = await revertEntity("nonExistent", graph, "/tmp");
80
- expect(result.reverted).toBe(false);
81
- expect(result.error).toContain("No drifted entity found");
82
- });
83
- it("reverts a MODIFIED entity by restoring previous_body", async () => {
84
- const projectRoot = mkdtempSync(join(tmpdir(), "rewind-"));
85
- const srcDir = join(projectRoot, "src");
86
- mkdirSync(srcDir, { recursive: true });
87
- // Write the current (modified) file
88
- const fileContent = [
89
- "// header",
90
- "function doStuff() {",
91
- " return 'modified';",
92
- "}",
93
- "// footer",
94
- ].join("\n");
95
- writeFileSync(join(srcDir, "test.ts"), fileContent);
96
- const graph = createMockGraph([
97
- makeDrift({
98
- key: "k1",
99
- name: "doStuff",
100
- file_path: "src/test.ts",
101
- body: "function doStuff() {\n return 'modified';\n}",
102
- line_start: 2,
103
- line_end: 4,
104
- drift_status: "modified",
105
- previous_body: "function doStuff() {\n return 'original';\n}",
106
- }),
107
- ]);
108
- const result = await revertEntity("doStuff", graph, projectRoot);
109
- expect(result.reverted).toBe(true);
110
- expect(result.file).toBe("src/test.ts");
111
- expect(result.drift_status).toBe("modified");
112
- expect(graph.removed).toContain("k1");
113
- // Verify file content was restored
114
- const restored = readFileSync(join(srcDir, "test.ts"), "utf-8");
115
- expect(restored).toContain("return 'original'");
116
- expect(restored).not.toContain("return 'modified'");
117
- expect(restored).toContain("// header");
118
- expect(restored).toContain("// footer");
119
- });
120
- it("reverts an ADDED entity by removing its lines", async () => {
121
- const projectRoot = mkdtempSync(join(tmpdir(), "rewind-"));
122
- const srcDir = join(projectRoot, "src");
123
- mkdirSync(srcDir, { recursive: true });
124
- const fileContent = [
125
- "// existing code",
126
- "function newFn() {",
127
- " return 42;",
128
- "}",
129
- "// more code",
130
- ].join("\n");
131
- writeFileSync(join(srcDir, "test.ts"), fileContent);
132
- const graph = createMockGraph([
133
- makeDrift({
134
- key: "k2",
135
- name: "newFn",
136
- file_path: "src/test.ts",
137
- body: "function newFn() {\n return 42;\n}",
138
- line_start: 2,
139
- line_end: 4,
140
- drift_status: "added",
141
- previous_body: "",
142
- }),
143
- ]);
144
- const result = await revertEntity("newFn", graph, projectRoot);
145
- expect(result.reverted).toBe(true);
146
- expect(result.drift_status).toBe("added");
147
- expect(graph.removed).toContain("k2");
148
- const restored = readFileSync(join(srcDir, "test.ts"), "utf-8");
149
- expect(restored).not.toContain("newFn");
150
- expect(restored).toContain("// existing code");
151
- expect(restored).toContain("// more code");
152
- });
153
- it("reverts a DELETED entity by inserting previous_body", async () => {
154
- const projectRoot = mkdtempSync(join(tmpdir(), "rewind-"));
155
- const srcDir = join(projectRoot, "src");
156
- mkdirSync(srcDir, { recursive: true });
157
- // File after deletion — the entity is missing
158
- const fileContent = ["// header", "// footer"].join("\n");
159
- writeFileSync(join(srcDir, "test.ts"), fileContent);
160
- const graph = createMockGraph([
161
- makeDrift({
162
- key: "k3",
163
- name: "removedFn",
164
- file_path: "src/test.ts",
165
- body: "",
166
- line_start: 2,
167
- line_end: 2,
168
- drift_status: "deleted",
169
- previous_body: "function removedFn() {\n return 'was here';\n}",
170
- }),
171
- ]);
172
- const result = await revertEntity("removedFn", graph, projectRoot);
173
- expect(result.reverted).toBe(true);
174
- expect(result.drift_status).toBe("deleted");
175
- expect(graph.removed).toContain("k3");
176
- const restored = readFileSync(join(srcDir, "test.ts"), "utf-8");
177
- expect(restored).toContain("removedFn");
178
- expect(restored).toContain("was here");
179
- });
180
- it("reverts dependency_changed by just removing overlay entry", async () => {
181
- const graph = createMockGraph([
182
- makeDrift({
183
- key: "k4",
184
- name: "callerFn",
185
- drift_status: "dependency_changed",
186
- }),
187
- ]);
188
- const result = await revertEntity("callerFn", graph, "/tmp");
189
- expect(result.reverted).toBe(true);
190
- expect(result.drift_status).toBe("dependency_changed");
191
- expect(graph.removed).toContain("k4");
192
- });
193
- it("filters by file_path when provided", async () => {
194
- const graph = createMockGraph([
195
- makeDrift({
196
- key: "k5a",
197
- name: "shared",
198
- file_path: "src/a.ts",
199
- drift_status: "modified",
200
- }),
201
- makeDrift({
202
- key: "k5b",
203
- name: "shared",
204
- file_path: "src/b.ts",
205
- drift_status: "modified",
206
- previous_body: "function shared() { return 'b'; }",
207
- }),
208
- ]);
209
- const projectRoot = mkdtempSync(join(tmpdir(), "rewind-"));
210
- mkdirSync(join(projectRoot, "src"), { recursive: true });
211
- writeFileSync(join(projectRoot, "src", "b.ts"), "function shared() { return 'modified'; }");
212
- const result = await revertEntity("shared", graph, projectRoot, "src/b.ts");
213
- expect(result.reverted).toBe(true);
214
- expect(result.file).toBe("src/b.ts");
215
- expect(graph.removed).toContain("k5b");
216
- expect(graph.removed).not.toContain("k5a");
217
- });
218
- it("returns error when modified entity has no previous_body", async () => {
219
- const projectRoot = mkdtempSync(join(tmpdir(), "rewind-"));
220
- mkdirSync(join(projectRoot, "src"), { recursive: true });
221
- writeFileSync(join(projectRoot, "src", "test.ts"), "some content");
222
- const graph = createMockGraph([
223
- makeDrift({
224
- key: "k6",
225
- name: "noHistory",
226
- file_path: "src/test.ts",
227
- drift_status: "modified",
228
- previous_body: "",
229
- }),
230
- ]);
231
- const result = await revertEntity("noHistory", graph, projectRoot);
232
- expect(result.reverted).toBe(false);
233
- expect(result.error).toContain("No previous body");
234
- });
235
- it("stores previous_body in drift overlay when entity is modified", () => {
236
- // This test verifies the DriftTracker integration —
237
- // modified entities should preserve the base body for rewind
238
- const drift = makeDrift({
239
- key: "k7",
240
- name: "fn",
241
- drift_status: "modified",
242
- previous_body: "function fn() { return 1; }",
243
- previous_signature: "()",
244
- });
245
- expect(drift.previous_body).toBe("function fn() { return 1; }");
246
- expect(drift.previous_signature).toBe("()");
247
- });
248
- });