@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.
- package/README.md +70 -194
- package/dist/cli.js +39149 -36991
- package/package.json +9 -2
- 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,330 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Leapfrog Sprint B TEST: Correction detector.
|
|
3
|
-
*
|
|
4
|
-
* Tests the 6-phase correction detection pipeline against synthetic
|
|
5
|
-
* shadow ledger data. Validates: error signal detection, correction pairing,
|
|
6
|
-
* confidence scoring, false positive filtering, and deduplication.
|
|
7
|
-
*/
|
|
8
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { describe, expect, it } from "vitest";
|
|
12
|
-
import { detectCorrections } from "../tracking/correction-detector.js";
|
|
13
|
-
function createTempLedger(entries) {
|
|
14
|
-
const dir = join(tmpdir(), `unerr-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
15
|
-
mkdirSync(dir, { recursive: true });
|
|
16
|
-
const path = join(dir, "shadow.jsonl");
|
|
17
|
-
const content = `${entries.map((e) => JSON.stringify(e)).join("\n")}\n`;
|
|
18
|
-
writeFileSync(path, content, "utf-8");
|
|
19
|
-
return path;
|
|
20
|
-
}
|
|
21
|
-
function makeLedgerEntry(overrides) {
|
|
22
|
-
return {
|
|
23
|
-
id: Math.random().toString(36).slice(2),
|
|
24
|
-
ts: new Date().toISOString(),
|
|
25
|
-
args_summary: {},
|
|
26
|
-
result_summary: {},
|
|
27
|
-
branch: "main",
|
|
28
|
-
head_sha: "abc123",
|
|
29
|
-
session_id: "session-1",
|
|
30
|
-
correlation_id: null,
|
|
31
|
-
...overrides,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
describe("detectCorrections", () => {
|
|
35
|
-
it("returns empty for nonexistent ledger", () => {
|
|
36
|
-
const result = detectCorrections("/nonexistent/path.jsonl");
|
|
37
|
-
expect(result).toEqual([]);
|
|
38
|
-
});
|
|
39
|
-
it("returns empty for empty ledger", () => {
|
|
40
|
-
const path = createTempLedger([]);
|
|
41
|
-
const result = detectCorrections(path);
|
|
42
|
-
expect(result).toEqual([]);
|
|
43
|
-
});
|
|
44
|
-
it("detects a simple error→fix correction pair", () => {
|
|
45
|
-
const now = Date.now();
|
|
46
|
-
const entries = [
|
|
47
|
-
makeLedgerEntry({
|
|
48
|
-
tool: "get_function",
|
|
49
|
-
ts: new Date(now).toISOString(),
|
|
50
|
-
args_summary: { key: "src/auth.ts:validateToken" },
|
|
51
|
-
result_summary: { found: true },
|
|
52
|
-
}),
|
|
53
|
-
makeLedgerEntry({
|
|
54
|
-
tool: "sync_local_diff",
|
|
55
|
-
ts: new Date(now + 5000).toISOString(),
|
|
56
|
-
args_summary: { file_path: "src/auth.ts" },
|
|
57
|
-
result_summary: {},
|
|
58
|
-
}),
|
|
59
|
-
makeLedgerEntry({
|
|
60
|
-
tool: "get_function",
|
|
61
|
-
ts: new Date(now + 15000).toISOString(),
|
|
62
|
-
args_summary: { key: "src/auth.ts:validateToken" },
|
|
63
|
-
result_summary: {
|
|
64
|
-
error: "TypeError: Cannot read property 'userId' of null",
|
|
65
|
-
},
|
|
66
|
-
}),
|
|
67
|
-
];
|
|
68
|
-
const path = createTempLedger(entries);
|
|
69
|
-
const result = detectCorrections(path, { min_confidence: 0.4 });
|
|
70
|
-
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
71
|
-
const correction = result.find((c) => c.entity_key === "src/auth.ts:validateToken");
|
|
72
|
-
expect(correction).toBeDefined();
|
|
73
|
-
expect(correction?.error_type).toBe("type_error");
|
|
74
|
-
expect(correction?.confidence).toBeGreaterThanOrEqual(0.5);
|
|
75
|
-
});
|
|
76
|
-
it("assigns higher confidence when fix succeeded (no re-query within 5min)", () => {
|
|
77
|
-
const now = Date.now();
|
|
78
|
-
const entries = [
|
|
79
|
-
makeLedgerEntry({
|
|
80
|
-
tool: "get_function",
|
|
81
|
-
ts: new Date(now).toISOString(),
|
|
82
|
-
args_summary: { key: "src/payment.ts:process" },
|
|
83
|
-
result_summary: { found: true },
|
|
84
|
-
}),
|
|
85
|
-
makeLedgerEntry({
|
|
86
|
-
tool: "sync_local_diff",
|
|
87
|
-
ts: new Date(now + 5000).toISOString(),
|
|
88
|
-
args_summary: {},
|
|
89
|
-
result_summary: {},
|
|
90
|
-
}),
|
|
91
|
-
makeLedgerEntry({
|
|
92
|
-
tool: "get_function",
|
|
93
|
-
ts: new Date(now + 20000).toISOString(),
|
|
94
|
-
args_summary: { key: "src/payment.ts:process" },
|
|
95
|
-
result_summary: { error: "TypeError: amount is not a number" },
|
|
96
|
-
}),
|
|
97
|
-
// No further re-query of this entity for 5+ minutes → fix succeeded
|
|
98
|
-
makeLedgerEntry({
|
|
99
|
-
tool: "get_function",
|
|
100
|
-
ts: new Date(now + 600_000).toISOString(),
|
|
101
|
-
args_summary: { key: "src/other.ts:unrelated" },
|
|
102
|
-
result_summary: { found: true },
|
|
103
|
-
}),
|
|
104
|
-
];
|
|
105
|
-
const path = createTempLedger(entries);
|
|
106
|
-
const result = detectCorrections(path, { min_confidence: 0.4 });
|
|
107
|
-
const correction = result.find((c) => c.entity_key === "src/payment.ts:process");
|
|
108
|
-
expect(correction).toBeDefined();
|
|
109
|
-
// Base 0.5 + fix success bonus 0.2 = at least 0.7
|
|
110
|
-
expect(correction?.confidence).toBeGreaterThanOrEqual(0.7);
|
|
111
|
-
});
|
|
112
|
-
it("filters corrections below minimum confidence threshold", () => {
|
|
113
|
-
const now = Date.now();
|
|
114
|
-
const entries = [
|
|
115
|
-
makeLedgerEntry({
|
|
116
|
-
tool: "get_function",
|
|
117
|
-
ts: new Date(now).toISOString(),
|
|
118
|
-
args_summary: { key: "src/utils.ts:helper" },
|
|
119
|
-
result_summary: { found: true },
|
|
120
|
-
}),
|
|
121
|
-
makeLedgerEntry({
|
|
122
|
-
tool: "sync_local_diff",
|
|
123
|
-
ts: new Date(now + 5000).toISOString(),
|
|
124
|
-
args_summary: {},
|
|
125
|
-
result_summary: {},
|
|
126
|
-
}),
|
|
127
|
-
makeLedgerEntry({
|
|
128
|
-
tool: "get_function",
|
|
129
|
-
ts: new Date(now + 15000).toISOString(),
|
|
130
|
-
args_summary: { key: "src/utils.ts:helper" },
|
|
131
|
-
result_summary: { error: "TypeError" },
|
|
132
|
-
}),
|
|
133
|
-
// Another re-query immediately → fix didn't succeed → lower confidence
|
|
134
|
-
makeLedgerEntry({
|
|
135
|
-
tool: "get_function",
|
|
136
|
-
ts: new Date(now + 25000).toISOString(),
|
|
137
|
-
args_summary: { key: "src/utils.ts:helper" },
|
|
138
|
-
result_summary: { error: "TypeError" },
|
|
139
|
-
}),
|
|
140
|
-
];
|
|
141
|
-
const path = createTempLedger(entries);
|
|
142
|
-
const highThreshold = detectCorrections(path, { min_confidence: 0.9 });
|
|
143
|
-
expect(highThreshold.length).toBe(0);
|
|
144
|
-
});
|
|
145
|
-
it("deduplicates patterns by entity_key + error_type", () => {
|
|
146
|
-
const now = Date.now();
|
|
147
|
-
const entries = [];
|
|
148
|
-
// Create two identical error→fix pairs for the same entity
|
|
149
|
-
for (let session = 0; session < 2; session++) {
|
|
150
|
-
const offset = session * 100_000;
|
|
151
|
-
entries.push(makeLedgerEntry({
|
|
152
|
-
tool: "get_function",
|
|
153
|
-
ts: new Date(now + offset).toISOString(),
|
|
154
|
-
args_summary: { key: "src/db.ts:query" },
|
|
155
|
-
result_summary: { found: true },
|
|
156
|
-
session_id: `session-${session}`,
|
|
157
|
-
}), makeLedgerEntry({
|
|
158
|
-
tool: "sync_local_diff",
|
|
159
|
-
ts: new Date(now + offset + 5000).toISOString(),
|
|
160
|
-
args_summary: {},
|
|
161
|
-
result_summary: {},
|
|
162
|
-
session_id: `session-${session}`,
|
|
163
|
-
}), makeLedgerEntry({
|
|
164
|
-
tool: "get_function",
|
|
165
|
-
ts: new Date(now + offset + 15000).toISOString(),
|
|
166
|
-
args_summary: { key: "src/db.ts:query" },
|
|
167
|
-
result_summary: { error: "TypeError: connection is undefined" },
|
|
168
|
-
session_id: `session-${session}`,
|
|
169
|
-
}));
|
|
170
|
-
}
|
|
171
|
-
const path = createTempLedger(entries);
|
|
172
|
-
const result = detectCorrections(path, { min_confidence: 0.4 });
|
|
173
|
-
// Should be deduplicated to a single pattern with occurrences=2
|
|
174
|
-
const dbPatterns = result.filter((c) => c.entity_key === "src/db.ts:query");
|
|
175
|
-
expect(dbPatterns.length).toBe(1);
|
|
176
|
-
expect(dbPatterns[0]?.occurrences).toBe(2);
|
|
177
|
-
});
|
|
178
|
-
it("skips entries outside the correction window", () => {
|
|
179
|
-
const now = Date.now();
|
|
180
|
-
const entries = [
|
|
181
|
-
makeLedgerEntry({
|
|
182
|
-
tool: "get_function",
|
|
183
|
-
ts: new Date(now).toISOString(),
|
|
184
|
-
args_summary: { key: "src/slow.ts:fn" },
|
|
185
|
-
result_summary: { found: true },
|
|
186
|
-
}),
|
|
187
|
-
makeLedgerEntry({
|
|
188
|
-
tool: "sync_local_diff",
|
|
189
|
-
ts: new Date(now + 5000).toISOString(),
|
|
190
|
-
args_summary: {},
|
|
191
|
-
result_summary: {},
|
|
192
|
-
}),
|
|
193
|
-
// Re-query 5 minutes later — outside the 60s default window
|
|
194
|
-
makeLedgerEntry({
|
|
195
|
-
tool: "get_function",
|
|
196
|
-
ts: new Date(now + 300_000).toISOString(),
|
|
197
|
-
args_summary: { key: "src/slow.ts:fn" },
|
|
198
|
-
result_summary: { error: "TypeError" },
|
|
199
|
-
}),
|
|
200
|
-
];
|
|
201
|
-
const path = createTempLedger(entries);
|
|
202
|
-
const result = detectCorrections(path);
|
|
203
|
-
const slowPatterns = result.filter((c) => c.entity_key === "src/slow.ts:fn");
|
|
204
|
-
expect(slowPatterns.length).toBe(0);
|
|
205
|
-
});
|
|
206
|
-
it("classifies different error types correctly", () => {
|
|
207
|
-
const now = Date.now();
|
|
208
|
-
const makeErrorPair = (key, errorMessage, offset) => [
|
|
209
|
-
makeLedgerEntry({
|
|
210
|
-
tool: "get_function",
|
|
211
|
-
ts: new Date(now + offset).toISOString(),
|
|
212
|
-
args_summary: { key },
|
|
213
|
-
result_summary: { found: true },
|
|
214
|
-
}),
|
|
215
|
-
makeLedgerEntry({
|
|
216
|
-
tool: "sync_local_diff",
|
|
217
|
-
ts: new Date(now + offset + 3000).toISOString(),
|
|
218
|
-
args_summary: {},
|
|
219
|
-
result_summary: {},
|
|
220
|
-
}),
|
|
221
|
-
makeLedgerEntry({
|
|
222
|
-
tool: "get_function",
|
|
223
|
-
ts: new Date(now + offset + 10000).toISOString(),
|
|
224
|
-
args_summary: { key },
|
|
225
|
-
result_summary: { error: errorMessage },
|
|
226
|
-
}),
|
|
227
|
-
];
|
|
228
|
-
const entries = [
|
|
229
|
-
...makeErrorPair("a", "TypeError: x is undefined", 0),
|
|
230
|
-
...makeErrorPair("b", "Cannot find module './missing'", 100_000),
|
|
231
|
-
...makeErrorPair("c", "Property 'name' does not exist on type 'Foo'", 200_000),
|
|
232
|
-
...makeErrorPair("d", "Expected 2 arguments but got 1", 300_000),
|
|
233
|
-
];
|
|
234
|
-
const path = createTempLedger(entries);
|
|
235
|
-
const result = detectCorrections(path, { min_confidence: 0.4 });
|
|
236
|
-
const types = new Map(result.map((c) => [c.entity_key, c.error_type]));
|
|
237
|
-
expect(types.get("a")).toBe("type_error");
|
|
238
|
-
expect(types.get("b")).toBe("import_error");
|
|
239
|
-
expect(types.get("c")).toBe("missing_field");
|
|
240
|
-
expect(types.get("d")).toBe("wrong_argument");
|
|
241
|
-
});
|
|
242
|
-
it("respects since_days filter", () => {
|
|
243
|
-
const now = Date.now();
|
|
244
|
-
const entries = [
|
|
245
|
-
// 10 days ago — outside default 7-day window
|
|
246
|
-
makeLedgerEntry({
|
|
247
|
-
tool: "get_function",
|
|
248
|
-
ts: new Date(now - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
|
249
|
-
args_summary: { key: "src/old.ts:fn" },
|
|
250
|
-
result_summary: { found: true },
|
|
251
|
-
}),
|
|
252
|
-
makeLedgerEntry({
|
|
253
|
-
tool: "sync_local_diff",
|
|
254
|
-
ts: new Date(now - 10 * 24 * 60 * 60 * 1000 + 5000).toISOString(),
|
|
255
|
-
args_summary: {},
|
|
256
|
-
result_summary: {},
|
|
257
|
-
}),
|
|
258
|
-
makeLedgerEntry({
|
|
259
|
-
tool: "get_function",
|
|
260
|
-
ts: new Date(now - 10 * 24 * 60 * 60 * 1000 + 15000).toISOString(),
|
|
261
|
-
args_summary: { key: "src/old.ts:fn" },
|
|
262
|
-
result_summary: { error: "TypeError" },
|
|
263
|
-
}),
|
|
264
|
-
];
|
|
265
|
-
const path = createTempLedger(entries);
|
|
266
|
-
// Default 7 days — should find nothing
|
|
267
|
-
expect(detectCorrections(path, { min_confidence: 0.4 }).length).toBe(0);
|
|
268
|
-
// 30 days — should find the pattern
|
|
269
|
-
const result = detectCorrections(path, {
|
|
270
|
-
since_days: 30,
|
|
271
|
-
min_confidence: 0.4,
|
|
272
|
-
});
|
|
273
|
-
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
274
|
-
});
|
|
275
|
-
it("returns patterns sorted by confidence descending", () => {
|
|
276
|
-
const now = Date.now();
|
|
277
|
-
const entries = [];
|
|
278
|
-
// Pattern 1: high confidence (fix succeeded)
|
|
279
|
-
entries.push(makeLedgerEntry({
|
|
280
|
-
tool: "get_function",
|
|
281
|
-
ts: new Date(now).toISOString(),
|
|
282
|
-
args_summary: { key: "high" },
|
|
283
|
-
result_summary: { found: true },
|
|
284
|
-
session_id: "s1",
|
|
285
|
-
}), makeLedgerEntry({
|
|
286
|
-
tool: "sync_local_diff",
|
|
287
|
-
ts: new Date(now + 3000).toISOString(),
|
|
288
|
-
args_summary: {},
|
|
289
|
-
result_summary: {},
|
|
290
|
-
session_id: "s1",
|
|
291
|
-
}), makeLedgerEntry({
|
|
292
|
-
tool: "get_function",
|
|
293
|
-
ts: new Date(now + 10000).toISOString(),
|
|
294
|
-
args_summary: { key: "high" },
|
|
295
|
-
result_summary: { error: "TypeError" },
|
|
296
|
-
session_id: "s1",
|
|
297
|
-
}));
|
|
298
|
-
// Pattern 2: lower confidence (fix failed — another re-query)
|
|
299
|
-
entries.push(makeLedgerEntry({
|
|
300
|
-
tool: "get_function",
|
|
301
|
-
ts: new Date(now + 100_000).toISOString(),
|
|
302
|
-
args_summary: { key: "low" },
|
|
303
|
-
result_summary: { found: true },
|
|
304
|
-
session_id: "s2",
|
|
305
|
-
}), makeLedgerEntry({
|
|
306
|
-
tool: "sync_local_diff",
|
|
307
|
-
ts: new Date(now + 103_000).toISOString(),
|
|
308
|
-
args_summary: {},
|
|
309
|
-
result_summary: {},
|
|
310
|
-
session_id: "s2",
|
|
311
|
-
}), makeLedgerEntry({
|
|
312
|
-
tool: "get_function",
|
|
313
|
-
ts: new Date(now + 110_000).toISOString(),
|
|
314
|
-
args_summary: { key: "low" },
|
|
315
|
-
result_summary: { error: "TypeError" },
|
|
316
|
-
session_id: "s2",
|
|
317
|
-
}), makeLedgerEntry({
|
|
318
|
-
tool: "get_function",
|
|
319
|
-
ts: new Date(now + 115_000).toISOString(),
|
|
320
|
-
args_summary: { key: "low" },
|
|
321
|
-
result_summary: { error: "TypeError" },
|
|
322
|
-
session_id: "s2",
|
|
323
|
-
}));
|
|
324
|
-
const path = createTempLedger(entries);
|
|
325
|
-
const result = detectCorrections(path, { min_confidence: 0.4 });
|
|
326
|
-
if (result.length >= 2) {
|
|
327
|
-
expect(result[0]?.confidence).toBeGreaterThanOrEqual(result[1]?.confidence);
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
});
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for DM-5 autostart gating:
|
|
3
|
-
* - First install triggers platform auto-start
|
|
4
|
-
* - Second call is a no-op (sentinel blocks)
|
|
5
|
-
* - CI=true skips entirely
|
|
6
|
-
* - Platform dispatches to correct module
|
|
7
|
-
*/
|
|
8
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
|
-
// Mock the CI detection module
|
|
13
|
-
vi.mock("../daemon/detect-ci.js", () => ({
|
|
14
|
-
isCI: vi.fn(() => false),
|
|
15
|
-
resetCICache: vi.fn(),
|
|
16
|
-
}));
|
|
17
|
-
// Mock platform modules
|
|
18
|
-
vi.mock("../daemon/platform-macos.js", () => ({
|
|
19
|
-
installLaunchd: vi.fn(() => ({ installed: true, path: "/mock/plist" })),
|
|
20
|
-
uninstallLaunchd: vi.fn(() => ({ installed: false, path: "/mock/plist" })),
|
|
21
|
-
isLaunchdInstalled: vi.fn(() => false),
|
|
22
|
-
getLaunchdStatus: vi.fn(() => ({ loaded: false, plistExists: false })),
|
|
23
|
-
}));
|
|
24
|
-
vi.mock("../daemon/platform-linux.js", () => ({
|
|
25
|
-
installSystemd: vi.fn(() => ({ installed: true, path: "/mock/unit" })),
|
|
26
|
-
uninstallSystemd: vi.fn(() => ({ installed: false, path: "/mock/unit" })),
|
|
27
|
-
isSystemdInstalled: vi.fn(() => false),
|
|
28
|
-
getSystemdStatus: vi.fn(() => ({
|
|
29
|
-
unitExists: false,
|
|
30
|
-
active: false,
|
|
31
|
-
enabled: false,
|
|
32
|
-
})),
|
|
33
|
-
}));
|
|
34
|
-
vi.mock("../daemon/platform-windows.js", () => ({
|
|
35
|
-
installWindows: vi.fn(() => ({
|
|
36
|
-
installed: true,
|
|
37
|
-
path: "Scheduled Task: Unerr Daemon",
|
|
38
|
-
})),
|
|
39
|
-
uninstallWindows: vi.fn(() => ({ installed: false, path: "" })),
|
|
40
|
-
isWindowsInstalled: vi.fn(() => false),
|
|
41
|
-
}));
|
|
42
|
-
let testHome;
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
testHome = join(tmpdir(), `unerr-autostart-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
45
|
-
mkdirSync(join(testHome, ".unerr"), { recursive: true });
|
|
46
|
-
});
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
try {
|
|
49
|
-
rmSync(testHome, { recursive: true, force: true });
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
// cleanup best-effort
|
|
53
|
-
}
|
|
54
|
-
vi.restoreAllMocks();
|
|
55
|
-
});
|
|
56
|
-
describe("CI detection", () => {
|
|
57
|
-
it("detects standard CI env var", async () => {
|
|
58
|
-
vi.resetModules();
|
|
59
|
-
// Direct test of detect-ci (unmoacked)
|
|
60
|
-
vi.doUnmock("../daemon/detect-ci.js");
|
|
61
|
-
const { isCI, resetCICache } = await import("../daemon/detect-ci.js");
|
|
62
|
-
resetCICache();
|
|
63
|
-
const orig = process.env.CI;
|
|
64
|
-
process.env.CI = "true";
|
|
65
|
-
try {
|
|
66
|
-
expect(isCI()).toBe(true);
|
|
67
|
-
}
|
|
68
|
-
finally {
|
|
69
|
-
if (orig === undefined)
|
|
70
|
-
process.env.CI = undefined;
|
|
71
|
-
else
|
|
72
|
-
process.env.CI = orig;
|
|
73
|
-
resetCICache();
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
it("detects GitHub Actions", async () => {
|
|
77
|
-
vi.resetModules();
|
|
78
|
-
vi.doUnmock("../daemon/detect-ci.js");
|
|
79
|
-
const { isCI, resetCICache } = await import("../daemon/detect-ci.js");
|
|
80
|
-
resetCICache();
|
|
81
|
-
const orig = process.env.GITHUB_ACTIONS;
|
|
82
|
-
process.env.GITHUB_ACTIONS = "true";
|
|
83
|
-
try {
|
|
84
|
-
expect(isCI()).toBe(true);
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
if (orig === undefined)
|
|
88
|
-
process.env.GITHUB_ACTIONS = undefined;
|
|
89
|
-
else
|
|
90
|
-
process.env.GITHUB_ACTIONS = orig;
|
|
91
|
-
resetCICache();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
it("returns false when no CI indicators present", async () => {
|
|
95
|
-
vi.resetModules();
|
|
96
|
-
vi.doUnmock("../daemon/detect-ci.js");
|
|
97
|
-
const { isCI, resetCICache } = await import("../daemon/detect-ci.js");
|
|
98
|
-
resetCICache();
|
|
99
|
-
// Temporarily clear all CI-related env vars
|
|
100
|
-
const ciVars = [
|
|
101
|
-
"CI",
|
|
102
|
-
"CONTINUOUS_INTEGRATION",
|
|
103
|
-
"BUILD_NUMBER",
|
|
104
|
-
"GITHUB_ACTIONS",
|
|
105
|
-
"GITLAB_CI",
|
|
106
|
-
"CIRCLECI",
|
|
107
|
-
"BUILDKITE",
|
|
108
|
-
"JENKINS_URL",
|
|
109
|
-
"TRAVIS",
|
|
110
|
-
"CODEBUILD_BUILD_ID",
|
|
111
|
-
"TF_BUILD",
|
|
112
|
-
"BITBUCKET_PIPELINE_UUID",
|
|
113
|
-
"DRONE",
|
|
114
|
-
"WOODPECKER_CI",
|
|
115
|
-
"TEAMCITY_VERSION",
|
|
116
|
-
"HEROKU_TEST_RUN_ID",
|
|
117
|
-
];
|
|
118
|
-
const saved = {};
|
|
119
|
-
for (const v of ciVars) {
|
|
120
|
-
saved[v] = process.env[v];
|
|
121
|
-
delete process.env[v];
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
expect(isCI()).toBe(false);
|
|
125
|
-
}
|
|
126
|
-
finally {
|
|
127
|
-
for (const v of ciVars) {
|
|
128
|
-
if (saved[v] !== undefined)
|
|
129
|
-
process.env[v] = saved[v];
|
|
130
|
-
else
|
|
131
|
-
delete process.env[v];
|
|
132
|
-
}
|
|
133
|
-
resetCICache();
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
it("caches result across calls", async () => {
|
|
137
|
-
vi.resetModules();
|
|
138
|
-
vi.doUnmock("../daemon/detect-ci.js");
|
|
139
|
-
const { isCI, resetCICache } = await import("../daemon/detect-ci.js");
|
|
140
|
-
resetCICache();
|
|
141
|
-
const ciVars = [
|
|
142
|
-
"CI",
|
|
143
|
-
"GITHUB_ACTIONS",
|
|
144
|
-
"GITLAB_CI",
|
|
145
|
-
"CIRCLECI",
|
|
146
|
-
"BUILDKITE",
|
|
147
|
-
"JENKINS_URL",
|
|
148
|
-
"TRAVIS",
|
|
149
|
-
"CODEBUILD_BUILD_ID",
|
|
150
|
-
"TF_BUILD",
|
|
151
|
-
"BITBUCKET_PIPELINE_UUID",
|
|
152
|
-
"DRONE",
|
|
153
|
-
"WOODPECKER_CI",
|
|
154
|
-
"TEAMCITY_VERSION",
|
|
155
|
-
"HEROKU_TEST_RUN_ID",
|
|
156
|
-
"CONTINUOUS_INTEGRATION",
|
|
157
|
-
"BUILD_NUMBER",
|
|
158
|
-
];
|
|
159
|
-
const saved = {};
|
|
160
|
-
for (const v of ciVars) {
|
|
161
|
-
saved[v] = process.env[v];
|
|
162
|
-
delete process.env[v];
|
|
163
|
-
}
|
|
164
|
-
try {
|
|
165
|
-
const first = isCI();
|
|
166
|
-
// Set CI=true after first call — should still return cached false
|
|
167
|
-
process.env.CI = "true";
|
|
168
|
-
expect(isCI()).toBe(first);
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
for (const v of ciVars) {
|
|
172
|
-
if (saved[v] !== undefined)
|
|
173
|
-
process.env[v] = saved[v];
|
|
174
|
-
else
|
|
175
|
-
delete process.env[v];
|
|
176
|
-
}
|
|
177
|
-
resetCICache();
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
describe("autoInstallIfNeeded", () => {
|
|
182
|
-
it("skips if sentinel already exists", async () => {
|
|
183
|
-
const { autoInstallIfNeeded } = await import("../daemon/autostart.js");
|
|
184
|
-
// Monkey-patch homedir for this module — use the real module path approach
|
|
185
|
-
// Instead, we just test the sentinel logic directly
|
|
186
|
-
// Create sentinel
|
|
187
|
-
const { isAutostartInstalled } = await import("../daemon/autostart.js");
|
|
188
|
-
// Since we can't easily mock homedir in ESM, just test the logic path
|
|
189
|
-
expect(typeof isAutostartInstalled).toBe("function");
|
|
190
|
-
});
|
|
191
|
-
it("skips in CI environment", async () => {
|
|
192
|
-
// The top-level vi.mock already mocks isCI — we just need to change its return
|
|
193
|
-
const detectCi = await import("../daemon/detect-ci.js");
|
|
194
|
-
vi.spyOn(detectCi, "isCI").mockReturnValue(true);
|
|
195
|
-
// Re-import to pick up mock — autoInstallIfNeeded calls isCI internally
|
|
196
|
-
const { autoInstallIfNeeded } = await import("../daemon/autostart.js");
|
|
197
|
-
const result = await autoInstallIfNeeded();
|
|
198
|
-
// Will be null from CI skip or sentinel skip
|
|
199
|
-
expect(result).toBeNull();
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
describe("platform dispatch", () => {
|
|
203
|
-
it("installForCurrentPlatform returns PlatformInstallResult", async () => {
|
|
204
|
-
const { installForCurrentPlatform } = await import("../daemon/autostart.js");
|
|
205
|
-
const result = await installForCurrentPlatform();
|
|
206
|
-
expect(result).toHaveProperty("installed");
|
|
207
|
-
expect(result).toHaveProperty("path");
|
|
208
|
-
});
|
|
209
|
-
it("uninstallForCurrentPlatform returns PlatformInstallResult", async () => {
|
|
210
|
-
const { uninstallForCurrentPlatform } = await import("../daemon/autostart.js");
|
|
211
|
-
const result = await uninstallForCurrentPlatform();
|
|
212
|
-
expect(result).toHaveProperty("installed");
|
|
213
|
-
expect(result).toHaveProperty("path");
|
|
214
|
-
});
|
|
215
|
-
it("getAutostartStatus returns structured info", async () => {
|
|
216
|
-
const { getAutostartStatus } = await import("../daemon/autostart.js");
|
|
217
|
-
const status = await getAutostartStatus();
|
|
218
|
-
expect(status).toHaveProperty("platform");
|
|
219
|
-
expect(status).toHaveProperty("installed");
|
|
220
|
-
expect(status).toHaveProperty("sentinelExists");
|
|
221
|
-
expect(status).toHaveProperty("details");
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
describe("detect-ci env var coverage", () => {
|
|
225
|
-
const envTests = [
|
|
226
|
-
"CI",
|
|
227
|
-
"GITLAB_CI",
|
|
228
|
-
"CIRCLECI",
|
|
229
|
-
"BUILDKITE",
|
|
230
|
-
"JENKINS_URL",
|
|
231
|
-
"TRAVIS",
|
|
232
|
-
"CODEBUILD_BUILD_ID",
|
|
233
|
-
"TF_BUILD",
|
|
234
|
-
"BITBUCKET_PIPELINE_UUID",
|
|
235
|
-
"DRONE",
|
|
236
|
-
"WOODPECKER_CI",
|
|
237
|
-
"TEAMCITY_VERSION",
|
|
238
|
-
];
|
|
239
|
-
for (const envVar of envTests) {
|
|
240
|
-
it(`detects ${envVar}`, async () => {
|
|
241
|
-
vi.resetModules();
|
|
242
|
-
vi.doUnmock("../daemon/detect-ci.js");
|
|
243
|
-
const { isCI, resetCICache } = await import("../daemon/detect-ci.js");
|
|
244
|
-
resetCICache();
|
|
245
|
-
const ciVars = [
|
|
246
|
-
"CI",
|
|
247
|
-
"CONTINUOUS_INTEGRATION",
|
|
248
|
-
"BUILD_NUMBER",
|
|
249
|
-
"GITHUB_ACTIONS",
|
|
250
|
-
"GITLAB_CI",
|
|
251
|
-
"CIRCLECI",
|
|
252
|
-
"BUILDKITE",
|
|
253
|
-
"JENKINS_URL",
|
|
254
|
-
"TRAVIS",
|
|
255
|
-
"CODEBUILD_BUILD_ID",
|
|
256
|
-
"TF_BUILD",
|
|
257
|
-
"BITBUCKET_PIPELINE_UUID",
|
|
258
|
-
"DRONE",
|
|
259
|
-
"WOODPECKER_CI",
|
|
260
|
-
"TEAMCITY_VERSION",
|
|
261
|
-
"HEROKU_TEST_RUN_ID",
|
|
262
|
-
];
|
|
263
|
-
const saved = {};
|
|
264
|
-
for (const v of ciVars) {
|
|
265
|
-
saved[v] = process.env[v];
|
|
266
|
-
delete process.env[v];
|
|
267
|
-
}
|
|
268
|
-
process.env[envVar] = "true";
|
|
269
|
-
try {
|
|
270
|
-
expect(isCI()).toBe(true);
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
for (const v of ciVars) {
|
|
274
|
-
if (saved[v] !== undefined)
|
|
275
|
-
process.env[v] = saved[v];
|
|
276
|
-
else
|
|
277
|
-
delete process.env[v];
|
|
278
|
-
}
|
|
279
|
-
resetCICache();
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
});
|