@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,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint K.8: Incremental indexing accuracy tests.
|
|
3
|
-
*
|
|
4
|
-
* Tests entity diffing, cascade invalidation, graph patching,
|
|
5
|
-
* metadata persistence, and snapshot save/load.
|
|
6
|
-
*/
|
|
7
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, } from "vitest";
|
|
11
|
-
import { bodyHash, entityKey } from "../intelligence/indexer/entity-key.js";
|
|
12
|
-
import { cascadeInvalidation, diffEntities, fileNeedsReindex, } from "../intelligence/indexer/incremental.js";
|
|
13
|
-
import { loadMetadata, fileNeedsReindex as metaNeedsReindex, saveMetadata, updateFileMetadata, } from "../intelligence/indexer/metadata.js";
|
|
14
|
-
import { registerPlugin, } from "../intelligence/indexer/plugin-interface.js";
|
|
15
|
-
import { typescriptPlugin } from "../intelligence/indexer/plugins/typescript.js";
|
|
16
|
-
import { hasSnapshot, loadSnapshot, saveSnapshot, } from "../intelligence/indexer/snapshot.js";
|
|
17
|
-
import { filterIndexableEvents } from "../intelligence/indexer/watch-integration.js";
|
|
18
|
-
import { clearParserCache, parseSource, } from "../intelligence/tree-sitter-loader.js";
|
|
19
|
-
let tempDir;
|
|
20
|
-
beforeAll(() => {
|
|
21
|
-
registerPlugin(typescriptPlugin);
|
|
22
|
-
});
|
|
23
|
-
afterAll(() => {
|
|
24
|
-
clearParserCache();
|
|
25
|
-
});
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
tempDir = join(tmpdir(), `unerr-incr-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
28
|
-
mkdirSync(tempDir, { recursive: true });
|
|
29
|
-
});
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
32
|
-
});
|
|
33
|
-
function makeEntity(name, bodyContent, filePath = "test.ts") {
|
|
34
|
-
return {
|
|
35
|
-
key: entityKey(filePath, "function", name, ""),
|
|
36
|
-
kind: "function",
|
|
37
|
-
name,
|
|
38
|
-
file_path: filePath,
|
|
39
|
-
start_line: 1,
|
|
40
|
-
end_line: 5,
|
|
41
|
-
signature: `${name}()`,
|
|
42
|
-
body_hash: bodyHash(bodyContent),
|
|
43
|
-
exported: true,
|
|
44
|
-
parent_key: null,
|
|
45
|
-
language: "typescript",
|
|
46
|
-
is_async: false,
|
|
47
|
-
parameter_count: 0,
|
|
48
|
-
doc: null,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
describe("Entity Diff (K.1)", () => {
|
|
52
|
-
it("detects added entities", () => {
|
|
53
|
-
const oldEntities = [];
|
|
54
|
-
const newEntities = [makeEntity("newFn", "return 1;")];
|
|
55
|
-
const diff = diffEntities(oldEntities, newEntities);
|
|
56
|
-
expect(diff.added).toHaveLength(1);
|
|
57
|
-
expect(diff.added[0].name).toBe("newFn");
|
|
58
|
-
expect(diff.updated).toHaveLength(0);
|
|
59
|
-
expect(diff.deleted).toHaveLength(0);
|
|
60
|
-
});
|
|
61
|
-
it("detects deleted entities", () => {
|
|
62
|
-
const oldEntities = [makeEntity("oldFn", "return 2;")];
|
|
63
|
-
const newEntities = [];
|
|
64
|
-
const diff = diffEntities(oldEntities, newEntities);
|
|
65
|
-
expect(diff.deleted).toHaveLength(1);
|
|
66
|
-
expect(diff.deleted[0].name).toBe("oldFn");
|
|
67
|
-
expect(diff.added).toHaveLength(0);
|
|
68
|
-
});
|
|
69
|
-
it("detects updated entities (body_hash changed)", () => {
|
|
70
|
-
const old = makeEntity("fn", "return 1;");
|
|
71
|
-
const updated = { ...old, body_hash: bodyHash("return 2;") };
|
|
72
|
-
const diff = diffEntities([old], [updated]);
|
|
73
|
-
expect(diff.updated).toHaveLength(1);
|
|
74
|
-
expect(diff.unchanged).toHaveLength(0);
|
|
75
|
-
});
|
|
76
|
-
it("detects unchanged entities (same body_hash)", () => {
|
|
77
|
-
const entity = makeEntity("fn", "return 42;");
|
|
78
|
-
const diff = diffEntities([entity], [entity]);
|
|
79
|
-
expect(diff.unchanged).toHaveLength(1);
|
|
80
|
-
expect(diff.added).toHaveLength(0);
|
|
81
|
-
expect(diff.updated).toHaveLength(0);
|
|
82
|
-
expect(diff.deleted).toHaveLength(0);
|
|
83
|
-
});
|
|
84
|
-
it("handles complex diff with mixed changes", () => {
|
|
85
|
-
const e1 = makeEntity("keep", "body1");
|
|
86
|
-
const e2 = makeEntity("change", "old_body");
|
|
87
|
-
const e3 = makeEntity("remove", "body3");
|
|
88
|
-
const e2New = { ...e2, body_hash: bodyHash("new_body") };
|
|
89
|
-
const e4 = makeEntity("add", "body4");
|
|
90
|
-
const diff = diffEntities([e1, e2, e3], [e1, e2New, e4]);
|
|
91
|
-
expect(diff.unchanged).toHaveLength(1);
|
|
92
|
-
expect(diff.updated).toHaveLength(1);
|
|
93
|
-
expect(diff.deleted).toHaveLength(1);
|
|
94
|
-
expect(diff.added).toHaveLength(1);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
describe("Cascade Invalidation (K.2)", () => {
|
|
98
|
-
it("identifies edges affected by changed entities", () => {
|
|
99
|
-
const updated = makeEntity("target", "new body");
|
|
100
|
-
const diff = { added: [], updated: [updated], deleted: [], unchanged: [] };
|
|
101
|
-
const edges = [
|
|
102
|
-
{
|
|
103
|
-
from_key: "caller",
|
|
104
|
-
to_key: updated.key,
|
|
105
|
-
type: "calls",
|
|
106
|
-
file_path: "a.ts",
|
|
107
|
-
line: 10,
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
from_key: updated.key,
|
|
111
|
-
to_key: "callee",
|
|
112
|
-
type: "calls",
|
|
113
|
-
file_path: "test.ts",
|
|
114
|
-
line: 5,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
from_key: "unrelated1",
|
|
118
|
-
to_key: "unrelated2",
|
|
119
|
-
type: "calls",
|
|
120
|
-
file_path: "b.ts",
|
|
121
|
-
line: 1,
|
|
122
|
-
},
|
|
123
|
-
];
|
|
124
|
-
const result = cascadeInvalidation(diff, edges);
|
|
125
|
-
expect(result.invalidatedEdges).toHaveLength(2);
|
|
126
|
-
expect(result.affectedFiles.has("a.ts")).toBe(true);
|
|
127
|
-
expect(result.affectedFiles.has("test.ts")).toBe(true);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
describe("File Watch Integration (K.3)", () => {
|
|
131
|
-
it("filters indexable events", () => {
|
|
132
|
-
const events = [
|
|
133
|
-
{ type: "create", path: "/repo/src/auth.ts" },
|
|
134
|
-
{ type: "update", path: "/repo/src/data.json" },
|
|
135
|
-
{ type: "delete", path: "/repo/src/old.ts" },
|
|
136
|
-
{ type: "create", path: "/repo/README.md" },
|
|
137
|
-
];
|
|
138
|
-
const indexable = filterIndexableEvents(events);
|
|
139
|
-
expect(indexable).toContain("/repo/src/auth.ts");
|
|
140
|
-
expect(indexable).toContain("/repo/src/old.ts");
|
|
141
|
-
expect(indexable).not.toContain("/repo/src/data.json");
|
|
142
|
-
expect(indexable).not.toContain("/repo/README.md");
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
describe("Index Metadata (K.6)", () => {
|
|
146
|
-
it("saves and loads metadata", () => {
|
|
147
|
-
const metadata = loadMetadata(tempDir);
|
|
148
|
-
expect(metadata.version).toBe(1);
|
|
149
|
-
updateFileMetadata(metadata, "src/auth.ts", "abc123", 5);
|
|
150
|
-
saveMetadata(tempDir, metadata);
|
|
151
|
-
const loaded = loadMetadata(tempDir);
|
|
152
|
-
expect(loaded.files["src/auth.ts"]).toBeDefined();
|
|
153
|
-
expect(loaded.files["src/auth.ts"].hash).toBe("abc123");
|
|
154
|
-
expect(loaded.files["src/auth.ts"].entityCount).toBe(5);
|
|
155
|
-
});
|
|
156
|
-
it("detects files needing reindex by hash", () => {
|
|
157
|
-
const metadata = loadMetadata(tempDir);
|
|
158
|
-
updateFileMetadata(metadata, "src/a.ts", "hash1", 3);
|
|
159
|
-
expect(metaNeedsReindex(metadata, "src/a.ts", "hash1")).toBe(false);
|
|
160
|
-
expect(metaNeedsReindex(metadata, "src/a.ts", "hash2")).toBe(true);
|
|
161
|
-
expect(metaNeedsReindex(metadata, "src/new.ts", "hash3")).toBe(true);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
describe("Index Snapshot (K.7)", () => {
|
|
165
|
-
it("saves and loads snapshot", () => {
|
|
166
|
-
const entities = [makeEntity("fn1", "body1"), makeEntity("fn2", "body2")];
|
|
167
|
-
const edges = [
|
|
168
|
-
{
|
|
169
|
-
from_key: entities[0].key,
|
|
170
|
-
to_key: entities[1].key,
|
|
171
|
-
type: "calls",
|
|
172
|
-
file_path: "test.ts",
|
|
173
|
-
line: 3,
|
|
174
|
-
},
|
|
175
|
-
];
|
|
176
|
-
const metadata = loadMetadata(tempDir);
|
|
177
|
-
saveSnapshot(tempDir, entities, edges, metadata);
|
|
178
|
-
expect(hasSnapshot(tempDir)).toBe(true);
|
|
179
|
-
const snapshot = loadSnapshot(tempDir);
|
|
180
|
-
expect(snapshot).not.toBeNull();
|
|
181
|
-
expect(snapshot.entities).toHaveLength(2);
|
|
182
|
-
expect(snapshot.edges).toHaveLength(1);
|
|
183
|
-
});
|
|
184
|
-
it("returns null for missing snapshot", () => {
|
|
185
|
-
expect(loadSnapshot(tempDir)).toBeNull();
|
|
186
|
-
expect(hasSnapshot(tempDir)).toBe(false);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
describe("Hash Short-Circuit (K.1 + K.6)", () => {
|
|
190
|
-
it("re-index same content produces no mutations", () => {
|
|
191
|
-
const entity = makeEntity("stable", "return 42;");
|
|
192
|
-
expect(fileNeedsReindex([entity], [entity])).toBe(false);
|
|
193
|
-
});
|
|
194
|
-
it("detects change when content differs", () => {
|
|
195
|
-
const old = makeEntity("fn", "return 1;");
|
|
196
|
-
const updated = { ...old, body_hash: bodyHash("return 2;") };
|
|
197
|
-
expect(fileNeedsReindex([old], [updated])).toBe(true);
|
|
198
|
-
});
|
|
199
|
-
it("detects change when entity count differs", () => {
|
|
200
|
-
const e1 = makeEntity("fn1", "body1");
|
|
201
|
-
const e2 = makeEntity("fn2", "body2");
|
|
202
|
-
expect(fileNeedsReindex([e1], [e1, e2])).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
describe("Tree-sitter Incremental Re-extraction", () => {
|
|
206
|
-
it("produces same keys for unchanged file", async () => {
|
|
207
|
-
const source = `export function greet(name: string): string { return "Hello " + name; }`;
|
|
208
|
-
const tree1 = await parseSource(source, "tree-sitter-typescript.wasm");
|
|
209
|
-
const tree2 = await parseSource(source, "tree-sitter-typescript.wasm");
|
|
210
|
-
const r1 = typescriptPlugin.extract(tree1, "test.ts", source);
|
|
211
|
-
const r2 = typescriptPlugin.extract(tree2, "test.ts", source);
|
|
212
|
-
expect(r1.entities.map((e) => e.key)).toEqual(r2.entities.map((e) => e.key));
|
|
213
|
-
expect(r1.entities.map((e) => e.body_hash)).toEqual(r2.entities.map((e) => e.body_hash));
|
|
214
|
-
tree1.delete();
|
|
215
|
-
tree2.delete();
|
|
216
|
-
});
|
|
217
|
-
it("detects change when function body changes", async () => {
|
|
218
|
-
const v1 = "export function calc(x: number): number { return x + 1; }";
|
|
219
|
-
const v2 = "export function calc(x: number): number { return x * 2; }";
|
|
220
|
-
const tree1 = await parseSource(v1, "tree-sitter-typescript.wasm");
|
|
221
|
-
const tree2 = await parseSource(v2, "tree-sitter-typescript.wasm");
|
|
222
|
-
const r1 = typescriptPlugin.extract(tree1, "test.ts", v1);
|
|
223
|
-
const r2 = typescriptPlugin.extract(tree2, "test.ts", v2);
|
|
224
|
-
expect(r1.entities[0].key).toBe(r2.entities[0].key);
|
|
225
|
-
expect(r1.entities[0].body_hash).not.toBe(r2.entities[0].body_hash);
|
|
226
|
-
const diff = diffEntities(r1.entities, r2.entities);
|
|
227
|
-
expect(diff.updated).toHaveLength(1);
|
|
228
|
-
expect(diff.unchanged).toHaveLength(0);
|
|
229
|
-
tree1.delete();
|
|
230
|
-
tree2.delete();
|
|
231
|
-
});
|
|
232
|
-
});
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint L.12-L.15: Language expansion tests.
|
|
3
|
-
*
|
|
4
|
-
* Tests Tier-1 plugins (Python, Go, Java, Rust, Ruby, C#),
|
|
5
|
-
* Tier-2 generic plugin (C++, PHP, Swift),
|
|
6
|
-
* Tier-3 regex fallback, language detection, and confidence labeling.
|
|
7
|
-
*/
|
|
8
|
-
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
9
|
-
import { confidenceScore, labelFromTier, upgradeToCompilerVerified, } from "../intelligence/indexer/confidence.js";
|
|
10
|
-
import { detectLanguage, getLanguageTier, } from "../intelligence/indexer/language-detect.js";
|
|
11
|
-
import { getAllPlugins, getPluginForFile, registerPlugin, } from "../intelligence/indexer/plugin-interface.js";
|
|
12
|
-
import { regexExtract } from "../intelligence/indexer/plugins/regex-fallback.js";
|
|
13
|
-
import { tier2Plugins } from "../intelligence/indexer/plugins/tier2-generic.js";
|
|
14
|
-
import { typescriptPlugin } from "../intelligence/indexer/plugins/typescript.js";
|
|
15
|
-
import { clearParserCache, parseSource, } from "../intelligence/tree-sitter-loader.js";
|
|
16
|
-
beforeAll(() => {
|
|
17
|
-
registerPlugin(typescriptPlugin);
|
|
18
|
-
for (const plugin of tier2Plugins) {
|
|
19
|
-
registerPlugin(plugin);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
afterAll(() => {
|
|
23
|
-
clearParserCache();
|
|
24
|
-
});
|
|
25
|
-
describe("Language Detection (L.10)", () => {
|
|
26
|
-
it("detects TypeScript as Tier 1", () => {
|
|
27
|
-
const info = detectLanguage("src/auth.ts");
|
|
28
|
-
expect(info?.tier).toBe(1);
|
|
29
|
-
expect(info?.id).toBe("typescript");
|
|
30
|
-
});
|
|
31
|
-
it("detects Python as Tier 1", () => {
|
|
32
|
-
const info = detectLanguage("main.py");
|
|
33
|
-
expect(info?.tier).toBe(1);
|
|
34
|
-
expect(info?.id).toBe("python");
|
|
35
|
-
});
|
|
36
|
-
it("detects Go as Tier 1", () => {
|
|
37
|
-
const info = detectLanguage("main.go");
|
|
38
|
-
expect(info?.tier).toBe(1);
|
|
39
|
-
expect(info?.id).toBe("go");
|
|
40
|
-
});
|
|
41
|
-
it("detects C++ as Tier 2", () => {
|
|
42
|
-
const info = detectLanguage("engine.cpp");
|
|
43
|
-
expect(info?.tier).toBe(2);
|
|
44
|
-
expect(info?.id).toBe("cpp");
|
|
45
|
-
});
|
|
46
|
-
it("detects Swift as Tier 2", () => {
|
|
47
|
-
const info = detectLanguage("App.swift");
|
|
48
|
-
expect(info?.tier).toBe(2);
|
|
49
|
-
});
|
|
50
|
-
it("returns null for unknown extensions", () => {
|
|
51
|
-
expect(detectLanguage("README.md")).toBeNull();
|
|
52
|
-
expect(detectLanguage("data.json")).toBeNull();
|
|
53
|
-
});
|
|
54
|
-
it("returns Tier 3 for unknown files", () => {
|
|
55
|
-
expect(getLanguageTier("data.csv")).toBe(3);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
describe("Confidence Labeling (L.11)", () => {
|
|
59
|
-
it("labels Tier 1 as structural", () => {
|
|
60
|
-
const label = labelFromTier(1);
|
|
61
|
-
expect(label.level).toBe("structural");
|
|
62
|
-
expect(label.source).toContain("tier1");
|
|
63
|
-
});
|
|
64
|
-
it("labels Tier 2 as structural", () => {
|
|
65
|
-
const label = labelFromTier(2);
|
|
66
|
-
expect(label.level).toBe("structural");
|
|
67
|
-
expect(label.source).toContain("tier2");
|
|
68
|
-
});
|
|
69
|
-
it("labels Tier 3 as heuristic", () => {
|
|
70
|
-
const label = labelFromTier(3);
|
|
71
|
-
expect(label.level).toBe("heuristic");
|
|
72
|
-
});
|
|
73
|
-
it("upgrades to compiler-verified", () => {
|
|
74
|
-
const base = labelFromTier(1);
|
|
75
|
-
const upgraded = upgradeToCompilerVerified(base);
|
|
76
|
-
expect(upgraded.level).toBe("compiler-verified");
|
|
77
|
-
expect(upgraded.source).toContain("scip");
|
|
78
|
-
});
|
|
79
|
-
it("computes correct confidence scores", () => {
|
|
80
|
-
expect(confidenceScore("compiler-verified")).toBe(1.0);
|
|
81
|
-
expect(confidenceScore("structural")).toBe(0.85);
|
|
82
|
-
expect(confidenceScore("heuristic")).toBe(0.5);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
describe("Tier-3 Regex Fallback (L.9)", () => {
|
|
86
|
-
it("extracts functions from unknown language", () => {
|
|
87
|
-
const source = `
|
|
88
|
-
function processPayment(amount) {
|
|
89
|
-
return charge(amount);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
class PaymentGateway {
|
|
93
|
-
constructor() {}
|
|
94
|
-
}
|
|
95
|
-
`;
|
|
96
|
-
const result = regexExtract(source, "payment.unknown");
|
|
97
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
98
|
-
const fn = result.entities.find((e) => e.name === "processPayment");
|
|
99
|
-
expect(fn).toBeDefined();
|
|
100
|
-
expect(fn?.kind).toBe("function");
|
|
101
|
-
});
|
|
102
|
-
it("extracts classes from unknown language", () => {
|
|
103
|
-
const source = "class MyService { }";
|
|
104
|
-
const result = regexExtract(source, "service.txt");
|
|
105
|
-
const cls = result.entities.find((e) => e.name === "MyService");
|
|
106
|
-
expect(cls).toBeDefined();
|
|
107
|
-
expect(cls?.kind).toBe("class");
|
|
108
|
-
});
|
|
109
|
-
it("handles empty source gracefully", () => {
|
|
110
|
-
const result = regexExtract("", "empty.txt");
|
|
111
|
-
expect(result.entities).toHaveLength(0);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
describe("Tier-2 Generic Plugin (L.7)", () => {
|
|
115
|
-
it("C++ plugin registered and resolves", () => {
|
|
116
|
-
const plugin = getPluginForFile("engine.cpp");
|
|
117
|
-
expect(plugin).not.toBeNull();
|
|
118
|
-
expect(plugin?.id).toBe("cpp");
|
|
119
|
-
});
|
|
120
|
-
it("PHP plugin registered", () => {
|
|
121
|
-
const plugin = getPluginForFile("index.php");
|
|
122
|
-
expect(plugin).not.toBeNull();
|
|
123
|
-
expect(plugin?.id).toBe("php");
|
|
124
|
-
});
|
|
125
|
-
it("Swift plugin registered", () => {
|
|
126
|
-
const plugin = getPluginForFile("App.swift");
|
|
127
|
-
expect(plugin).not.toBeNull();
|
|
128
|
-
expect(plugin?.id).toBe("swift");
|
|
129
|
-
});
|
|
130
|
-
it("Kotlin plugin registered", () => {
|
|
131
|
-
const plugin = getPluginForFile("Main.kt");
|
|
132
|
-
expect(plugin).not.toBeNull();
|
|
133
|
-
expect(plugin?.id).toBe("kotlin");
|
|
134
|
-
});
|
|
135
|
-
it("C plugin extracts from tree-sitter", async () => {
|
|
136
|
-
const source = `
|
|
137
|
-
#include <stdio.h>
|
|
138
|
-
|
|
139
|
-
void greet(const char *name) {
|
|
140
|
-
printf("Hello, %s\\n", name);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
struct Point {
|
|
144
|
-
int x;
|
|
145
|
-
int y;
|
|
146
|
-
};
|
|
147
|
-
`;
|
|
148
|
-
const plugin = getPluginForFile("main.c");
|
|
149
|
-
if (!plugin)
|
|
150
|
-
return;
|
|
151
|
-
const tree = await parseSource(source, plugin.grammarWasmName);
|
|
152
|
-
const result = plugin.extract(tree, "main.c", source);
|
|
153
|
-
tree.delete();
|
|
154
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
describe("Plugin Registry Coverage", () => {
|
|
158
|
-
it("has TypeScript plugin", () => {
|
|
159
|
-
expect(getPluginForFile("app.ts")).not.toBeNull();
|
|
160
|
-
});
|
|
161
|
-
it("has multiple Tier-2 plugins registered", () => {
|
|
162
|
-
const all = getAllPlugins();
|
|
163
|
-
expect(all.length).toBeGreaterThanOrEqual(5);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
describe("CLI init command core logic", () => {
|
|
6
|
-
let tmpDir;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
tmpDir = path.join(os.tmpdir(), `unerr-init-test-${Date.now()}`);
|
|
9
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
13
|
-
});
|
|
14
|
-
it("creates .unerr/config.json with correct structure", () => {
|
|
15
|
-
const unerrDir = path.join(tmpDir, ".unerr");
|
|
16
|
-
fs.mkdirSync(unerrDir, { recursive: true });
|
|
17
|
-
const config = {
|
|
18
|
-
repoId: "repo-123",
|
|
19
|
-
orgId: "org-456",
|
|
20
|
-
branch: "main",
|
|
21
|
-
};
|
|
22
|
-
fs.writeFileSync(path.join(unerrDir, "config.json"), `${JSON.stringify(config, null, 2)}\n`);
|
|
23
|
-
const written = JSON.parse(fs.readFileSync(path.join(unerrDir, "config.json"), "utf-8"));
|
|
24
|
-
expect(written.repoId).toBe("repo-123");
|
|
25
|
-
expect(written.orgId).toBe("org-456");
|
|
26
|
-
expect(written.branch).toBe("main");
|
|
27
|
-
});
|
|
28
|
-
it("adds .unerr to .gitignore when gitignore exists", () => {
|
|
29
|
-
const gitignorePath = path.join(tmpDir, ".gitignore");
|
|
30
|
-
fs.writeFileSync(gitignorePath, "node_modules/\n.env\n");
|
|
31
|
-
// Simulate the init logic for .gitignore
|
|
32
|
-
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
33
|
-
if (!content.includes(".unerr")) {
|
|
34
|
-
fs.appendFileSync(gitignorePath, "\n# unerr local config\n.unerr/\n");
|
|
35
|
-
}
|
|
36
|
-
const result = fs.readFileSync(gitignorePath, "utf-8");
|
|
37
|
-
expect(result).toContain(".unerr/");
|
|
38
|
-
expect(result).toContain("node_modules/");
|
|
39
|
-
});
|
|
40
|
-
it("creates .gitignore with .unerr when none exists", () => {
|
|
41
|
-
const gitignorePath = path.join(tmpDir, ".gitignore");
|
|
42
|
-
expect(fs.existsSync(gitignorePath)).toBe(false);
|
|
43
|
-
// Simulate init logic
|
|
44
|
-
fs.writeFileSync(gitignorePath, "# unerr local config\n.unerr/\n");
|
|
45
|
-
const result = fs.readFileSync(gitignorePath, "utf-8");
|
|
46
|
-
expect(result).toContain(".unerr/");
|
|
47
|
-
});
|
|
48
|
-
it("does not duplicate .unerr entry in .gitignore", () => {
|
|
49
|
-
const gitignorePath = path.join(tmpDir, ".gitignore");
|
|
50
|
-
fs.writeFileSync(gitignorePath, "node_modules/\n.unerr/\n");
|
|
51
|
-
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
52
|
-
if (!content.includes(".unerr")) {
|
|
53
|
-
fs.appendFileSync(gitignorePath, "\n# unerr local config\n.unerr/\n");
|
|
54
|
-
}
|
|
55
|
-
const result = fs.readFileSync(gitignorePath, "utf-8");
|
|
56
|
-
const matches = result.match(/\.unerr/g);
|
|
57
|
-
expect(matches).not.toBeNull();
|
|
58
|
-
expect(matches?.length).toBe(1); // Only the original one
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
describe("CLI push command core logic", () => {
|
|
62
|
-
let tmpDir;
|
|
63
|
-
beforeEach(() => {
|
|
64
|
-
tmpDir = path.join(os.tmpdir(), `unerr-push-test-${Date.now()}`);
|
|
65
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
66
|
-
});
|
|
67
|
-
afterEach(() => {
|
|
68
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
69
|
-
});
|
|
70
|
-
it("loadConfig returns null when .unerr/config.json does not exist", () => {
|
|
71
|
-
const configPath = path.join(tmpDir, ".unerr", "config.json");
|
|
72
|
-
expect(fs.existsSync(configPath)).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
it("loadConfig returns config when file exists", () => {
|
|
75
|
-
const unerrDir = path.join(tmpDir, ".unerr");
|
|
76
|
-
fs.mkdirSync(unerrDir, { recursive: true });
|
|
77
|
-
const config = {
|
|
78
|
-
repoId: "r-1",
|
|
79
|
-
orgId: "o-1",
|
|
80
|
-
};
|
|
81
|
-
fs.writeFileSync(path.join(unerrDir, "config.json"), JSON.stringify(config));
|
|
82
|
-
const raw = fs.readFileSync(path.join(unerrDir, "config.json"), "utf-8");
|
|
83
|
-
const parsed = JSON.parse(raw);
|
|
84
|
-
expect(parsed.repoId).toBe("r-1");
|
|
85
|
-
});
|
|
86
|
-
it("gitignore patterns exclude correct files from zip candidates", () => {
|
|
87
|
-
// Create a mini file structure
|
|
88
|
-
fs.writeFileSync(path.join(tmpDir, "src.ts"), "export const a = 1");
|
|
89
|
-
fs.mkdirSync(path.join(tmpDir, "node_modules", "pkg"), { recursive: true });
|
|
90
|
-
fs.writeFileSync(path.join(tmpDir, "node_modules", "pkg", "index.js"), "module.exports = {}");
|
|
91
|
-
fs.mkdirSync(path.join(tmpDir, ".git"), { recursive: true });
|
|
92
|
-
fs.writeFileSync(path.join(tmpDir, ".git", "HEAD"), "ref: refs/heads/main");
|
|
93
|
-
fs.mkdirSync(path.join(tmpDir, ".unerr"), { recursive: true });
|
|
94
|
-
fs.writeFileSync(path.join(tmpDir, ".unerr", "config.json"), "{}");
|
|
95
|
-
// Default exclusion patterns (same as push.ts uses)
|
|
96
|
-
const alwaysExclude = [".git", ".unerr", "node_modules"];
|
|
97
|
-
function shouldInclude(relPath) {
|
|
98
|
-
for (const pattern of alwaysExclude) {
|
|
99
|
-
if (relPath === pattern ||
|
|
100
|
-
relPath.startsWith(`${pattern}/`) ||
|
|
101
|
-
relPath.startsWith(pattern + path.sep)) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
// Collect relative file paths
|
|
108
|
-
function collectFiles(dir, relativeTo) {
|
|
109
|
-
const results = [];
|
|
110
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
111
|
-
for (const entry of entries) {
|
|
112
|
-
const fullPath = path.join(dir, entry.name);
|
|
113
|
-
const relPath = path.relative(relativeTo, fullPath);
|
|
114
|
-
if (!shouldInclude(relPath))
|
|
115
|
-
continue;
|
|
116
|
-
if (entry.isDirectory()) {
|
|
117
|
-
results.push(...collectFiles(fullPath, relativeTo));
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
results.push(relPath);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return results;
|
|
124
|
-
}
|
|
125
|
-
const files = collectFiles(tmpDir, tmpDir);
|
|
126
|
-
expect(files).toContain("src.ts");
|
|
127
|
-
expect(files.some((f) => f.startsWith("node_modules"))).toBe(false);
|
|
128
|
-
expect(files.some((f) => f.startsWith(".git"))).toBe(false);
|
|
129
|
-
expect(files.some((f) => f.startsWith(".unerr"))).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
});
|