@unerr-ai/unerr 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -196
- package/dist/cli.js +19821 -16589
- package/package.json +17 -3
- package/dist/__tests__/architecture-guard.test.js +0 -122
- package/dist/__tests__/arg-validator.test.js +0 -205
- package/dist/__tests__/ast-extractor.test.js +0 -203
- package/dist/__tests__/auto-bootstrap.test.js +0 -280
- package/dist/__tests__/background-indexer.test.js +0 -228
- package/dist/__tests__/blast-radius-engine.test.js +0 -200
- package/dist/__tests__/bridge-isolation.test.js +0 -37
- package/dist/__tests__/budget-enforcer.test.js +0 -53
- package/dist/__tests__/cfg-test-detection-perf.test.js +0 -82
- package/dist/__tests__/change-narrative.test.js +0 -190
- package/dist/__tests__/check-commit.test.js +0 -258
- package/dist/__tests__/checksum.test.js +0 -34
- package/dist/__tests__/commit-watcher.test.js +0 -154
- package/dist/__tests__/community-detection.test.js +0 -179
- package/dist/__tests__/community-tools.test.js +0 -299
- package/dist/__tests__/components.test.js +0 -449
- package/dist/__tests__/compression-log.test.js +0 -174
- package/dist/__tests__/compression-quality-monitor.test.js +0 -40
- package/dist/__tests__/config-healer.test.js +0 -165
- package/dist/__tests__/context-ledger.test.js +0 -58
- package/dist/__tests__/convention-detector.test.js +0 -99
- package/dist/__tests__/convention-learner.test.js +0 -86
- package/dist/__tests__/correction-detector.test.js +0 -330
- package/dist/__tests__/daemon-autostart-install.test.js +0 -283
- package/dist/__tests__/daemon-bridge.test.js +0 -222
- package/dist/__tests__/daemon-dashboard.test.js +0 -202
- package/dist/__tests__/daemon-registry.test.js +0 -240
- package/dist/__tests__/daemon-supervisor.test.js +0 -318
- package/dist/__tests__/daemon-version-check.test.js +0 -275
- package/dist/__tests__/decision-point-detector.test.js +0 -98
- package/dist/__tests__/deep-link.test.js +0 -143
- package/dist/__tests__/disallowed-tools.test.js +0 -115
- package/dist/__tests__/drift-tracker.test.js +0 -582
- package/dist/__tests__/durability-scorer.test.js +0 -152
- package/dist/__tests__/efficiency-tracker.test.js +0 -65
- package/dist/__tests__/enrich.test.js +0 -144
- package/dist/__tests__/entity-rewind.test.js +0 -248
- package/dist/__tests__/ephemeral.test.js +0 -111
- package/dist/__tests__/exploration-cost.test.js +0 -93
- package/dist/__tests__/fact-generator.test.js +0 -197
- package/dist/__tests__/file-l0-graph.test.js +0 -244
- package/dist/__tests__/file-logger.test.js +0 -82
- package/dist/__tests__/file-outline.test.js +0 -141
- package/dist/__tests__/file-read-protocol.test.js +0 -188
- package/dist/__tests__/format-encoder.test.js +0 -233
- package/dist/__tests__/git-attribution.test.js +0 -259
- package/dist/__tests__/graph-temporal-joiner.test.js +0 -219
- package/dist/__tests__/health-grade-enhanced.test.js +0 -138
- package/dist/__tests__/health-map-data.test.js +0 -173
- package/dist/__tests__/helpers/mcp-harness.js +0 -45
- package/dist/__tests__/helpers/mcp-harness.test.js +0 -68
- package/dist/__tests__/hook-dedup.test.js +0 -112
- package/dist/__tests__/hook-runner.test.js +0 -253
- package/dist/__tests__/indexer-cfg.test.js +0 -185
- package/dist/__tests__/indexer-cross-file.test.js +0 -172
- package/dist/__tests__/indexer-extraction.test.js +0 -245
- package/dist/__tests__/indexer-incremental.test.js +0 -232
- package/dist/__tests__/indexer-language-expansion.test.js +0 -165
- package/dist/__tests__/init-push.test.js +0 -131
- package/dist/__tests__/instruction-writer.test.js +0 -179
- package/dist/__tests__/intelligence-integration.test.js +0 -217
- package/dist/__tests__/intent-correlator.test.js +0 -175
- package/dist/__tests__/intent-detector.test.js +0 -235
- package/dist/__tests__/intent-encoder.test.js +0 -167
- package/dist/__tests__/java-build-tool-detection.test.js +0 -174
- package/dist/__tests__/layer3-sprint-q.test.js +0 -160
- package/dist/__tests__/layer3-sprint-r.test.js +0 -91
- package/dist/__tests__/layer3-sprint-s.test.js +0 -183
- package/dist/__tests__/layer3-sprint-t.test.js +0 -201
- package/dist/__tests__/layer3-sprint-u.test.js +0 -174
- package/dist/__tests__/layer4-sprint-ba2.test.js +0 -354
- package/dist/__tests__/layer4-sprint-ba4.test.js +0 -84
- package/dist/__tests__/layer4-sprint-vs.test.js +0 -105
- package/dist/__tests__/ledger-chains.test.js +0 -162
- package/dist/__tests__/lifecycle-machine.test.js +0 -226
- package/dist/__tests__/local-chat-provider.test.js +0 -170
- package/dist/__tests__/local-convention-detector.test.js +0 -308
- package/dist/__tests__/local-embeddings.test.js +0 -422
- package/dist/__tests__/local-graph.test.js +0 -540
- package/dist/__tests__/local-indexer.test.js +0 -228
- package/dist/__tests__/local-intelligence-l3.test.js +0 -332
- package/dist/__tests__/local-llm.test.js +0 -253
- package/dist/__tests__/local-mode-offline.test.js +0 -187
- package/dist/__tests__/local-mode-stats.test.js +0 -273
- package/dist/__tests__/local-mode-tui.test.js +0 -343
- package/dist/__tests__/local-parse.test.js +0 -199
- package/dist/__tests__/log-tailer.test.js +0 -208
- package/dist/__tests__/loop-breaker.test.js +0 -276
- package/dist/__tests__/loop-miner.test.js +0 -226
- package/dist/__tests__/mcp-config.test.js +0 -126
- package/dist/__tests__/mcp-content-json.test.js +0 -10
- package/dist/__tests__/mcp-envelope.test.js +0 -124
- package/dist/__tests__/metrics-store.test.js +0 -223
- package/dist/__tests__/native-watcher.test.js +0 -191
- package/dist/__tests__/navigation-hooks-agent-aware.test.js +0 -145
- package/dist/__tests__/negative-knowledge.test.js +0 -116
- package/dist/__tests__/network-boundary.test.js +0 -190
- package/dist/__tests__/network-firewall.test.js +0 -112
- package/dist/__tests__/nudge-invariants.test.js +0 -160
- package/dist/__tests__/nudge-v2.test.js +0 -225
- package/dist/__tests__/offline-rewind.test.js +0 -251
- package/dist/__tests__/open-threads.test.js +0 -89
- package/dist/__tests__/output-compressor.test.js +0 -93
- package/dist/__tests__/pending-violations.test.js +0 -112
- package/dist/__tests__/persistence-effectiveness.test.js +0 -143
- package/dist/__tests__/provider-factory.test.js +0 -42
- package/dist/__tests__/providers.test.js +0 -24
- package/dist/__tests__/proxy.test.js +0 -314
- package/dist/__tests__/query-router.test.js +0 -1018
- package/dist/__tests__/reasoning-quality-route.test.js +0 -138
- package/dist/__tests__/redactor.test.js +0 -120
- package/dist/__tests__/resource-monitor.test.js +0 -57
- package/dist/__tests__/response-envelope.test.js +0 -100
- package/dist/__tests__/risk-classifier.test.js +0 -101
- package/dist/__tests__/risk-signal-scope.test.js +0 -75
- package/dist/__tests__/rule-evaluator.test.js +0 -280
- package/dist/__tests__/scip-decoder.test.js +0 -49
- package/dist/__tests__/scip-downloader.test.js +0 -201
- package/dist/__tests__/scip-merger.test.js +0 -103
- package/dist/__tests__/search-index.test.js +0 -422
- package/dist/__tests__/semantic-enrichment.test.js +0 -360
- package/dist/__tests__/session-brief-builder.test.js +0 -187
- package/dist/__tests__/session-context.test.js +0 -221
- package/dist/__tests__/session-continuity.test.js +0 -144
- package/dist/__tests__/session-dedup.test.js +0 -74
- package/dist/__tests__/session-event-wiring.test.js +0 -206
- package/dist/__tests__/session-events.test.js +0 -149
- package/dist/__tests__/session-legend.test.js +0 -20
- package/dist/__tests__/session-persistence.test.js +0 -131
- package/dist/__tests__/session-resume-block.test.js +0 -107
- package/dist/__tests__/session-resume.test.js +0 -97
- package/dist/__tests__/session-summary-writer.test.js +0 -134
- package/dist/__tests__/shadow-ledger.test.js +0 -203
- package/dist/__tests__/shell-classifier.test.js +0 -151
- package/dist/__tests__/shell-compression-floor.test.js +0 -189
- package/dist/__tests__/shell-compression-v2.test.js +0 -339
- package/dist/__tests__/shell-compressor.test.js +0 -35
- package/dist/__tests__/shell-hooks.test.js +0 -128
- package/dist/__tests__/shell-strategies.test.js +0 -644
- package/dist/__tests__/shell-tee.test.js +0 -133
- package/dist/__tests__/signal-dedup.test.js +0 -158
- package/dist/__tests__/signal-reinforcer.test.js +0 -77
- package/dist/__tests__/signal-scorer.test.js +0 -251
- package/dist/__tests__/signal-show-store.test.js +0 -108
- package/dist/__tests__/smart-truncate.test.js +0 -215
- package/dist/__tests__/snapshot-v2.test.js +0 -113
- package/dist/__tests__/sprint-l1-local-mode.test.js +0 -130
- package/dist/__tests__/sprint-l10-boot.test.js +0 -220
- package/dist/__tests__/sprint-l9-offline-commands.test.js +0 -189
- package/dist/__tests__/sprint-q-persistent-context.test.js +0 -198
- package/dist/__tests__/sprint-s1-wiring.test.js +0 -215
- package/dist/__tests__/sprint-s2-wiring.test.js +0 -256
- package/dist/__tests__/sprint-s3-wiring.test.js +0 -195
- package/dist/__tests__/sprint-s4-wiring.test.js +0 -213
- package/dist/__tests__/sprint-s6-hooks.test.js +0 -222
- package/dist/__tests__/sprint-s7-persistent.test.js +0 -263
- package/dist/__tests__/sprint-s8-value.test.js +0 -167
- package/dist/__tests__/sprint-s9-behavioral.test.js +0 -179
- package/dist/__tests__/sprint3-intelligence.test.js +0 -297
- package/dist/__tests__/sprint5-mcp-server.test.js +0 -136
- package/dist/__tests__/startup-display.test.js +0 -302
- package/dist/__tests__/startup-log-file.test.js +0 -97
- package/dist/__tests__/stash-manager.test.js +0 -229
- package/dist/__tests__/state-detector.test.js +0 -92
- package/dist/__tests__/status-dashboard.test.js +0 -142
- package/dist/__tests__/temporal-facts.test.js +0 -292
- package/dist/__tests__/temporal-routes.test.js +0 -142
- package/dist/__tests__/test-detector.test.js +0 -174
- package/dist/__tests__/theme.test.js +0 -72
- package/dist/__tests__/timeline-agents.test.js +0 -122
- package/dist/__tests__/timeline-bootstrap.test.js +0 -176
- package/dist/__tests__/timeline-filters.test.js +0 -193
- package/dist/__tests__/timeline-markers.test.js +0 -151
- package/dist/__tests__/timeline-routes.test.js +0 -156
- package/dist/__tests__/timeline-store.test.js +0 -171
- package/dist/__tests__/token-counter.test.js +0 -86
- package/dist/__tests__/token-estimator.test.js +0 -96
- package/dist/__tests__/token-flow-api.test.js +0 -239
- package/dist/__tests__/token-flow-instrumentation.test.js +0 -437
- package/dist/__tests__/token-flow-persistence.test.js +0 -356
- package/dist/__tests__/token-flow-routes.test.js +0 -199
- package/dist/__tests__/token-flow.test.js +0 -695
- package/dist/__tests__/tool-clusters.test.js +0 -177
- package/dist/__tests__/transport-mux.test.js +0 -283
- package/dist/__tests__/turn-segmenter.test.js +0 -166
- package/dist/__tests__/uninstall.test.js +0 -141
- package/dist/__tests__/warm-start-policy.test.js +0 -271
- package/dist/__tests__/wire-cap-nudge.test.js +0 -77
- package/dist/__tests__/worker-pool.test.js +0 -101
- package/dist/ui/assets/index-7gl3mIuY.css +0 -1
- package/dist/ui/assets/index-CX4FCWGT.js +0 -10
- package/dist/ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
- package/dist/ui/assets/vis-network-NIJHUFI3.js +0 -908
- package/dist/ui/fonts/jetbrains-mono-latin-400-normal.woff +0 -0
- package/dist/ui/icon-wordmark.png +0 -0
- package/dist/ui/icon-wordmark.svg +0 -30
- package/dist/ui/icon.png +0 -0
- package/dist/ui/icon.svg +0 -25
- package/dist/ui/index.html +0 -15
- package/dist/ui/prototype-sandbox/index.html +0 -257
- package/dist/ui/screenshots/activity.png +0 -0
- package/dist/ui/screenshots/code-base-intelligence.png +0 -0
- package/dist/ui/screenshots/dashboard.png +0 -0
- package/dist/ui/screenshots/project-memory.png +0 -0
- package/dist/ui/screenshots/reasoning-quality.png +0 -0
- package/dist/ui/screenshots/reasoning-session.png +0 -0
- package/dist/ui/screenshots/token-session.png +0 -0
- package/dist/ui/screenshots/token-trace-main.png +0 -0
- package/dist/ui/screenshots/token-turn.png +0 -0
- package/dist/ui/unerr-wordmark.png +0 -0
- package/dist/ui/unerr-wordmark.svg +0 -9
- package/dist/ui/unerr.png +0 -0
- package/dist/ui/unerr.svg +0 -25
- package/dist/ui/web-app-manifest-192x192.png +0 -0
- package/dist/ui/web-app-manifest-512x512.png +0 -0
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Sprint 1 Ink components (Task 1.2).
|
|
3
|
-
*
|
|
4
|
-
* Tests validate:
|
|
5
|
-
* - Banner: renders brand name and tagline
|
|
6
|
-
* - Section: renders title with line decoration
|
|
7
|
-
* - KeyValue: renders label and value with alignment
|
|
8
|
-
* - ProgressBar: sub-character Unicode blocks, clamping, label
|
|
9
|
-
* - GradeBadge: colored grade with optional score
|
|
10
|
-
* - StepLine: icon + label + value for each status
|
|
11
|
-
* - HealthCard: grade + bar + dead functions + chokepoints
|
|
12
|
-
* - DriftSummary: modified/added/deleted counts
|
|
13
|
-
* - ViolationList: severity icons + messages + suggestions
|
|
14
|
-
* - SessionSummaryCard: tool calls, local rate, savings, latency
|
|
15
|
-
* - InkSpinner: renders frame and label
|
|
16
|
-
* - ConfirmPrompt: renders message and hint
|
|
17
|
-
*/
|
|
18
|
-
import { render } from "ink-testing-library";
|
|
19
|
-
import React from "react";
|
|
20
|
-
import { describe, expect, it } from "vitest";
|
|
21
|
-
import { Banner } from "../components/Banner.js";
|
|
22
|
-
import { ConfirmPrompt } from "../components/ConfirmPrompt.js";
|
|
23
|
-
import { DriftSummary } from "../components/DriftSummary.js";
|
|
24
|
-
import { GradeBadge } from "../components/GradeBadge.js";
|
|
25
|
-
import { HealthCard } from "../components/HealthCard.js";
|
|
26
|
-
import { InkSpinner } from "../components/InkSpinner.js";
|
|
27
|
-
import { KeyValue } from "../components/KeyValue.js";
|
|
28
|
-
import { BLOCKS, ProgressBar } from "../components/ProgressBar.js";
|
|
29
|
-
import { Section } from "../components/Section.js";
|
|
30
|
-
import { SessionSummaryCard } from "../components/SessionSummaryCard.js";
|
|
31
|
-
import { StepLine } from "../components/StepLine.js";
|
|
32
|
-
import { ViolationList } from "../components/ViolationList.js";
|
|
33
|
-
import { createSessionStats } from "../proxy/session-stats.js";
|
|
34
|
-
// ── Banner ───────────────────────────────────────────────────────
|
|
35
|
-
describe("Banner", () => {
|
|
36
|
-
it("renders brand name", () => {
|
|
37
|
-
const { lastFrame } = render(React.createElement(Banner));
|
|
38
|
-
expect(lastFrame()).toContain("unerr");
|
|
39
|
-
});
|
|
40
|
-
it("renders tagline", () => {
|
|
41
|
-
const { lastFrame } = render(React.createElement(Banner));
|
|
42
|
-
expect(lastFrame()).toContain("Code intelligence");
|
|
43
|
-
});
|
|
44
|
-
it("renders separator", () => {
|
|
45
|
-
const { lastFrame } = render(React.createElement(Banner));
|
|
46
|
-
expect(lastFrame()).toContain("▸");
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
// ── Section ──────────────────────────────────────────────────────
|
|
50
|
-
describe("Section", () => {
|
|
51
|
-
it("renders title with line decoration", () => {
|
|
52
|
-
const { lastFrame } = render(React.createElement(Section, { title: "First Look" }));
|
|
53
|
-
const frame = lastFrame() ?? "";
|
|
54
|
-
expect(frame).toContain("──");
|
|
55
|
-
expect(frame).toContain("First Look");
|
|
56
|
-
});
|
|
57
|
-
it("renders with custom width", () => {
|
|
58
|
-
const { lastFrame } = render(React.createElement(Section, { title: "Test", width: 30 }));
|
|
59
|
-
expect(lastFrame()).toContain("──");
|
|
60
|
-
expect(lastFrame()).toContain("Test");
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
// ── KeyValue ─────────────────────────────────────────────────────
|
|
64
|
-
describe("KeyValue", () => {
|
|
65
|
-
it("renders label and value", () => {
|
|
66
|
-
const { lastFrame } = render(React.createElement(KeyValue, {
|
|
67
|
-
label: "Repository",
|
|
68
|
-
value: "acme/widgets",
|
|
69
|
-
}));
|
|
70
|
-
const frame = lastFrame() ?? "";
|
|
71
|
-
expect(frame).toContain("Repository");
|
|
72
|
-
expect(frame).toContain("acme/widgets");
|
|
73
|
-
});
|
|
74
|
-
it("pads label to specified width", () => {
|
|
75
|
-
const { lastFrame } = render(React.createElement(KeyValue, {
|
|
76
|
-
label: "Key",
|
|
77
|
-
value: "val",
|
|
78
|
-
labelWidth: 10,
|
|
79
|
-
}));
|
|
80
|
-
// Label "Key" padded to 10 chars
|
|
81
|
-
expect(lastFrame()).toContain("Key");
|
|
82
|
-
expect(lastFrame()).toContain("val");
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
// ── ProgressBar ──────────────────────────────────────────────────
|
|
86
|
-
describe("ProgressBar", () => {
|
|
87
|
-
it("renders empty bar for value 0", () => {
|
|
88
|
-
const { lastFrame } = render(React.createElement(ProgressBar, { value: 0, width: 10 }));
|
|
89
|
-
const frame = lastFrame() ?? "";
|
|
90
|
-
// Should be all spaces (empty bar)
|
|
91
|
-
expect(frame.includes("█")).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
it("renders full bar for value 1", () => {
|
|
94
|
-
const { lastFrame } = render(React.createElement(ProgressBar, { value: 1, width: 10 }));
|
|
95
|
-
const frame = lastFrame() ?? "";
|
|
96
|
-
expect(frame).toContain("██████████");
|
|
97
|
-
});
|
|
98
|
-
it("renders partial fill for value 0.5", () => {
|
|
99
|
-
const { lastFrame } = render(React.createElement(ProgressBar, { value: 0.5, width: 10 }));
|
|
100
|
-
const frame = lastFrame() ?? "";
|
|
101
|
-
expect(frame).toContain("█████");
|
|
102
|
-
});
|
|
103
|
-
it("shows percentage label when showLabel is true", () => {
|
|
104
|
-
const { lastFrame } = render(React.createElement(ProgressBar, {
|
|
105
|
-
value: 0.62,
|
|
106
|
-
width: 10,
|
|
107
|
-
showLabel: true,
|
|
108
|
-
}));
|
|
109
|
-
expect(lastFrame()).toContain("62%");
|
|
110
|
-
});
|
|
111
|
-
it("clamps value above 1 to 100%", () => {
|
|
112
|
-
const { lastFrame } = render(React.createElement(ProgressBar, {
|
|
113
|
-
value: 1.5,
|
|
114
|
-
width: 5,
|
|
115
|
-
showLabel: true,
|
|
116
|
-
}));
|
|
117
|
-
expect(lastFrame()).toContain("100%");
|
|
118
|
-
});
|
|
119
|
-
it("clamps value below 0 to 0%", () => {
|
|
120
|
-
const { lastFrame } = render(React.createElement(ProgressBar, {
|
|
121
|
-
value: -0.5,
|
|
122
|
-
width: 5,
|
|
123
|
-
showLabel: true,
|
|
124
|
-
}));
|
|
125
|
-
expect(lastFrame()).toContain("0%");
|
|
126
|
-
});
|
|
127
|
-
it("BLOCKS array has 9 sub-character elements", () => {
|
|
128
|
-
expect(BLOCKS).toHaveLength(9);
|
|
129
|
-
expect(BLOCKS[0]).toBe(" ");
|
|
130
|
-
expect(BLOCKS[8]).toBe("█");
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
// ── GradeBadge ───────────────────────────────────────────────────
|
|
134
|
-
describe("GradeBadge", () => {
|
|
135
|
-
it("renders grade letter", () => {
|
|
136
|
-
const { lastFrame } = render(React.createElement(GradeBadge, { grade: "A" }));
|
|
137
|
-
expect(lastFrame()).toContain("A");
|
|
138
|
-
});
|
|
139
|
-
it("renders grade with score when provided", () => {
|
|
140
|
-
const { lastFrame } = render(React.createElement(GradeBadge, { grade: "C+", score: 62 }));
|
|
141
|
-
const frame = lastFrame() ?? "";
|
|
142
|
-
expect(frame).toContain("C+");
|
|
143
|
-
expect(frame).toContain("62/100");
|
|
144
|
-
});
|
|
145
|
-
it("renders without score when not provided", () => {
|
|
146
|
-
const { lastFrame } = render(React.createElement(GradeBadge, { grade: "B+" }));
|
|
147
|
-
expect(lastFrame()).toContain("B+");
|
|
148
|
-
expect(lastFrame()).not.toContain("/100");
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
// ── StepLine ─────────────────────────────────────────────────────
|
|
152
|
-
describe("StepLine", () => {
|
|
153
|
-
it("renders done status with check icon", () => {
|
|
154
|
-
const { lastFrame } = render(React.createElement(StepLine, {
|
|
155
|
-
label: "Authenticated",
|
|
156
|
-
value: "Org",
|
|
157
|
-
status: "done",
|
|
158
|
-
}));
|
|
159
|
-
const frame = lastFrame() ?? "";
|
|
160
|
-
expect(frame).toContain("✓");
|
|
161
|
-
expect(frame).toContain("Authenticated");
|
|
162
|
-
expect(frame).toContain("Org");
|
|
163
|
-
});
|
|
164
|
-
it("renders active status with filled circle", () => {
|
|
165
|
-
const { lastFrame } = render(React.createElement(StepLine, {
|
|
166
|
-
label: "Loading",
|
|
167
|
-
status: "active",
|
|
168
|
-
}));
|
|
169
|
-
expect(lastFrame()).toContain("●");
|
|
170
|
-
expect(lastFrame()).toContain("Loading");
|
|
171
|
-
});
|
|
172
|
-
it("renders error status with cross icon", () => {
|
|
173
|
-
const { lastFrame } = render(React.createElement(StepLine, {
|
|
174
|
-
label: "Failed",
|
|
175
|
-
status: "error",
|
|
176
|
-
}));
|
|
177
|
-
expect(lastFrame()).toContain("✗");
|
|
178
|
-
});
|
|
179
|
-
it("renders pending status with empty circle", () => {
|
|
180
|
-
const { lastFrame } = render(React.createElement(StepLine, {
|
|
181
|
-
label: "Waiting",
|
|
182
|
-
status: "pending",
|
|
183
|
-
}));
|
|
184
|
-
expect(lastFrame()).toContain("○");
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
// ── HealthCard ───────────────────────────────────────────────────
|
|
188
|
-
describe("HealthCard", () => {
|
|
189
|
-
const baseHealth = {
|
|
190
|
-
grade: "C+",
|
|
191
|
-
totalEntities: 2341,
|
|
192
|
-
totalEdges: 1892,
|
|
193
|
-
totalRules: 12,
|
|
194
|
-
deadFunctionCount: 23,
|
|
195
|
-
highRiskEntities: [
|
|
196
|
-
{
|
|
197
|
-
name: "processPayment",
|
|
198
|
-
kind: "function",
|
|
199
|
-
file_path: "src/billing.ts",
|
|
200
|
-
fan_in: 14,
|
|
201
|
-
fan_out: 8,
|
|
202
|
-
},
|
|
203
|
-
],
|
|
204
|
-
score: 62,
|
|
205
|
-
};
|
|
206
|
-
it("renders grade and score in full mode", () => {
|
|
207
|
-
const { lastFrame } = render(React.createElement(HealthCard, { health: baseHealth }));
|
|
208
|
-
const frame = lastFrame() ?? "";
|
|
209
|
-
expect(frame).toContain("C+");
|
|
210
|
-
expect(frame).toContain("62/100");
|
|
211
|
-
});
|
|
212
|
-
it("renders dead function warning", () => {
|
|
213
|
-
const { lastFrame } = render(React.createElement(HealthCard, { health: baseHealth }));
|
|
214
|
-
expect(lastFrame()).toContain("23 dead functions");
|
|
215
|
-
});
|
|
216
|
-
it("renders chokepoint warning with fan counts", () => {
|
|
217
|
-
const { lastFrame } = render(React.createElement(HealthCard, { health: baseHealth }));
|
|
218
|
-
const frame = lastFrame() ?? "";
|
|
219
|
-
expect(frame).toContain("processPayment");
|
|
220
|
-
expect(frame).toContain("14 callers");
|
|
221
|
-
expect(frame).toContain("8 callees");
|
|
222
|
-
expect(frame).toContain("chokepoint");
|
|
223
|
-
});
|
|
224
|
-
it("renders compact mode with entity/edge counts", () => {
|
|
225
|
-
const { lastFrame } = render(React.createElement(HealthCard, { health: baseHealth, compact: true }));
|
|
226
|
-
const frame = lastFrame() ?? "";
|
|
227
|
-
expect(frame).toContain("C+");
|
|
228
|
-
expect(frame).toContain("2341 entities");
|
|
229
|
-
expect(frame).toContain("1892 edges");
|
|
230
|
-
});
|
|
231
|
-
it("omits dead function line when count is 0", () => {
|
|
232
|
-
const healthy = { ...baseHealth, deadFunctionCount: 0 };
|
|
233
|
-
const { lastFrame } = render(React.createElement(HealthCard, { health: healthy }));
|
|
234
|
-
expect(lastFrame()).not.toContain("dead function");
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
// ── DriftSummary ─────────────────────────────────────────────────
|
|
238
|
-
describe("DriftSummary", () => {
|
|
239
|
-
it("renders zero drift message", () => {
|
|
240
|
-
const { lastFrame } = render(React.createElement(DriftSummary, {
|
|
241
|
-
drift: { modified: 0, added: 0, deleted: 0 },
|
|
242
|
-
}));
|
|
243
|
-
expect(lastFrame()).toContain("No drift detected");
|
|
244
|
-
});
|
|
245
|
-
it("renders modified count", () => {
|
|
246
|
-
const { lastFrame } = render(React.createElement(DriftSummary, {
|
|
247
|
-
drift: { modified: 5, added: 0, deleted: 0 },
|
|
248
|
-
}));
|
|
249
|
-
expect(lastFrame()).toContain("5 modified");
|
|
250
|
-
});
|
|
251
|
-
it("renders all drift types", () => {
|
|
252
|
-
const { lastFrame } = render(React.createElement(DriftSummary, {
|
|
253
|
-
drift: { modified: 3, added: 2, deleted: 1 },
|
|
254
|
-
}));
|
|
255
|
-
const frame = lastFrame() ?? "";
|
|
256
|
-
expect(frame).toContain("6 drifted entities");
|
|
257
|
-
expect(frame).toContain("3 modified");
|
|
258
|
-
expect(frame).toContain("2 added");
|
|
259
|
-
expect(frame).toContain("1 deleted");
|
|
260
|
-
});
|
|
261
|
-
it("uses singular 'entity' for count of 1", () => {
|
|
262
|
-
const { lastFrame } = render(React.createElement(DriftSummary, {
|
|
263
|
-
drift: { modified: 1, added: 0, deleted: 0 },
|
|
264
|
-
}));
|
|
265
|
-
expect(lastFrame()).toContain("1 drifted entity");
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
// ── ViolationList ────────────────────────────────────────────────
|
|
269
|
-
describe("ViolationList", () => {
|
|
270
|
-
it("renders no-violations message when empty", () => {
|
|
271
|
-
const { lastFrame } = render(React.createElement(ViolationList, { violations: [] }));
|
|
272
|
-
expect(lastFrame()).toContain("No violations");
|
|
273
|
-
});
|
|
274
|
-
it("renders error violation with cross icon", () => {
|
|
275
|
-
const { lastFrame } = render(React.createElement(ViolationList, {
|
|
276
|
-
violations: [
|
|
277
|
-
{ message: "Missing return type", severity: "error" },
|
|
278
|
-
],
|
|
279
|
-
}));
|
|
280
|
-
const frame = lastFrame() ?? "";
|
|
281
|
-
expect(frame).toContain("✗");
|
|
282
|
-
expect(frame).toContain("Missing return type");
|
|
283
|
-
});
|
|
284
|
-
it("renders warning violation with warning icon", () => {
|
|
285
|
-
const { lastFrame } = render(React.createElement(ViolationList, {
|
|
286
|
-
violations: [
|
|
287
|
-
{ message: "Unused import", severity: "warning" },
|
|
288
|
-
],
|
|
289
|
-
}));
|
|
290
|
-
const frame = lastFrame() ?? "";
|
|
291
|
-
expect(frame).toContain("⚠");
|
|
292
|
-
expect(frame).toContain("Unused import");
|
|
293
|
-
});
|
|
294
|
-
it("renders file path when provided", () => {
|
|
295
|
-
const { lastFrame } = render(React.createElement(ViolationList, {
|
|
296
|
-
violations: [
|
|
297
|
-
{
|
|
298
|
-
message: "Bad naming",
|
|
299
|
-
severity: "info",
|
|
300
|
-
file: "src/foo.ts",
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
}));
|
|
304
|
-
expect(lastFrame()).toContain("src/foo.ts");
|
|
305
|
-
});
|
|
306
|
-
it("renders suggestion when provided", () => {
|
|
307
|
-
const { lastFrame } = render(React.createElement(ViolationList, {
|
|
308
|
-
violations: [
|
|
309
|
-
{
|
|
310
|
-
message: "Unused var",
|
|
311
|
-
severity: "warning",
|
|
312
|
-
suggestion: "Remove or prefix with _",
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
}));
|
|
316
|
-
expect(lastFrame()).toContain("Remove or prefix with _");
|
|
317
|
-
});
|
|
318
|
-
it("renders violation count in title", () => {
|
|
319
|
-
const { lastFrame } = render(React.createElement(ViolationList, {
|
|
320
|
-
violations: [
|
|
321
|
-
{ message: "Issue 1", severity: "error" },
|
|
322
|
-
{ message: "Issue 2", severity: "warning" },
|
|
323
|
-
],
|
|
324
|
-
title: "Violations",
|
|
325
|
-
}));
|
|
326
|
-
expect(lastFrame()).toContain("2 violations");
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
// ── SessionSummaryCard ───────────────────────────────────────────
|
|
330
|
-
describe("SessionSummaryCard", () => {
|
|
331
|
-
it("renders tool call counts in full summary", () => {
|
|
332
|
-
const stats = createSessionStats();
|
|
333
|
-
stats.toolCallsLocal = 15;
|
|
334
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
335
|
-
const frame = lastFrame() ?? "";
|
|
336
|
-
expect(frame).toContain("15 (all local)");
|
|
337
|
-
});
|
|
338
|
-
it("renders local rate with progress bar", () => {
|
|
339
|
-
const stats = createSessionStats();
|
|
340
|
-
stats.toolCallsLocal = 16;
|
|
341
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
342
|
-
expect(lastFrame()).toContain("100%");
|
|
343
|
-
expect(lastFrame()).toContain("Local rate");
|
|
344
|
-
});
|
|
345
|
-
it("renders session section header with duration", () => {
|
|
346
|
-
const stats = createSessionStats();
|
|
347
|
-
stats.toolCallsLocal = 20;
|
|
348
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
349
|
-
expect(lastFrame()).toContain("unerr session");
|
|
350
|
-
});
|
|
351
|
-
it("renders specific caught event labels", () => {
|
|
352
|
-
const stats = createSessionStats();
|
|
353
|
-
stats.toolCallsLocal = 20;
|
|
354
|
-
stats.events.conventionViolationsCaught = 3;
|
|
355
|
-
stats.events.chokepointWarningsIssued = 2;
|
|
356
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
357
|
-
const frame = lastFrame() ?? "";
|
|
358
|
-
expect(frame).toContain("Caught:");
|
|
359
|
-
expect(frame).toContain("3 convention violations before commit");
|
|
360
|
-
expect(frame).toContain("2 chokepoint modifications warned");
|
|
361
|
-
});
|
|
362
|
-
it("renders one-liner for short sessions (≤10 tool calls)", () => {
|
|
363
|
-
const stats = createSessionStats();
|
|
364
|
-
stats.toolCallsLocal = 5;
|
|
365
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
366
|
-
const frame = lastFrame() ?? "";
|
|
367
|
-
expect(frame).toContain("5 tool calls");
|
|
368
|
-
// Should NOT show full section header
|
|
369
|
-
expect(frame).not.toContain("Tool calls");
|
|
370
|
-
});
|
|
371
|
-
it("renders nothing when zero tool calls", () => {
|
|
372
|
-
const stats = createSessionStats();
|
|
373
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
374
|
-
expect(lastFrame()).toBe("");
|
|
375
|
-
});
|
|
376
|
-
it("renders deep link when provided", () => {
|
|
377
|
-
const stats = createSessionStats();
|
|
378
|
-
stats.toolCallsLocal = 20;
|
|
379
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, {
|
|
380
|
-
stats,
|
|
381
|
-
deepLink: "https://app.unerr.dev/r/repo_123?utm_source=cli_session",
|
|
382
|
-
}));
|
|
383
|
-
expect(lastFrame()).toContain("https://app.unerr.dev/r/repo_123");
|
|
384
|
-
});
|
|
385
|
-
it("renders cumulative this-week stats", () => {
|
|
386
|
-
const stats = createSessionStats();
|
|
387
|
-
stats.toolCallsLocal = 20;
|
|
388
|
-
const cumulative = {
|
|
389
|
-
totalTokensSaved: 100000,
|
|
390
|
-
totalDollarsSaved: 5.6,
|
|
391
|
-
totalSessions: 4,
|
|
392
|
-
weekStart: "2026-04-06",
|
|
393
|
-
violationsCaughtAllTime: 12,
|
|
394
|
-
chokepointWarningsAllTime: 3,
|
|
395
|
-
};
|
|
396
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats, cumulative }));
|
|
397
|
-
const frame = lastFrame() ?? "";
|
|
398
|
-
expect(frame).toContain("This week:");
|
|
399
|
-
expect(frame).toContain("4 sessions");
|
|
400
|
-
expect(frame).toContain("$5.60");
|
|
401
|
-
});
|
|
402
|
-
it("omits caught section when no events", () => {
|
|
403
|
-
const stats = createSessionStats();
|
|
404
|
-
stats.toolCallsLocal = 20;
|
|
405
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
406
|
-
expect(lastFrame()).not.toContain("Caught:");
|
|
407
|
-
});
|
|
408
|
-
it("renders token savings", () => {
|
|
409
|
-
const stats = createSessionStats();
|
|
410
|
-
stats.toolCallsLocal = 20;
|
|
411
|
-
stats.estimatedTokensSaved = 64000;
|
|
412
|
-
const { lastFrame } = render(React.createElement(SessionSummaryCard, { stats }));
|
|
413
|
-
expect(lastFrame()).toContain("64.0k tokens");
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
// ── InkSpinner ───────────────────────────────────────────────────
|
|
417
|
-
describe("InkSpinner", () => {
|
|
418
|
-
it("renders label text", () => {
|
|
419
|
-
const { lastFrame } = render(React.createElement(InkSpinner, { label: "Loading graph..." }));
|
|
420
|
-
expect(lastFrame()).toContain("Loading graph...");
|
|
421
|
-
});
|
|
422
|
-
it("renders a spinner frame character", () => {
|
|
423
|
-
const { lastFrame } = render(React.createElement(InkSpinner, { label: "test" }));
|
|
424
|
-
const frame = lastFrame() ?? "";
|
|
425
|
-
// Should contain one of the braille spinner chars
|
|
426
|
-
expect(frame.match(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/)).toBeTruthy();
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
// ── ConfirmPrompt ────────────────────────────────────────────────
|
|
430
|
-
describe("ConfirmPrompt", () => {
|
|
431
|
-
it("renders message and default-yes hint", () => {
|
|
432
|
-
const { lastFrame } = render(React.createElement(ConfirmPrompt, {
|
|
433
|
-
message: "Delete this?",
|
|
434
|
-
onConfirm: () => { },
|
|
435
|
-
defaultYes: true,
|
|
436
|
-
}));
|
|
437
|
-
const frame = lastFrame() ?? "";
|
|
438
|
-
expect(frame).toContain("Delete this?");
|
|
439
|
-
expect(frame).toContain("[Y/n]");
|
|
440
|
-
});
|
|
441
|
-
it("renders default-no hint", () => {
|
|
442
|
-
const { lastFrame } = render(React.createElement(ConfirmPrompt, {
|
|
443
|
-
message: "Proceed?",
|
|
444
|
-
onConfirm: () => { },
|
|
445
|
-
defaultYes: false,
|
|
446
|
-
}));
|
|
447
|
-
expect(lastFrame()).toContain("[y/N]");
|
|
448
|
-
});
|
|
449
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, 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 { appendCompressionLog, appendFileReadLog, readRecentCompressionLogs, readRecentFileReadLogs, } from "../proxy/shell-compression-log.js";
|
|
6
|
-
import { closeMetricsStore } from "../tracking/metrics-store.js";
|
|
7
|
-
describe("compression-log", () => {
|
|
8
|
-
let tmpDir;
|
|
9
|
-
let unerrDir;
|
|
10
|
-
let dbPath;
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
tmpDir = join(os.tmpdir(), `unerr-complog-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
13
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
14
|
-
unerrDir = join(tmpDir, ".unerr");
|
|
15
|
-
dbPath = join(unerrDir, "metrics.db");
|
|
16
|
-
});
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
closeMetricsStore(unerrDir);
|
|
19
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
20
|
-
});
|
|
21
|
-
function makeEntry(overrides) {
|
|
22
|
-
return {
|
|
23
|
-
ts: new Date().toISOString(),
|
|
24
|
-
command: "ps aux",
|
|
25
|
-
category: "tabular",
|
|
26
|
-
confidence: 0.85,
|
|
27
|
-
rawBytes: 1000,
|
|
28
|
-
compressedBytes: 300,
|
|
29
|
-
savedPct: 70,
|
|
30
|
-
omniFallback: false,
|
|
31
|
-
...overrides,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
it("creates metrics.db and inserts compression rows", () => {
|
|
35
|
-
appendCompressionLog(tmpDir, makeEntry());
|
|
36
|
-
expect(existsSync(dbPath)).toBe(true);
|
|
37
|
-
const entries = readRecentCompressionLogs(tmpDir, 10);
|
|
38
|
-
expect(entries).toHaveLength(1);
|
|
39
|
-
expect(entries[0].command).toBe("ps aux");
|
|
40
|
-
expect(entries[0].category).toBe("tabular");
|
|
41
|
-
expect(entries[0].savedPct).toBe(70);
|
|
42
|
-
});
|
|
43
|
-
it("records entries with 0% savings (passthrough)", () => {
|
|
44
|
-
appendCompressionLog(tmpDir, makeEntry({ savedPct: 0, compressedBytes: 1000 }));
|
|
45
|
-
const entries = readRecentCompressionLogs(tmpDir, 10);
|
|
46
|
-
expect(entries).toHaveLength(1);
|
|
47
|
-
expect(entries[0].savedPct).toBe(0);
|
|
48
|
-
});
|
|
49
|
-
it("readRecentCompressionLogs returns entries in reverse chronological order", () => {
|
|
50
|
-
appendCompressionLog(tmpDir, makeEntry({ command: "first", ts: "2024-01-01T00:00:00Z" }));
|
|
51
|
-
appendCompressionLog(tmpDir, makeEntry({ command: "second", ts: "2024-01-01T00:01:00Z" }));
|
|
52
|
-
appendCompressionLog(tmpDir, makeEntry({ command: "third", ts: "2024-01-01T00:02:00Z" }));
|
|
53
|
-
const entries = readRecentCompressionLogs(tmpDir, 10);
|
|
54
|
-
expect(entries).toHaveLength(3);
|
|
55
|
-
expect(entries[0].command).toBe("third");
|
|
56
|
-
expect(entries[2].command).toBe("first");
|
|
57
|
-
});
|
|
58
|
-
it("readRecentCompressionLogs respects limit", () => {
|
|
59
|
-
for (let i = 0; i < 20; i++) {
|
|
60
|
-
appendCompressionLog(tmpDir, makeEntry({ command: `cmd-${i}` }));
|
|
61
|
-
}
|
|
62
|
-
const entries = readRecentCompressionLogs(tmpDir, 5);
|
|
63
|
-
expect(entries).toHaveLength(5);
|
|
64
|
-
expect(entries[0].command).toBe("cmd-19");
|
|
65
|
-
});
|
|
66
|
-
it("returns empty array when no log file exists", () => {
|
|
67
|
-
const entries = readRecentCompressionLogs(tmpDir, 10);
|
|
68
|
-
expect(entries).toHaveLength(0);
|
|
69
|
-
});
|
|
70
|
-
it("includes teeFile when present", () => {
|
|
71
|
-
appendCompressionLog(tmpDir, makeEntry({ teeFile: "/tmp/shell-tee-123.txt" }));
|
|
72
|
-
const entries = readRecentCompressionLogs(tmpDir, 1);
|
|
73
|
-
expect(entries[0].teeFile).toBe("/tmp/shell-tee-123.txt");
|
|
74
|
-
});
|
|
75
|
-
it("handles high-volume writes (replaces legacy line rotation)", () => {
|
|
76
|
-
// SQLite doesn't trim rows — it indexes them. Verify 1050 inserts
|
|
77
|
-
// round-trip and the most recent entry surfaces first.
|
|
78
|
-
for (let i = 0; i < 1050; i++) {
|
|
79
|
-
appendCompressionLog(tmpDir, makeEntry({ command: `cmd-${i}` }));
|
|
80
|
-
}
|
|
81
|
-
const entries = readRecentCompressionLogs(tmpDir, 5);
|
|
82
|
-
expect(entries).toHaveLength(5);
|
|
83
|
-
expect(entries[0].command).toBe("cmd-1049");
|
|
84
|
-
expect(entries[4].command).toBe("cmd-1045");
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
describe("file-read-log", () => {
|
|
88
|
-
let tmpDir;
|
|
89
|
-
let unerrDir;
|
|
90
|
-
let dbPath;
|
|
91
|
-
beforeEach(() => {
|
|
92
|
-
tmpDir = join(os.tmpdir(), `unerr-filelog-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
93
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
94
|
-
unerrDir = join(tmpDir, ".unerr");
|
|
95
|
-
dbPath = join(unerrDir, "metrics.db");
|
|
96
|
-
});
|
|
97
|
-
afterEach(() => {
|
|
98
|
-
closeMetricsStore(unerrDir);
|
|
99
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
100
|
-
});
|
|
101
|
-
function makeFileEntry(overrides) {
|
|
102
|
-
return {
|
|
103
|
-
ts: new Date().toISOString(),
|
|
104
|
-
file: "src/main.ts",
|
|
105
|
-
mode: "entity",
|
|
106
|
-
totalLines: 500,
|
|
107
|
-
returnedLines: 45,
|
|
108
|
-
savedPct: 91,
|
|
109
|
-
...overrides,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
it("creates metrics.db and inserts file-read rows", () => {
|
|
113
|
-
appendFileReadLog(tmpDir, makeFileEntry());
|
|
114
|
-
expect(existsSync(dbPath)).toBe(true);
|
|
115
|
-
const entries = readRecentFileReadLogs(tmpDir, 10);
|
|
116
|
-
expect(entries).toHaveLength(1);
|
|
117
|
-
expect(entries[0].file).toBe("src/main.ts");
|
|
118
|
-
expect(entries[0].mode).toBe("entity");
|
|
119
|
-
expect(entries[0].savedPct).toBe(91);
|
|
120
|
-
});
|
|
121
|
-
it("entry contains all fields", () => {
|
|
122
|
-
appendFileReadLog(tmpDir, makeFileEntry({
|
|
123
|
-
entity: "MyClass",
|
|
124
|
-
tokenEstimate: 1200,
|
|
125
|
-
}));
|
|
126
|
-
const entries = readRecentFileReadLogs(tmpDir, 1);
|
|
127
|
-
expect(entries).toHaveLength(1);
|
|
128
|
-
const e = entries[0];
|
|
129
|
-
expect(e.file).toBe("src/main.ts");
|
|
130
|
-
expect(e.mode).toBe("entity");
|
|
131
|
-
expect(e.totalLines).toBe(500);
|
|
132
|
-
expect(e.returnedLines).toBe(45);
|
|
133
|
-
expect(e.savedPct).toBe(91);
|
|
134
|
-
expect(e.entity).toBe("MyClass");
|
|
135
|
-
expect(e.tokenEstimate).toBe(1200);
|
|
136
|
-
});
|
|
137
|
-
it("readRecentFileReadLogs returns entries in reverse chronological order", () => {
|
|
138
|
-
appendFileReadLog(tmpDir, makeFileEntry({ file: "a.ts", ts: "2024-01-01T00:00:00Z" }));
|
|
139
|
-
appendFileReadLog(tmpDir, makeFileEntry({ file: "b.ts", ts: "2024-01-01T00:01:00Z" }));
|
|
140
|
-
appendFileReadLog(tmpDir, makeFileEntry({ file: "c.ts", ts: "2024-01-01T00:02:00Z" }));
|
|
141
|
-
const entries = readRecentFileReadLogs(tmpDir, 10);
|
|
142
|
-
expect(entries).toHaveLength(3);
|
|
143
|
-
expect(entries[0].file).toBe("c.ts");
|
|
144
|
-
expect(entries[2].file).toBe("a.ts");
|
|
145
|
-
});
|
|
146
|
-
it("readRecentFileReadLogs respects limit", () => {
|
|
147
|
-
for (let i = 0; i < 15; i++) {
|
|
148
|
-
appendFileReadLog(tmpDir, makeFileEntry({ file: `file-${i}.ts` }));
|
|
149
|
-
}
|
|
150
|
-
const entries = readRecentFileReadLogs(tmpDir, 5);
|
|
151
|
-
expect(entries).toHaveLength(5);
|
|
152
|
-
expect(entries[0].file).toBe("file-14.ts");
|
|
153
|
-
});
|
|
154
|
-
it("returns empty array when no log file exists", () => {
|
|
155
|
-
const entries = readRecentFileReadLogs(tmpDir, 10);
|
|
156
|
-
expect(entries).toHaveLength(0);
|
|
157
|
-
});
|
|
158
|
-
it("supports all file-read modes", () => {
|
|
159
|
-
const modes = [
|
|
160
|
-
"outline",
|
|
161
|
-
"entity",
|
|
162
|
-
"slice",
|
|
163
|
-
"full",
|
|
164
|
-
"log_tail",
|
|
165
|
-
"gated",
|
|
166
|
-
];
|
|
167
|
-
for (const mode of modes) {
|
|
168
|
-
appendFileReadLog(tmpDir, makeFileEntry({ mode }));
|
|
169
|
-
}
|
|
170
|
-
const entries = readRecentFileReadLogs(tmpDir, 10);
|
|
171
|
-
expect(entries).toHaveLength(6);
|
|
172
|
-
expect(entries.map((e) => e.mode).sort()).toEqual([...modes].sort());
|
|
173
|
-
});
|
|
174
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 6 Sprint FE-E — Layer 6 tier escalation on repeated retries.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { createCompressionQualityMonitor } from "../proxy/compression-quality-monitor.js";
|
|
6
|
-
describe("CompressionQualityMonitor Layer 6 tiers (FE-E)", () => {
|
|
7
|
-
it("starts at columnar tier", () => {
|
|
8
|
-
const m = createCompressionQualityMonitor();
|
|
9
|
-
expect(m.getLayer6Tier("get_callers")).toBe("columnar");
|
|
10
|
-
expect(m.getLayer6RetryCount("get_callers")).toBe(0);
|
|
11
|
-
});
|
|
12
|
-
it("escalates to minified after 3 retries", () => {
|
|
13
|
-
const m = createCompressionQualityMonitor();
|
|
14
|
-
m.recordLayer6Retry("search_code");
|
|
15
|
-
m.recordLayer6Retry("search_code");
|
|
16
|
-
expect(m.getLayer6Tier("search_code")).toBe("columnar");
|
|
17
|
-
m.recordLayer6Retry("search_code");
|
|
18
|
-
expect(m.getLayer6Tier("search_code")).toBe("minified");
|
|
19
|
-
expect(m.getLayer6RetryCount("search_code")).toBe(3);
|
|
20
|
-
});
|
|
21
|
-
it("escalates to expanded after 6 retries", () => {
|
|
22
|
-
const m = createCompressionQualityMonitor();
|
|
23
|
-
for (let i = 0; i < 6; i++)
|
|
24
|
-
m.recordLayer6Retry("get_function");
|
|
25
|
-
expect(m.getLayer6Tier("get_function")).toBe("expanded");
|
|
26
|
-
});
|
|
27
|
-
it("tracks layer6 content types for retention", () => {
|
|
28
|
-
const m = createCompressionQualityMonitor();
|
|
29
|
-
expect(m.getRetention("layer6_columnar")).toBeGreaterThanOrEqual(0.4);
|
|
30
|
-
});
|
|
31
|
-
it("FE-F: shell category retention defaults exist", () => {
|
|
32
|
-
const m = createCompressionQualityMonitor();
|
|
33
|
-
expect(m.getRetention("shell_diff")).toBeGreaterThanOrEqual(0.4);
|
|
34
|
-
expect(m.getRetention("shell_structured")).toBeGreaterThanOrEqual(0.4);
|
|
35
|
-
expect(m.getRetention("shell_error_diagnostic")).toBeGreaterThanOrEqual(0.4);
|
|
36
|
-
m.recordCompression("s1", "shell_tabular", 0.5);
|
|
37
|
-
m.recordAgentAction("e", true, false);
|
|
38
|
-
expect(m.getSignalCount()).toBeGreaterThan(0);
|
|
39
|
-
});
|
|
40
|
-
});
|