@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,302 +0,0 @@
1
- /**
2
- * Tests for StartupDisplay + StartupRenderer (Task 1.3).
3
- *
4
- * Tests validate:
5
- * - Three-Act structure: Banner (Act 1), HealthCard (Act 2), Invitation (Act 3)
6
- * - Step status progression: pending → active → done
7
- * - Health Shock display: full card on first boot, compact on subsequent
8
- * - Invitation references specific entity from health data
9
- * - Deep link included when repo ID available
10
- * - Proxy mode displayed when not "full"
11
- */
12
- import * as fs from "node:fs";
13
- import * as os from "node:os";
14
- import * as path from "node:path";
15
- import { render } from "ink-testing-library";
16
- import React from "react";
17
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
18
- import { StartupDisplay, } from "../components/StartupDisplay.js";
19
- import { ThemeProvider } from "../components/Theme.js";
20
- function renderStartup(state) {
21
- return render(React.createElement(ThemeProvider, null, React.createElement(StartupDisplay, { state })));
22
- }
23
- describe("StartupDisplay (1.3)", () => {
24
- // ── Act 1: Instant Competence ──────────────────────────────────
25
- describe("Act 1: Banner + Steps", () => {
26
- it("renders brand banner", () => {
27
- const { lastFrame } = renderStartup({
28
- localMode: false,
29
- steps: [],
30
- firstBoot: false,
31
- ready: false,
32
- });
33
- expect(lastFrame()).toContain("unerr");
34
- expect(lastFrame()).toContain("▸");
35
- });
36
- it("renders steps with correct status icons", () => {
37
- const { lastFrame } = renderStartup({
38
- localMode: false,
39
- steps: [
40
- { label: "Authenticated", value: "Org", status: "done" },
41
- { label: "Repository", value: "acme/repo", status: "done" },
42
- { label: "Graph loaded", status: "active" },
43
- ],
44
- firstBoot: false,
45
- ready: false,
46
- });
47
- const frame = lastFrame() ?? "";
48
- expect(frame).toContain("✓");
49
- expect(frame).toContain("Authenticated");
50
- expect(frame).toContain("Org");
51
- expect(frame).toContain("●");
52
- expect(frame).toContain("Graph loaded");
53
- });
54
- it("shows step values alongside labels", () => {
55
- const { lastFrame } = renderStartup({
56
- localMode: false,
57
- steps: [
58
- {
59
- label: "Graph loaded",
60
- value: "2,341 entities · 1,892 edges",
61
- status: "done",
62
- },
63
- ],
64
- firstBoot: false,
65
- ready: false,
66
- });
67
- expect(lastFrame()).toContain("2,341 entities");
68
- expect(lastFrame()).toContain("1,892 edges");
69
- });
70
- });
71
- // ── Act 2: Revelation (Health Shock) ───────────────────────────
72
- describe("Act 2: Health Shock", () => {
73
- const health = {
74
- grade: "C+",
75
- totalEntities: 2341,
76
- totalEdges: 1892,
77
- totalRules: 12,
78
- deadFunctionCount: 23,
79
- highRiskEntities: [
80
- {
81
- name: "processPayment",
82
- kind: "function",
83
- file_path: "src/billing.ts",
84
- fan_in: 14,
85
- fan_out: 8,
86
- },
87
- ],
88
- score: 62,
89
- };
90
- it("renders full health card on first boot", () => {
91
- const { lastFrame } = renderStartup({
92
- localMode: false,
93
- steps: [],
94
- health,
95
- firstBoot: true,
96
- ready: false,
97
- });
98
- const frame = lastFrame() ?? "";
99
- expect(frame).toContain("First Look");
100
- expect(frame).toContain("C+");
101
- expect(frame).toContain("62/100");
102
- expect(frame).toContain("23 dead functions");
103
- expect(frame).toContain("processPayment");
104
- });
105
- it("renders compact health line on subsequent boots", () => {
106
- const { lastFrame } = renderStartup({
107
- localMode: false,
108
- steps: [],
109
- health,
110
- firstBoot: false,
111
- ready: false,
112
- });
113
- const frame = lastFrame() ?? "";
114
- expect(frame).toContain("C+");
115
- expect(frame).toContain("2341 entities");
116
- // Should NOT show full dead function detail in compact
117
- expect(frame).not.toContain("23 dead functions");
118
- });
119
- it("does not render health section when no health data", () => {
120
- const { lastFrame } = renderStartup({
121
- localMode: false,
122
- steps: [],
123
- firstBoot: false,
124
- ready: false,
125
- });
126
- expect(lastFrame()).not.toContain("First Look");
127
- });
128
- });
129
- // ── Act 3: Invitation ──────────────────────────────────────────
130
- describe("Act 3: Invitation", () => {
131
- it("renders invitation with specific entity name", () => {
132
- const { lastFrame } = renderStartup({
133
- localMode: false,
134
- steps: [],
135
- firstBoot: false,
136
- ready: true,
137
- invitationEntity: "processPayment",
138
- });
139
- const frame = lastFrame() ?? "";
140
- expect(frame).toContain("What depends on processPayment?");
141
- expect(frame).toContain("blast radius");
142
- });
143
- it("renders proxy ready message", () => {
144
- const { lastFrame } = renderStartup({
145
- localMode: false,
146
- steps: [],
147
- firstBoot: false,
148
- ready: true,
149
- });
150
- expect(lastFrame()).toContain("Proxy ready");
151
- expect(lastFrame()).toContain("MCP on stdio");
152
- });
153
- it("shows proxy mode when not full", () => {
154
- const { lastFrame } = renderStartup({
155
- localMode: false,
156
- steps: [],
157
- firstBoot: false,
158
- ready: true,
159
- proxyMode: "parse",
160
- });
161
- expect(lastFrame()).toContain("parse mode");
162
- });
163
- it("shows local mode label when proxyMode is local", () => {
164
- const { lastFrame } = renderStartup({
165
- localMode: false,
166
- steps: [],
167
- firstBoot: false,
168
- ready: true,
169
- proxyMode: "local",
170
- });
171
- expect(lastFrame()).toContain("local mode");
172
- });
173
- it("renders deep link when available", () => {
174
- const { lastFrame } = renderStartup({
175
- localMode: false,
176
- steps: [],
177
- firstBoot: false,
178
- ready: true,
179
- deepLink: "https://app.unerr.dev/r/repo_123?utm_source=cli_startup",
180
- });
181
- expect(lastFrame()).toContain("https://app.unerr.dev/r/repo_123");
182
- });
183
- it("does not render invitation when not ready", () => {
184
- const { lastFrame } = renderStartup({
185
- localMode: false,
186
- steps: [],
187
- firstBoot: false,
188
- ready: false,
189
- invitationEntity: "processPayment",
190
- });
191
- expect(lastFrame()).not.toContain("What depends on");
192
- });
193
- });
194
- // ── Full Three-Act integration ─────────────────────────────────
195
- describe("Full Three-Act integration", () => {
196
- it("renders all three acts together", () => {
197
- const { lastFrame } = renderStartup({
198
- localMode: false,
199
- steps: [
200
- { label: "Authenticated", value: "Jaswanth's Org", status: "done" },
201
- { label: "Repository", value: "unerr-server (main)", status: "done" },
202
- {
203
- label: "Graph loaded",
204
- value: "2,341 entities · 1,892 edges",
205
- status: "done",
206
- },
207
- {
208
- label: "MCP ready",
209
- value: "15 tools (all local)",
210
- status: "done",
211
- },
212
- ],
213
- health: {
214
- grade: "C+",
215
- totalEntities: 2341,
216
- totalEdges: 1892,
217
- totalRules: 12,
218
- deadFunctionCount: 23,
219
- highRiskEntities: [
220
- {
221
- name: "processPayment",
222
- kind: "function",
223
- file_path: "src/billing.ts",
224
- fan_in: 14,
225
- fan_out: 8,
226
- },
227
- ],
228
- score: 62,
229
- },
230
- firstBoot: true,
231
- ready: true,
232
- invitationEntity: "processPayment",
233
- deepLink: "https://app.unerr.dev/r/repo_123?utm_source=cli_startup",
234
- });
235
- const frame = lastFrame() ?? "";
236
- // Act 1
237
- expect(frame).toContain("unerr");
238
- expect(frame).toContain("Authenticated");
239
- expect(frame).toContain("Jaswanth's Org");
240
- // Act 2
241
- expect(frame).toContain("First Look");
242
- expect(frame).toContain("C+");
243
- expect(frame).toContain("23 dead functions");
244
- // Act 3
245
- expect(frame).toContain("What depends on processPayment?");
246
- expect(frame).toContain("Proxy ready");
247
- expect(frame).toContain("https://app.unerr.dev/r/repo_123");
248
- });
249
- });
250
- });
251
- // ── StartupRenderer unit tests ───────────────────────────────────
252
- describe("StartupRenderer", () => {
253
- let tmpDir;
254
- let origCwd;
255
- beforeEach(() => {
256
- tmpDir = path.join(os.tmpdir(), `unerr-startup-test-${Date.now()}`);
257
- fs.mkdirSync(path.join(tmpDir, ".unerr", "state"), { recursive: true });
258
- origCwd = process.cwd();
259
- process.chdir(tmpDir);
260
- });
261
- afterEach(() => {
262
- process.chdir(origCwd);
263
- fs.rmSync(tmpDir, { recursive: true, force: true });
264
- });
265
- it("tracks first_boot_shown flag", async () => {
266
- const { StartupRenderer } = await import("../proxy/startup-renderer.js");
267
- const renderer = new StartupRenderer();
268
- // No graph_version.json → first boot
269
- renderer.setHealth({
270
- grade: "C+",
271
- totalEntities: 100,
272
- totalEdges: 50,
273
- totalRules: 5,
274
- deadFunctionCount: 10,
275
- highRiskEntities: [],
276
- score: 62,
277
- }, "repo_test");
278
- // Should have written first_boot_shown = true
279
- const versionPath = path.join(tmpDir, ".unerr", "state", "graph_version.json");
280
- const data = JSON.parse(fs.readFileSync(versionPath, "utf-8"));
281
- expect(data.first_boot_shown).toBe(true);
282
- });
283
- it("detects subsequent boot after first_boot_shown is set", async () => {
284
- const versionPath = path.join(tmpDir, ".unerr", "state", "graph_version.json");
285
- fs.writeFileSync(versionPath, JSON.stringify({ first_boot_shown: true }));
286
- const { StartupRenderer } = await import("../proxy/startup-renderer.js");
287
- const renderer = new StartupRenderer();
288
- renderer.setHealth({
289
- grade: "A",
290
- totalEntities: 100,
291
- totalEdges: 50,
292
- totalRules: 5,
293
- deadFunctionCount: 0,
294
- highRiskEntities: [],
295
- score: 95,
296
- }, "repo_test");
297
- // firstBoot should be false since flag was already set
298
- // We verify indirectly — the renderer won't overwrite the flag
299
- const data = JSON.parse(fs.readFileSync(versionPath, "utf-8"));
300
- expect(data.first_boot_shown).toBe(true);
301
- });
302
- });
@@ -1,97 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
- import os from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
- import { initFileLog, startupLog } from "../utils/startup-log.js";
6
- describe("startup-log file logging", () => {
7
- let tmpDir;
8
- let logPath;
9
- beforeEach(() => {
10
- tmpDir = join(os.tmpdir(), `unerr-log-test-${Date.now()}`);
11
- mkdirSync(tmpDir, { recursive: true });
12
- initFileLog(tmpDir);
13
- logPath = join(tmpDir, ".unerr", "logs", "unerr.jsonl");
14
- });
15
- afterEach(() => {
16
- rmSync(tmpDir, { recursive: true, force: true });
17
- });
18
- it("creates .unerr/logs/unerr.jsonl on initFileLog", () => {
19
- // initFileLog creates the directory; file created on first write
20
- startupLog.step("test step");
21
- expect(existsSync(logPath)).toBe(true);
22
- });
23
- it("step() writes a JSONL entry with level=step", () => {
24
- startupLog.step("indexing files");
25
- const lines = readFileSync(logPath, "utf-8").trim().split("\n");
26
- const entry = JSON.parse(lines[lines.length - 1]);
27
- expect(entry.level).toBe("step");
28
- expect(entry.msg).toContain("indexing files");
29
- expect(entry.ts).toBeDefined();
30
- expect(entry.pid).toBe(process.pid);
31
- });
32
- it("done() includes ms metadata when provided", () => {
33
- startupLog.done("graph loaded", 42);
34
- const lines = readFileSync(logPath, "utf-8").trim().split("\n");
35
- const entry = JSON.parse(lines[lines.length - 1]);
36
- expect(entry.level).toBe("done");
37
- expect(entry.ms).toBe(42);
38
- });
39
- it("metric() includes raw value and unit", () => {
40
- startupLog.metric("entities", 1234, "nodes");
41
- const lines = readFileSync(logPath, "utf-8").trim().split("\n");
42
- const entry = JSON.parse(lines[lines.length - 1]);
43
- expect(entry.level).toBe("metric");
44
- expect(entry.value).toBe(1234);
45
- expect(entry.unit).toBe("nodes");
46
- });
47
- it("warn() and error() write correct levels", () => {
48
- startupLog.warn("something odd");
49
- startupLog.error("something broke");
50
- const lines = readFileSync(logPath, "utf-8").trim().split("\n");
51
- const entries = lines.map((l) => JSON.parse(l));
52
- const warn = entries.find((e) => e.level === "warn");
53
- const err = entries.find((e) => e.level === "error");
54
- expect(warn).toBeDefined();
55
- expect(warn.msg).toContain("something odd");
56
- expect(err).toBeDefined();
57
- expect(err.msg).toContain("something broke");
58
- });
59
- it("graphLoaded() writes full stats object", () => {
60
- startupLog.graphLoaded({
61
- entities: 500,
62
- edges: 1200,
63
- files: 80,
64
- communities: 5,
65
- patterns: 12,
66
- rules: 3,
67
- ms: 150,
68
- hottestFile: "src/main.ts",
69
- hottestCount: 25,
70
- });
71
- const lines = readFileSync(logPath, "utf-8").trim().split("\n");
72
- const entry = JSON.parse(lines[lines.length - 1]);
73
- expect(entry.level).toBe("graph_loaded");
74
- expect(entry.entities).toBe(500);
75
- expect(entry.edges).toBe(1200);
76
- expect(entry.files).toBe(80);
77
- expect(entry.communities).toBe(5);
78
- expect(entry.patterns).toBe(12);
79
- expect(entry.rules).toBe(3);
80
- expect(entry.ms).toBe(150);
81
- expect(entry.hottestFile).toBe("src/main.ts");
82
- expect(entry.hottestCount).toBe(25);
83
- });
84
- it("file entries have no ANSI escape codes", () => {
85
- startupLog.step("test with colors");
86
- const content = readFileSync(logPath, "utf-8");
87
- expect(content).not.toContain("\x1b[");
88
- });
89
- it("ready() includes toolCount and mode", () => {
90
- startupLog.ready(17, "local");
91
- const lines = readFileSync(logPath, "utf-8").trim().split("\n");
92
- const entry = JSON.parse(lines[lines.length - 1]);
93
- expect(entry.level).toBe("ready");
94
- expect(entry.toolCount).toBe(17);
95
- expect(entry.mode).toBe("local");
96
- });
97
- });
@@ -1,229 +0,0 @@
1
- /**
2
- * Sprint 7.1: Git stash awareness tests.
3
- */
4
- import { mkdirSync, mkdtempSync, 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 { StashManager } from "../tracking/stash-manager.js";
9
- /** Minimal mock CozoGraphStore backed by a Map for drift overlay. */
10
- function createMockGraph(driftEntities = []) {
11
- const entities = new Map();
12
- for (const e of driftEntities) {
13
- entities.set(e.key, e);
14
- }
15
- return {
16
- entities,
17
- getAllDriftEntities: () => [...entities.values()],
18
- getAllDriftEdges: () => [],
19
- upsertDriftEntity: (entity) => {
20
- entities.set(entity.key, entity);
21
- },
22
- upsertDriftEdge: () => { },
23
- removeDriftEntity: (key) => {
24
- entities.delete(key);
25
- },
26
- clearDriftOverlay: () => {
27
- entities.clear();
28
- },
29
- };
30
- }
31
- function makeDrift(overrides) {
32
- return {
33
- kind: "function",
34
- signature: "()",
35
- body: "function test() {}",
36
- file_path: "src/test.ts",
37
- line_start: 1,
38
- line_end: 3,
39
- content_hash: "abc123",
40
- drift_status: "modified",
41
- intent_id: "",
42
- modified_at: new Date().toISOString(),
43
- origin: "human",
44
- previous_body: "",
45
- previous_signature: "",
46
- ...overrides,
47
- };
48
- }
49
- function setupProjectWithGit() {
50
- const projectRoot = mkdtempSync(join(tmpdir(), "stash-"));
51
- const unerrDir = join(projectRoot, ".unerr");
52
- const gitDir = join(projectRoot, ".git");
53
- mkdirSync(unerrDir, { recursive: true });
54
- mkdirSync(join(gitDir, "refs"), { recursive: true });
55
- mkdirSync(join(gitDir, "logs", "refs"), { recursive: true });
56
- return { projectRoot, unerrDir, gitDir };
57
- }
58
- function writeStashRef(gitDir, ref) {
59
- writeFileSync(join(gitDir, "refs", "stash"), `${ref}\n`, "utf-8");
60
- }
61
- function writeStashLog(gitDir, count) {
62
- const lines = Array.from({ length: count }, (_, i) => `0000000 abcdef${i} Author <a@b.com> ${Date.now()} +0000\tstash@{${i}}: WIP`);
63
- writeFileSync(join(gitDir, "logs", "refs", "stash"), `${lines.join("\n")}\n`, "utf-8");
64
- }
65
- const MOCK_FILE_HASHES = {
66
- files: {
67
- "src/test.ts": {
68
- contentSha: "sha256abc",
69
- headSha: "head123",
70
- processedAt: new Date().toISOString(),
71
- },
72
- },
73
- };
74
- describe("StashManager", () => {
75
- it("detects no change when stash ref unchanged", () => {
76
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
77
- writeStashRef(gitDir, "abc123def456");
78
- writeStashLog(gitDir, 1);
79
- const manager = new StashManager(unerrDir, projectRoot);
80
- expect(manager.detectStashChange()).toBeNull();
81
- });
82
- it("detects stash push when count increases", () => {
83
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
84
- writeStashRef(gitDir, "ref1");
85
- writeStashLog(gitDir, 1);
86
- const manager = new StashManager(unerrDir, projectRoot);
87
- // Simulate stash push
88
- writeStashRef(gitDir, "ref2");
89
- writeStashLog(gitDir, 2);
90
- expect(manager.detectStashChange()).toBe("push");
91
- });
92
- it("detects stash pop when count decreases", () => {
93
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
94
- writeStashRef(gitDir, "ref1");
95
- writeStashLog(gitDir, 2);
96
- const manager = new StashManager(unerrDir, projectRoot);
97
- // Simulate stash pop
98
- writeStashRef(gitDir, "ref0");
99
- writeStashLog(gitDir, 1);
100
- expect(manager.detectStashChange()).toBe("pop");
101
- });
102
- it("saves and restores a stash snapshot", async () => {
103
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
104
- writeStashRef(gitDir, "abc123def456abc123def456abc123def456abc1");
105
- writeStashLog(gitDir, 1);
106
- const entities = [
107
- makeDrift({ key: "k1", name: "fn1", drift_status: "modified" }),
108
- makeDrift({ key: "k2", name: "fn2", drift_status: "added" }),
109
- ];
110
- const graph = createMockGraph(entities);
111
- const manager = new StashManager(unerrDir, projectRoot);
112
- // Save snapshot
113
- const snapshotId = await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
114
- expect(snapshotId).toBe("abc123def456");
115
- // Clear overlay to simulate stash applying
116
- graph.entities.clear();
117
- expect(graph.getAllDriftEntities()).toHaveLength(0);
118
- // Restore snapshot
119
- const restored = await manager.restoreSnapshot(graph);
120
- expect(restored).toBe(2);
121
- expect(graph.entities.size).toBe(2);
122
- expect(graph.entities.get("k1")?.name).toBe("fn1");
123
- expect(graph.entities.get("k2")?.name).toBe("fn2");
124
- });
125
- it("returns null when saving with no drift entities", async () => {
126
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
127
- writeStashRef(gitDir, "abc123def456");
128
- const graph = createMockGraph([]);
129
- const manager = new StashManager(unerrDir, projectRoot);
130
- const snapshotId = await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
131
- expect(snapshotId).toBeNull();
132
- });
133
- it("returns null when saving with no stash ref", async () => {
134
- const { projectRoot, unerrDir } = setupProjectWithGit();
135
- const graph = createMockGraph([makeDrift({ key: "k1", name: "fn1" })]);
136
- const manager = new StashManager(unerrDir, projectRoot);
137
- const snapshotId = await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
138
- expect(snapshotId).toBeNull();
139
- });
140
- it("restores 0 when no snapshots exist", async () => {
141
- const { projectRoot, unerrDir } = setupProjectWithGit();
142
- const graph = createMockGraph([]);
143
- const manager = new StashManager(unerrDir, projectRoot);
144
- expect(await manager.restoreSnapshot(graph)).toBe(0);
145
- });
146
- it("drops a specific snapshot", async () => {
147
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
148
- const fullRef = "abc123def456abc123def456abc123def456abc1";
149
- writeStashRef(gitDir, fullRef);
150
- const graph = createMockGraph([makeDrift({ key: "k1", name: "fn1" })]);
151
- const manager = new StashManager(unerrDir, projectRoot);
152
- await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
153
- expect(manager.listSnapshots()).toHaveLength(1);
154
- expect(manager.dropSnapshot(fullRef)).toBe(true);
155
- expect(manager.listSnapshots()).toHaveLength(0);
156
- });
157
- it("enforces LRU cap of 10 snapshots", async () => {
158
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
159
- const graph = createMockGraph([makeDrift({ key: "k1", name: "fn1" })]);
160
- const manager = new StashManager(unerrDir, projectRoot);
161
- // Create 12 snapshots
162
- for (let i = 0; i < 12; i++) {
163
- const ref = `ref${String(i).padStart(12, "0")}aaaaaaaaaaaaaaaa`;
164
- writeStashRef(gitDir, ref);
165
- await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
166
- }
167
- // Should be capped at 10
168
- expect(manager.listSnapshots().length).toBeLessThanOrEqual(10);
169
- });
170
- it("retrieves file hash state from snapshot", async () => {
171
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
172
- writeStashRef(gitDir, "abc123def456abc1");
173
- const graph = createMockGraph([makeDrift({ key: "k1", name: "fn1" })]);
174
- const manager = new StashManager(unerrDir, projectRoot);
175
- await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
176
- const hashes = manager.getSnapshotFileHashes();
177
- expect(hashes).not.toBeNull();
178
- expect(hashes?.files["src/test.ts"]?.contentSha).toBe("sha256abc");
179
- });
180
- it("cleans up snapshot after restore", async () => {
181
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
182
- writeStashRef(gitDir, "abc123def456abc1");
183
- const graph = createMockGraph([makeDrift({ key: "k1", name: "fn1" })]);
184
- const manager = new StashManager(unerrDir, projectRoot);
185
- await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
186
- expect(manager.listSnapshots()).toHaveLength(1);
187
- graph.entities.clear();
188
- await manager.restoreSnapshot(graph);
189
- // Snapshot should be cleaned up after restore
190
- expect(manager.listSnapshots()).toHaveLength(0);
191
- });
192
- it("handles stash push → pop cycle preserving overlay", async () => {
193
- const { projectRoot, unerrDir, gitDir } = setupProjectWithGit();
194
- const originalEntities = [
195
- makeDrift({
196
- key: "k1",
197
- name: "handler",
198
- drift_status: "modified",
199
- body: "function handler() { return 42; }",
200
- previous_body: "function handler() { return 0; }",
201
- }),
202
- ];
203
- const graph = createMockGraph(originalEntities);
204
- // Initial state: 1 stash
205
- writeStashRef(gitDir, "stashref1aaa");
206
- writeStashLog(gitDir, 0);
207
- const manager = new StashManager(unerrDir, projectRoot);
208
- // Simulate stash push
209
- writeStashRef(gitDir, "stashref2bbb");
210
- writeStashLog(gitDir, 1);
211
- expect(manager.detectStashChange()).toBe("push");
212
- await manager.saveSnapshot(graph, MOCK_FILE_HASHES);
213
- // Clear overlay (simulating git stash restoring working tree)
214
- graph.entities.clear();
215
- expect(graph.getAllDriftEntities()).toHaveLength(0);
216
- // Simulate stash pop
217
- writeStashRef(gitDir, "stashref1aaa");
218
- writeStashLog(gitDir, 0);
219
- expect(manager.detectStashChange()).toBe("pop");
220
- const restored = await manager.restoreSnapshot(graph);
221
- expect(restored).toBe(1);
222
- const entity = graph.entities.get("k1");
223
- expect(entity).toBeDefined();
224
- expect(entity?.name).toBe("handler");
225
- expect(entity?.drift_status).toBe("modified");
226
- expect(entity?.body).toBe("function handler() { return 42; }");
227
- expect(entity?.previous_body).toBe("function handler() { return 0; }");
228
- });
229
- });