@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,141 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { normalizeAgentName } from "../config/agent-registry.js";
|
|
6
|
-
import { mergePreToolUseBashHook, removePreToolUseBashHook, } from "../config/claude-settings-hooks.js";
|
|
7
|
-
import { removeInstructionSection, writeInstructionFile, } from "../config/instruction-writer.js";
|
|
8
|
-
import { removeMcpConfig, writeMcpConfig, } from "../config/mcp-config-writer.js";
|
|
9
|
-
import { removeInstalledSkills } from "../skills/resolver.js";
|
|
10
|
-
describe("uninstall", () => {
|
|
11
|
-
let tmpDir;
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
tmpDir = join(tmpdir(), `unerr-uninstall-test-${Date.now()}`);
|
|
14
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
15
|
-
});
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
18
|
-
});
|
|
19
|
-
describe("normalizeAgentName", () => {
|
|
20
|
-
it("resolves 'claude' to 'claude-code'", () => {
|
|
21
|
-
expect(normalizeAgentName("claude")).toBe("claude-code");
|
|
22
|
-
});
|
|
23
|
-
it("resolves 'Claude' case-insensitively", () => {
|
|
24
|
-
expect(normalizeAgentName("Claude")).toBe("claude-code");
|
|
25
|
-
});
|
|
26
|
-
it("resolves 'gemini' to 'gemini-cli'", () => {
|
|
27
|
-
expect(normalizeAgentName("gemini")).toBe("gemini-cli");
|
|
28
|
-
});
|
|
29
|
-
it("passes through unknown names unchanged", () => {
|
|
30
|
-
expect(normalizeAgentName("cursor")).toBe("cursor");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
describe("removeInstalledSkills", () => {
|
|
34
|
-
it("removes flat skill files for cursor", () => {
|
|
35
|
-
const rulesDir = join(tmpDir, ".cursor", "rules");
|
|
36
|
-
mkdirSync(rulesDir, { recursive: true });
|
|
37
|
-
writeFileSync(join(rulesDir, "unerr-graph-first.mdc"), "content");
|
|
38
|
-
writeFileSync(join(rulesDir, "unerr-search.mdc"), "content");
|
|
39
|
-
writeFileSync(join(rulesDir, "other-rule.mdc"), "should stay");
|
|
40
|
-
const removed = removeInstalledSkills("cursor", tmpDir);
|
|
41
|
-
expect(removed).toBe(2);
|
|
42
|
-
expect(existsSync(join(rulesDir, "unerr-graph-first.mdc"))).toBe(false);
|
|
43
|
-
expect(existsSync(join(rulesDir, "unerr-search.mdc"))).toBe(false);
|
|
44
|
-
expect(existsSync(join(rulesDir, "other-rule.mdc"))).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
it("removes directory-per-skill for claude-code", () => {
|
|
47
|
-
const skillsDir = join(tmpDir, ".claude", "skills");
|
|
48
|
-
const skillDir = join(skillsDir, "unerr-graph-first");
|
|
49
|
-
mkdirSync(skillDir, { recursive: true });
|
|
50
|
-
writeFileSync(join(skillDir, "SKILL.md"), "content");
|
|
51
|
-
const removed = removeInstalledSkills("claude-code", tmpDir);
|
|
52
|
-
expect(removed).toBe(1);
|
|
53
|
-
expect(existsSync(skillDir)).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
it("returns 0 when no skills exist", () => {
|
|
56
|
-
const removed = removeInstalledSkills("cursor", tmpDir);
|
|
57
|
-
expect(removed).toBe(0);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
describe("removePreToolUseBashHook", () => {
|
|
61
|
-
it("removes unerr hook from settings.json", () => {
|
|
62
|
-
// First merge it in
|
|
63
|
-
mergePreToolUseBashHook(tmpDir);
|
|
64
|
-
const settingsPath = join(tmpDir, ".claude", "settings.json");
|
|
65
|
-
expect(existsSync(settingsPath)).toBe(true);
|
|
66
|
-
const before = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
67
|
-
expect(before.hooks.PreToolUse).toHaveLength(6); // Bash, Read, Grep, Glob, Write, Edit
|
|
68
|
-
// Now remove
|
|
69
|
-
const removed = removePreToolUseBashHook(tmpDir);
|
|
70
|
-
expect(removed).toBe(true);
|
|
71
|
-
const after = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
72
|
-
// PreToolUse should be cleaned up
|
|
73
|
-
expect(after.hooks?.PreToolUse).toBeUndefined();
|
|
74
|
-
});
|
|
75
|
-
it("preserves other hooks when removing unerr hook", () => {
|
|
76
|
-
const dir = join(tmpDir, ".claude");
|
|
77
|
-
mkdirSync(dir, { recursive: true });
|
|
78
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({
|
|
79
|
-
hooks: {
|
|
80
|
-
PreToolUse: [
|
|
81
|
-
{
|
|
82
|
-
matcher: "Bash",
|
|
83
|
-
hooks: [{ type: "command", command: "other-tool" }],
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
matcher: "Bash",
|
|
87
|
-
hooks: [{ type: "command", command: "unerr hook pre-bash" }],
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
PostToolUse: [
|
|
91
|
-
{
|
|
92
|
-
matcher: "Write",
|
|
93
|
-
hooks: [{ type: "command", command: "lint" }],
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
|
-
}));
|
|
98
|
-
const removed = removePreToolUseBashHook(tmpDir);
|
|
99
|
-
expect(removed).toBe(true);
|
|
100
|
-
const after = JSON.parse(readFileSync(join(dir, "settings.json"), "utf-8"));
|
|
101
|
-
expect(after.hooks.PreToolUse).toHaveLength(1);
|
|
102
|
-
expect(after.hooks.PreToolUse[0].hooks[0].command).toBe("other-tool");
|
|
103
|
-
expect(after.hooks.PostToolUse).toHaveLength(1);
|
|
104
|
-
});
|
|
105
|
-
it("returns false when no settings.json exists", () => {
|
|
106
|
-
expect(removePreToolUseBashHook(tmpDir)).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
it("returns false when no unerr hook present", () => {
|
|
109
|
-
const dir = join(tmpDir, ".claude");
|
|
110
|
-
mkdirSync(dir, { recursive: true });
|
|
111
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({ hooks: { PreToolUse: [] } }));
|
|
112
|
-
expect(removePreToolUseBashHook(tmpDir)).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
describe("per-agent uninstall symmetry", () => {
|
|
116
|
-
it("removeMcpConfig reverses writeMcpConfig for cursor", () => {
|
|
117
|
-
const result = writeMcpConfig(tmpDir, "cursor");
|
|
118
|
-
expect(existsSync(result.path)).toBe(true);
|
|
119
|
-
const removed = removeMcpConfig(tmpDir, "cursor");
|
|
120
|
-
expect(removed).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
it("removeInstructionSection reverses writeInstructionFile", () => {
|
|
123
|
-
writeInstructionFile(tmpDir, "claude-code");
|
|
124
|
-
const claudeMd = join(tmpDir, "CLAUDE.md");
|
|
125
|
-
expect(existsSync(claudeMd)).toBe(true);
|
|
126
|
-
const removed = removeInstructionSection(tmpDir, "claude-code");
|
|
127
|
-
expect(removed).toBe(true);
|
|
128
|
-
// File should be deleted since it only had sentinel content
|
|
129
|
-
expect(existsSync(claudeMd)).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
it("uninstall is idempotent — second call returns false/0", () => {
|
|
132
|
-
// Install then uninstall
|
|
133
|
-
writeMcpConfig(tmpDir, "cursor");
|
|
134
|
-
removeMcpConfig(tmpDir, "cursor");
|
|
135
|
-
// Second uninstall should be no-ops
|
|
136
|
-
expect(removeMcpConfig(tmpDir, "cursor")).toBe(false);
|
|
137
|
-
expect(removeInstalledSkills("cursor", tmpDir)).toBe(0);
|
|
138
|
-
expect(removeInstructionSection(tmpDir, "cursor")).toBe(false);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
});
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for DM-5 warm-start policy:
|
|
3
|
-
* - Candidate selection respects autostart, budget, idle days
|
|
4
|
-
* - MRU ordering (eager first, then by lastActivity)
|
|
5
|
-
* - Budget enforcement (only top N warmed)
|
|
6
|
-
* - Battery/load abort
|
|
7
|
-
* - Budget=0 disables entirely
|
|
8
|
-
*/
|
|
9
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
10
|
-
import { tmpdir } from "node:os";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
13
|
-
import { selectCandidates, } from "../daemon/warm-start.js";
|
|
14
|
-
function makeRepo(name, opts = {}) {
|
|
15
|
-
const path = opts.path ??
|
|
16
|
-
join(tmpdir(), `warmtest-${name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
17
|
-
if (opts.exists !== false) {
|
|
18
|
-
mkdirSync(path, { recursive: true });
|
|
19
|
-
}
|
|
20
|
-
const settings = {};
|
|
21
|
-
if (opts.autostart)
|
|
22
|
-
settings.autostart = opts.autostart;
|
|
23
|
-
return {
|
|
24
|
-
path,
|
|
25
|
-
addedAt: new Date().toISOString(),
|
|
26
|
-
lastStarted: null,
|
|
27
|
-
lastActivity: opts.lastActivity?.toISOString() ?? null,
|
|
28
|
-
idleTimeout: 1800,
|
|
29
|
-
label: name,
|
|
30
|
-
settings,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
const tempPaths = [];
|
|
34
|
-
function trackedRepo(name, opts = {}) {
|
|
35
|
-
const repo = makeRepo(name, opts);
|
|
36
|
-
tempPaths.push(repo.path);
|
|
37
|
-
return repo;
|
|
38
|
-
}
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
for (const p of tempPaths) {
|
|
41
|
-
try {
|
|
42
|
-
rmSync(p, { recursive: true, force: true });
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
/* ok */
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
tempPaths.length = 0;
|
|
49
|
-
});
|
|
50
|
-
const defaultConfig = {
|
|
51
|
-
warmStartBudget: 3,
|
|
52
|
-
warmStartIdleDays: 14,
|
|
53
|
-
warmStartDelayMs: 0,
|
|
54
|
-
};
|
|
55
|
-
describe("selectCandidates", () => {
|
|
56
|
-
it("selects MRU repos up to budget", () => {
|
|
57
|
-
const repos = [
|
|
58
|
-
trackedRepo("repo1", { lastActivity: new Date("2026-05-14T10:00:00Z") }),
|
|
59
|
-
trackedRepo("repo2", { lastActivity: new Date("2026-05-13T10:00:00Z") }),
|
|
60
|
-
trackedRepo("repo3", { lastActivity: new Date("2026-05-12T10:00:00Z") }),
|
|
61
|
-
trackedRepo("repo4", { lastActivity: new Date("2026-05-11T10:00:00Z") }),
|
|
62
|
-
trackedRepo("repo5", { lastActivity: new Date("2026-05-10T10:00:00Z") }),
|
|
63
|
-
];
|
|
64
|
-
const { candidates, skipped } = selectCandidates(repos, {
|
|
65
|
-
...defaultConfig,
|
|
66
|
-
warmStartBudget: 3,
|
|
67
|
-
});
|
|
68
|
-
// All 5 are candidates (within idle window), but budget limits to 3
|
|
69
|
-
expect(candidates.length).toBe(5); // selectCandidates returns all eligible; budget is enforced during run
|
|
70
|
-
expect(candidates[0].entry.label).toBe("repo1");
|
|
71
|
-
expect(candidates[1].entry.label).toBe("repo2");
|
|
72
|
-
expect(candidates[2].entry.label).toBe("repo3");
|
|
73
|
-
});
|
|
74
|
-
it("excludes repos with autostart=never", () => {
|
|
75
|
-
const repos = [
|
|
76
|
-
trackedRepo("active", { lastActivity: new Date("2026-05-14T10:00:00Z") }),
|
|
77
|
-
trackedRepo("never", {
|
|
78
|
-
lastActivity: new Date("2026-05-14T10:00:00Z"),
|
|
79
|
-
autostart: "never",
|
|
80
|
-
}),
|
|
81
|
-
];
|
|
82
|
-
const { candidates, skipped } = selectCandidates(repos, defaultConfig);
|
|
83
|
-
expect(candidates.length).toBe(1);
|
|
84
|
-
expect(candidates[0].entry.label).toBe("active");
|
|
85
|
-
expect(skipped.length).toBe(1);
|
|
86
|
-
expect(skipped[0].reason).toBe("autostart=never");
|
|
87
|
-
});
|
|
88
|
-
it("excludes repos inactive beyond idle days cutoff", () => {
|
|
89
|
-
const repos = [
|
|
90
|
-
trackedRepo("recent", { lastActivity: new Date("2026-05-14T10:00:00Z") }),
|
|
91
|
-
trackedRepo("old", { lastActivity: new Date("2026-01-01T10:00:00Z") }),
|
|
92
|
-
];
|
|
93
|
-
const { candidates, skipped } = selectCandidates(repos, {
|
|
94
|
-
...defaultConfig,
|
|
95
|
-
warmStartIdleDays: 14,
|
|
96
|
-
});
|
|
97
|
-
expect(candidates.length).toBe(1);
|
|
98
|
-
expect(candidates[0].entry.label).toBe("recent");
|
|
99
|
-
expect(skipped.length).toBe(1);
|
|
100
|
-
expect(skipped[0].reason).toContain("inactive");
|
|
101
|
-
});
|
|
102
|
-
it("eager repos are prioritized over auto repos", () => {
|
|
103
|
-
const repos = [
|
|
104
|
-
trackedRepo("auto-recent", {
|
|
105
|
-
lastActivity: new Date("2026-05-14T10:00:00Z"),
|
|
106
|
-
autostart: "auto",
|
|
107
|
-
}),
|
|
108
|
-
trackedRepo("eager-old", {
|
|
109
|
-
lastActivity: new Date("2026-05-10T10:00:00Z"),
|
|
110
|
-
autostart: "eager",
|
|
111
|
-
}),
|
|
112
|
-
];
|
|
113
|
-
const { candidates } = selectCandidates(repos, defaultConfig);
|
|
114
|
-
expect(candidates[0].entry.label).toBe("eager-old");
|
|
115
|
-
expect(candidates[1].entry.label).toBe("auto-recent");
|
|
116
|
-
});
|
|
117
|
-
it("skips repos whose directory does not exist", () => {
|
|
118
|
-
const repos = [
|
|
119
|
-
trackedRepo("good", { lastActivity: new Date("2026-05-14T10:00:00Z") }),
|
|
120
|
-
trackedRepo("gone", {
|
|
121
|
-
lastActivity: new Date("2026-05-14T10:00:00Z"),
|
|
122
|
-
exists: false,
|
|
123
|
-
}),
|
|
124
|
-
];
|
|
125
|
-
const { candidates, skipped } = selectCandidates(repos, defaultConfig);
|
|
126
|
-
expect(candidates.length).toBe(1);
|
|
127
|
-
expect(skipped.length).toBe(1);
|
|
128
|
-
expect(skipped[0].reason).toBe("directory not found");
|
|
129
|
-
});
|
|
130
|
-
it("repos without lastActivity are included (never started)", () => {
|
|
131
|
-
const repos = [trackedRepo("new-repo", { lastActivity: null })];
|
|
132
|
-
const { candidates } = selectCandidates(repos, defaultConfig);
|
|
133
|
-
expect(candidates.length).toBe(1);
|
|
134
|
-
});
|
|
135
|
-
it("eager repos bypass idle cutoff even if old", () => {
|
|
136
|
-
const repos = [
|
|
137
|
-
trackedRepo("eager-old", {
|
|
138
|
-
lastActivity: new Date("2025-01-01T10:00:00Z"),
|
|
139
|
-
autostart: "eager",
|
|
140
|
-
}),
|
|
141
|
-
];
|
|
142
|
-
const { candidates } = selectCandidates(repos, {
|
|
143
|
-
...defaultConfig,
|
|
144
|
-
warmStartIdleDays: 7,
|
|
145
|
-
});
|
|
146
|
-
// Eager repos are never filtered by idle cutoff
|
|
147
|
-
expect(candidates.length).toBe(1);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
describe("warm-start budget enforcement", () => {
|
|
151
|
-
it("budget=0 returns empty results", async () => {
|
|
152
|
-
vi.mock("../daemon/detect-ci.js", () => ({
|
|
153
|
-
isCI: vi.fn(() => false),
|
|
154
|
-
resetCICache: vi.fn(),
|
|
155
|
-
}));
|
|
156
|
-
const { loadWarmStartConfig } = await import("../daemon/warm-start.js");
|
|
157
|
-
const config = loadWarmStartConfig();
|
|
158
|
-
// Test the config loading works
|
|
159
|
-
expect(typeof config.warmStartBudget).toBe("number");
|
|
160
|
-
});
|
|
161
|
-
it("table-driven: 20 repos with budget=3 selects exactly top 3", () => {
|
|
162
|
-
const repos = [];
|
|
163
|
-
for (let i = 0; i < 20; i++) {
|
|
164
|
-
repos.push(trackedRepo(`repo-${i}`, {
|
|
165
|
-
lastActivity: new Date(Date.now() - i * 86400000),
|
|
166
|
-
autostart: i < 2 ? "eager" : i >= 18 ? "never" : "auto",
|
|
167
|
-
}));
|
|
168
|
-
}
|
|
169
|
-
const { candidates, skipped } = selectCandidates(repos, {
|
|
170
|
-
...defaultConfig,
|
|
171
|
-
warmStartBudget: 3,
|
|
172
|
-
});
|
|
173
|
-
// 2 never repos skipped
|
|
174
|
-
expect(skipped.filter((s) => s.reason === "autostart=never").length).toBe(2);
|
|
175
|
-
// Eager repos should be first in candidates
|
|
176
|
-
expect(candidates[0].entry.settings.autostart).toBe("eager");
|
|
177
|
-
expect(candidates[1].entry.settings.autostart).toBe("eager");
|
|
178
|
-
});
|
|
179
|
-
it("table-driven: 20 repos with budget=5 selects top 5", () => {
|
|
180
|
-
const repos = [];
|
|
181
|
-
for (let i = 0; i < 20; i++) {
|
|
182
|
-
repos.push(trackedRepo(`repo-${i}`, {
|
|
183
|
-
lastActivity: new Date(Date.now() - i * 86400000),
|
|
184
|
-
autostart: i >= 18 ? "never" : "auto",
|
|
185
|
-
}));
|
|
186
|
-
}
|
|
187
|
-
const config = {
|
|
188
|
-
...defaultConfig,
|
|
189
|
-
warmStartBudget: 5,
|
|
190
|
-
warmStartIdleDays: 30,
|
|
191
|
-
};
|
|
192
|
-
const { candidates } = selectCandidates(repos, config);
|
|
193
|
-
// All non-never repos within 30 days are candidates
|
|
194
|
-
expect(candidates.length).toBeLessThanOrEqual(18);
|
|
195
|
-
// First 5 would be the ones the scheduler picks
|
|
196
|
-
const topFive = candidates.slice(0, 5);
|
|
197
|
-
expect(topFive.length).toBe(5);
|
|
198
|
-
});
|
|
199
|
-
it("table-driven: budget=0 means selectCandidates still works but nothing warmed", () => {
|
|
200
|
-
const repos = [trackedRepo("repo-a", { lastActivity: new Date() })];
|
|
201
|
-
const config = { ...defaultConfig, warmStartBudget: 0 };
|
|
202
|
-
// With budget=0, the scheduler itself returns early before calling selectCandidates
|
|
203
|
-
// But selectCandidates itself still returns valid results
|
|
204
|
-
const { candidates } = selectCandidates(repos, config);
|
|
205
|
-
expect(candidates.length).toBe(1);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
describe("system health integration", () => {
|
|
209
|
-
it("onBattery returns boolean", async () => {
|
|
210
|
-
const { onBattery, resetHealthCaches } = await import("../daemon/system-health.js");
|
|
211
|
-
resetHealthCaches();
|
|
212
|
-
const result = onBattery();
|
|
213
|
-
expect(typeof result).toBe("boolean");
|
|
214
|
-
});
|
|
215
|
-
it("loadAverage1 returns non-negative number", async () => {
|
|
216
|
-
const { loadAverage1, resetHealthCaches } = await import("../daemon/system-health.js");
|
|
217
|
-
resetHealthCaches();
|
|
218
|
-
const result = loadAverage1();
|
|
219
|
-
expect(typeof result).toBe("number");
|
|
220
|
-
expect(result).toBeGreaterThanOrEqual(0);
|
|
221
|
-
});
|
|
222
|
-
it("caches are reset correctly", async () => {
|
|
223
|
-
const { onBattery, loadAverage1, resetHealthCaches } = await import("../daemon/system-health.js");
|
|
224
|
-
const b1 = onBattery();
|
|
225
|
-
const l1 = loadAverage1();
|
|
226
|
-
resetHealthCaches();
|
|
227
|
-
// After reset, should still return valid values
|
|
228
|
-
const b2 = onBattery();
|
|
229
|
-
const l2 = loadAverage1();
|
|
230
|
-
expect(typeof b2).toBe("boolean");
|
|
231
|
-
expect(typeof l2).toBe("number");
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
describe("warm-start config", () => {
|
|
235
|
-
it("loadWarmStartConfig returns defaults when no config exists", async () => {
|
|
236
|
-
const { loadWarmStartConfig } = await import("../daemon/warm-start.js");
|
|
237
|
-
const config = loadWarmStartConfig();
|
|
238
|
-
expect(config.warmStartBudget).toBe(3);
|
|
239
|
-
expect(config.warmStartIdleDays).toBe(14);
|
|
240
|
-
expect(config.warmStartDelayMs).toBe(30_000);
|
|
241
|
-
});
|
|
242
|
-
it("saveWarmStartConfig persists values", async () => {
|
|
243
|
-
const { saveWarmStartConfig, loadWarmStartConfig } = await import("../daemon/warm-start.js");
|
|
244
|
-
// This test modifies the real config file — we accept that as it's using the real ~/.unerr/config.json
|
|
245
|
-
// In a real CI environment, this would need a mock
|
|
246
|
-
expect(typeof saveWarmStartConfig).toBe("function");
|
|
247
|
-
expect(typeof loadWarmStartConfig).toBe("function");
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
describe("WarmStartEvent types", () => {
|
|
251
|
-
it("events have correct shape", () => {
|
|
252
|
-
const event = {
|
|
253
|
-
type: "warm_start",
|
|
254
|
-
repo: "/tmp/test",
|
|
255
|
-
label: "test",
|
|
256
|
-
status: "started",
|
|
257
|
-
ms: 1500,
|
|
258
|
-
};
|
|
259
|
-
expect(event.type).toBe("warm_start");
|
|
260
|
-
expect(event.status).toBe("started");
|
|
261
|
-
const skipped = {
|
|
262
|
-
type: "warm_start",
|
|
263
|
-
repo: "/tmp/test2",
|
|
264
|
-
label: "test2",
|
|
265
|
-
status: "skipped",
|
|
266
|
-
ms: 0,
|
|
267
|
-
reason: "autostart=never",
|
|
268
|
-
};
|
|
269
|
-
expect(skipped.reason).toBe("autostart=never");
|
|
270
|
-
});
|
|
271
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Verifies the wire-cap nudge interpolation is concrete (numeric values, not
|
|
3
|
-
* literal `N`) and context-aware (knows whether entity/limit/token_budget
|
|
4
|
-
* were already set).
|
|
5
|
-
*
|
|
6
|
-
* Before this change, the same generic "narrow with limit:N/entity:<name> or
|
|
7
|
-
* token_budget:N" hint reappeared on every retry — even when the caller had
|
|
8
|
-
* already passed entity: or token_budget: — producing a retry loop.
|
|
9
|
-
*/
|
|
10
|
-
import { describe, expect, it } from "vitest";
|
|
11
|
-
import { applyWireCap } from "../proxy/wire-cap.js";
|
|
12
|
-
function bigString(bytes) {
|
|
13
|
-
return "x".repeat(bytes);
|
|
14
|
-
}
|
|
15
|
-
describe("wire-cap too_large nudge — concrete & context-aware", () => {
|
|
16
|
-
it("emits a numeric suggested_token_budget in the body", () => {
|
|
17
|
-
const oversized = bigString(20_000);
|
|
18
|
-
const { body, pageHint } = applyWireCap("file_read", oversized, {});
|
|
19
|
-
const obj = body;
|
|
20
|
-
expect(obj.status).toBe("too_large");
|
|
21
|
-
expect(typeof obj.suggested_token_budget).toBe("number");
|
|
22
|
-
expect(obj.needed_tokens).toBeGreaterThan(0);
|
|
23
|
-
expect(pageHint).toMatch(/token_budget:\d+/);
|
|
24
|
-
expect(pageHint).not.toMatch(/token_budget:N/);
|
|
25
|
-
});
|
|
26
|
-
it("uses `entity_too_large` reason when caller already passed entity", () => {
|
|
27
|
-
const oversized = bigString(20_000);
|
|
28
|
-
const { body, pageHint } = applyWireCap("file_read", oversized, {
|
|
29
|
-
entity: "indexLocalProject",
|
|
30
|
-
});
|
|
31
|
-
const obj = body;
|
|
32
|
-
expect(obj.reason).toBe("entity_too_large");
|
|
33
|
-
expect(obj.entity).toBe("indexLocalProject");
|
|
34
|
-
expect(pageHint).toContain("offset/limit");
|
|
35
|
-
// Critical: must NOT tell caller to "narrow with entity:" — they did.
|
|
36
|
-
expect(pageHint).not.toContain("entity:<name>");
|
|
37
|
-
});
|
|
38
|
-
it("echoes the requested_token_budget when caller already set one", () => {
|
|
39
|
-
const oversized = bigString(40_000);
|
|
40
|
-
const { body } = applyWireCap("file_read", oversized, {
|
|
41
|
-
token_budget: 2000,
|
|
42
|
-
});
|
|
43
|
-
const obj = body;
|
|
44
|
-
expect(obj.requested_token_budget).toBe(2000);
|
|
45
|
-
expect(obj.suggested_token_budget).toBeGreaterThan(2000);
|
|
46
|
-
});
|
|
47
|
-
it("uses `page_too_large` and shrinks the limit when paginated", () => {
|
|
48
|
-
const oversized = bigString(20_000);
|
|
49
|
-
const { body, pageHint } = applyWireCap("search_code", oversized, {
|
|
50
|
-
limit: 100,
|
|
51
|
-
});
|
|
52
|
-
const obj = body;
|
|
53
|
-
expect(obj.reason).toBe("page_too_large");
|
|
54
|
-
expect(pageHint).toMatch(/limit:\d+/);
|
|
55
|
-
expect(pageHint).not.toMatch(/limit:N/);
|
|
56
|
-
});
|
|
57
|
-
it("uses `narrow_required` when token_budget was lifted and still overflowed", () => {
|
|
58
|
-
// 16384 / 4 = 4096 — bump well above HARD_BYTE_CAP so a token_budget of
|
|
59
|
-
// 5000 lifts the cap to 20000 bytes, but the payload is bigger still.
|
|
60
|
-
const oversized = bigString(30_000);
|
|
61
|
-
const { body } = applyWireCap("file_read", oversized, {
|
|
62
|
-
token_budget: 5000,
|
|
63
|
-
});
|
|
64
|
-
const obj = body;
|
|
65
|
-
expect(obj.reason).toBe("narrow_required");
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
describe("wire-cap pagination hint — concrete next cursor", () => {
|
|
69
|
-
it("emits a numeric cursor (not the literal N)", () => {
|
|
70
|
-
// search_code returns a top-level array; its cursorArg is `limit`.
|
|
71
|
-
const arr = Array.from({ length: 50 }, (_, i) => ({ id: i }));
|
|
72
|
-
const { pageHint } = applyWireCap("search_code", arr, {});
|
|
73
|
-
// Cursor should be a real number, never the placeholder.
|
|
74
|
-
expect(pageHint).toMatch(/:\d+/);
|
|
75
|
-
expect(pageHint).not.toMatch(/:N(\s|\/|$)/);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker pool tests — parallel AST parsing via tinypool.
|
|
3
|
-
*/
|
|
4
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
8
|
-
import { destroy, parseFiles, resetPoolState, } from "../intelligence/worker-pool.js";
|
|
9
|
-
const SAMPLE_TS = `
|
|
10
|
-
export function greet(name: string): string {
|
|
11
|
-
return \`Hello, \${name}\`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class Greeter {
|
|
15
|
-
private name: string;
|
|
16
|
-
|
|
17
|
-
constructor(name: string) {
|
|
18
|
-
this.name = name;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
sayHello(): string {
|
|
22
|
-
return \`Hello, \${this.name}\`;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface GreetOptions {
|
|
27
|
-
loud: boolean;
|
|
28
|
-
prefix?: string;
|
|
29
|
-
}
|
|
30
|
-
`;
|
|
31
|
-
describe("worker-pool", () => {
|
|
32
|
-
let tempDir;
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
tempDir = mkdtempSync(join(tmpdir(), "unerr-wp-"));
|
|
35
|
-
});
|
|
36
|
-
afterEach(async () => {
|
|
37
|
-
await destroy();
|
|
38
|
-
resetPoolState();
|
|
39
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
40
|
-
});
|
|
41
|
-
it("initializes and parses a single file", async () => {
|
|
42
|
-
const filePath = join(tempDir, "sample.ts");
|
|
43
|
-
writeFileSync(filePath, SAMPLE_TS);
|
|
44
|
-
const results = await parseFiles([{ filePath, content: SAMPLE_TS }]);
|
|
45
|
-
expect(results).toHaveLength(1);
|
|
46
|
-
expect(results[0]?.filePath).toBe(filePath);
|
|
47
|
-
expect(results[0]?.entities.length).toBeGreaterThan(0);
|
|
48
|
-
expect(results[0]?.durationMs).toBeGreaterThanOrEqual(0);
|
|
49
|
-
const names = results[0]?.entities.map((e) => e.name);
|
|
50
|
-
expect(names).toContain("greet");
|
|
51
|
-
expect(names).toContain("Greeter");
|
|
52
|
-
expect(names).toContain("GreetOptions");
|
|
53
|
-
});
|
|
54
|
-
it("parses 10 files in parallel", async () => {
|
|
55
|
-
const files = Array.from({ length: 10 }, (_, i) => {
|
|
56
|
-
const filePath = join(tempDir, `file${i}.ts`);
|
|
57
|
-
const content = `export function fn${i}(x: number): number { return x * ${i}; }\n`;
|
|
58
|
-
writeFileSync(filePath, content);
|
|
59
|
-
return { filePath, content };
|
|
60
|
-
});
|
|
61
|
-
const results = await parseFiles(files);
|
|
62
|
-
expect(results).toHaveLength(10);
|
|
63
|
-
for (let i = 0; i < 10; i++) {
|
|
64
|
-
expect(results[i]?.filePath).toBe(files[i]?.filePath);
|
|
65
|
-
expect(results[i]?.entities.length).toBeGreaterThanOrEqual(1);
|
|
66
|
-
const fnEntity = results[i]?.entities.find((e) => e.name === `fn${i}`);
|
|
67
|
-
expect(fnEntity).toBeDefined();
|
|
68
|
-
expect(fnEntity?.kind).toBe("function");
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
it("handles empty input", async () => {
|
|
72
|
-
const results = await parseFiles([]);
|
|
73
|
-
expect(results).toEqual([]);
|
|
74
|
-
});
|
|
75
|
-
it("handles files with no extractable entities", async () => {
|
|
76
|
-
const filePath = join(tempDir, "data.json");
|
|
77
|
-
const content = '{"key": "value"}';
|
|
78
|
-
writeFileSync(filePath, content);
|
|
79
|
-
const results = await parseFiles([{ filePath, content }]);
|
|
80
|
-
expect(results).toHaveLength(1);
|
|
81
|
-
expect(results[0]?.entities).toEqual([]);
|
|
82
|
-
});
|
|
83
|
-
it("can be destroyed and recreated", async () => {
|
|
84
|
-
const filePath = join(tempDir, "first.ts");
|
|
85
|
-
const content = "export function first(): void {}\n";
|
|
86
|
-
writeFileSync(filePath, content);
|
|
87
|
-
const results1 = await parseFiles([{ filePath, content }]);
|
|
88
|
-
expect(results1).toHaveLength(1);
|
|
89
|
-
expect(results1[0]?.entities.length).toBeGreaterThan(0);
|
|
90
|
-
await destroy();
|
|
91
|
-
resetPoolState();
|
|
92
|
-
const filePath2 = join(tempDir, "second.ts");
|
|
93
|
-
const content2 = "export function second(): void {}\n";
|
|
94
|
-
writeFileSync(filePath2, content2);
|
|
95
|
-
const results2 = await parseFiles([
|
|
96
|
-
{ filePath: filePath2, content: content2 },
|
|
97
|
-
]);
|
|
98
|
-
expect(results2).toHaveLength(1);
|
|
99
|
-
expect(results2[0]?.entities.find((e) => e.name === "second")).toBeDefined();
|
|
100
|
-
});
|
|
101
|
-
});
|