@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,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint L2 Tests: Local indexing pipeline, snapshot persistence, edge extraction.
|
|
3
|
-
*/
|
|
4
|
-
import { mkdirSync, 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, vi } from "vitest";
|
|
8
|
-
// ── Fixture project factory ─────────────────────────────────────
|
|
9
|
-
function createFixtureProject() {
|
|
10
|
-
const dir = mkdtempSync(join(tmpdir(), "unerr-l2-test-"));
|
|
11
|
-
// Create a minimal multi-file TypeScript project
|
|
12
|
-
mkdirSync(join(dir, "src"), { recursive: true });
|
|
13
|
-
mkdirSync(join(dir, "src", "utils"), { recursive: true });
|
|
14
|
-
writeFileSync(join(dir, "src", "index.ts"), `
|
|
15
|
-
import { greet } from "./utils/greet.js";
|
|
16
|
-
|
|
17
|
-
export function main(): void {
|
|
18
|
-
const message = greet("world");
|
|
19
|
-
console.log(message);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class App {
|
|
23
|
-
private name: string;
|
|
24
|
-
|
|
25
|
-
constructor(name: string) {
|
|
26
|
-
this.name = name;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
run(): void {
|
|
30
|
-
main();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
`);
|
|
34
|
-
writeFileSync(join(dir, "src", "utils", "greet.ts"), `
|
|
35
|
-
export function greet(name: string): string {
|
|
36
|
-
return formatMessage(\`Hello, \${name}!\`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function formatMessage(msg: string): string {
|
|
40
|
-
return msg.trim();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface Greeting {
|
|
44
|
-
name: string;
|
|
45
|
-
message: string;
|
|
46
|
-
}
|
|
47
|
-
`);
|
|
48
|
-
writeFileSync(join(dir, "src", "service.ts"), `
|
|
49
|
-
import { greet } from "./utils/greet.js";
|
|
50
|
-
|
|
51
|
-
export class UserService {
|
|
52
|
-
getGreeting(userId: string): string {
|
|
53
|
-
return greet(userId);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface UserConfig {
|
|
58
|
-
timeout: number;
|
|
59
|
-
retries: number;
|
|
60
|
-
}
|
|
61
|
-
`);
|
|
62
|
-
return dir;
|
|
63
|
-
}
|
|
64
|
-
// ── Tests ───────────────────────────────────────────────────────
|
|
65
|
-
describe("discoverSourceFiles", () => {
|
|
66
|
-
let projectDir;
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
projectDir = createFixtureProject();
|
|
69
|
-
});
|
|
70
|
-
afterEach(() => {
|
|
71
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
72
|
-
});
|
|
73
|
-
it("discovers .ts files and skips node_modules", async () => {
|
|
74
|
-
const { discoverSourceFiles } = await import("../intelligence/local-indexer.js");
|
|
75
|
-
// Add a node_modules dir that should be excluded
|
|
76
|
-
mkdirSync(join(projectDir, "node_modules", "fake-pkg"), {
|
|
77
|
-
recursive: true,
|
|
78
|
-
});
|
|
79
|
-
writeFileSync(join(projectDir, "node_modules", "fake-pkg", "index.ts"), "export const x = 1;");
|
|
80
|
-
const files = discoverSourceFiles(projectDir);
|
|
81
|
-
expect(files.length).toBeGreaterThanOrEqual(3);
|
|
82
|
-
expect(files.every((f) => !f.includes("node_modules"))).toBe(true);
|
|
83
|
-
expect(files.some((f) => f.endsWith("index.ts"))).toBe(true);
|
|
84
|
-
expect(files.some((f) => f.endsWith("greet.ts"))).toBe(true);
|
|
85
|
-
expect(files.some((f) => f.endsWith("service.ts"))).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
it("skips files exceeding 1MB", async () => {
|
|
88
|
-
const { discoverSourceFiles } = await import("../intelligence/local-indexer.js");
|
|
89
|
-
// Create a huge file
|
|
90
|
-
writeFileSync(join(projectDir, "src", "huge.ts"), "x".repeat(2_000_000));
|
|
91
|
-
const files = discoverSourceFiles(projectDir);
|
|
92
|
-
expect(files.every((f) => !f.endsWith("huge.ts"))).toBe(true);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe("extractEdgesAsync", () => {
|
|
96
|
-
it("extracts import edges from TypeScript", async () => {
|
|
97
|
-
const { extractEdgesAsync, extractEntities } = await import("../intelligence/ast-extractor.js");
|
|
98
|
-
const content = `
|
|
99
|
-
import { greet } from "./utils/greet.js";
|
|
100
|
-
import { App } from "./app.js";
|
|
101
|
-
|
|
102
|
-
export function main() {
|
|
103
|
-
const msg = greet("world");
|
|
104
|
-
const app = new App("test");
|
|
105
|
-
app.run();
|
|
106
|
-
}
|
|
107
|
-
`;
|
|
108
|
-
const entities = extractEntities(content, "src/index.ts");
|
|
109
|
-
const edges = await extractEdgesAsync(content, "src/index.ts", entities);
|
|
110
|
-
// Should have import edges
|
|
111
|
-
const importEdges = edges.filter((e) => e.type === "imports");
|
|
112
|
-
expect(importEdges.length).toBeGreaterThanOrEqual(1);
|
|
113
|
-
// Should detect greet import
|
|
114
|
-
const greetImport = importEdges.find((e) => e.to_name === "greet");
|
|
115
|
-
expect(greetImport).toBeDefined();
|
|
116
|
-
});
|
|
117
|
-
it("extracts call edges from TypeScript", async () => {
|
|
118
|
-
const { extractEdgesAsync, extractEntities } = await import("../intelligence/ast-extractor.js");
|
|
119
|
-
const content = `
|
|
120
|
-
export function main() {
|
|
121
|
-
const result = helper();
|
|
122
|
-
process(result);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function helper(): number {
|
|
126
|
-
return 42;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function process(n: number): void {
|
|
130
|
-
console.log(n);
|
|
131
|
-
}
|
|
132
|
-
`;
|
|
133
|
-
const entities = extractEntities(content, "src/index.ts");
|
|
134
|
-
const edges = await extractEdgesAsync(content, "src/index.ts", entities);
|
|
135
|
-
const callEdges = edges.filter((e) => e.type === "calls");
|
|
136
|
-
// main calls helper and process
|
|
137
|
-
expect(callEdges.some((e) => e.to_name === "helper")).toBe(true);
|
|
138
|
-
expect(callEdges.some((e) => e.to_name === "process")).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
it("extracts extends/implements edges from classes", async () => {
|
|
141
|
-
const { extractEdgesAsync, extractEntities } = await import("../intelligence/ast-extractor.js");
|
|
142
|
-
const content = `
|
|
143
|
-
interface Runnable {
|
|
144
|
-
run(): void;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
class Base {
|
|
148
|
-
start(): void {}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export class App extends Base implements Runnable {
|
|
152
|
-
run(): void {}
|
|
153
|
-
}
|
|
154
|
-
`;
|
|
155
|
-
const entities = extractEntities(content, "src/app.ts");
|
|
156
|
-
const edges = await extractEdgesAsync(content, "src/app.ts", entities);
|
|
157
|
-
const extendsEdges = edges.filter((e) => e.type === "extends");
|
|
158
|
-
const implementsEdges = edges.filter((e) => e.type === "implements");
|
|
159
|
-
expect(extendsEdges.some((e) => e.to_name === "Base")).toBe(true);
|
|
160
|
-
expect(implementsEdges.some((e) => e.to_name === "Runnable")).toBe(true);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
describe("local-snapshot", () => {
|
|
164
|
-
it("snapshotPath returns consistent path for same root", async () => {
|
|
165
|
-
const { snapshotPath } = await import("../intelligence/local-snapshot.js");
|
|
166
|
-
const path1 = snapshotPath("/tmp/my-project");
|
|
167
|
-
const path2 = snapshotPath("/tmp/my-project");
|
|
168
|
-
expect(path1).toBe(path2);
|
|
169
|
-
expect(path1).toContain(".unerr/snapshots/");
|
|
170
|
-
expect(path1).toMatch(/\.msgpack\.gz$/);
|
|
171
|
-
});
|
|
172
|
-
it("snapshotPath is within the project root", async () => {
|
|
173
|
-
const { snapshotPath } = await import("../intelligence/local-snapshot.js");
|
|
174
|
-
const path1 = snapshotPath("/tmp/project-a");
|
|
175
|
-
const path2 = snapshotPath("/tmp/project-b");
|
|
176
|
-
expect(path1).toContain("/tmp/project-a/.unerr/");
|
|
177
|
-
expect(path2).toContain("/tmp/project-b/.unerr/");
|
|
178
|
-
expect(path1).not.toBe(path2);
|
|
179
|
-
});
|
|
180
|
-
it("shouldReindex returns true when no snapshot exists", async () => {
|
|
181
|
-
const { shouldReindex } = await import("../intelligence/local-snapshot.js");
|
|
182
|
-
// Non-existent project — no snapshot can exist
|
|
183
|
-
const result = shouldReindex(`/tmp/nonexistent-project-${Date.now()}`);
|
|
184
|
-
expect(result).toBe(true);
|
|
185
|
-
});
|
|
186
|
-
it("getSnapshotMeta reports non-existent snapshot", async () => {
|
|
187
|
-
const { getSnapshotMeta } = await import("../intelligence/local-snapshot.js");
|
|
188
|
-
const meta = getSnapshotMeta(`/tmp/nonexistent-project-${Date.now()}`);
|
|
189
|
-
expect(meta.exists).toBe(false);
|
|
190
|
-
expect(meta.path).toContain(".unerr/snapshots/");
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
describe("DriftTracker local reindex hook", () => {
|
|
194
|
-
it("setLocalReindex is callable and stores the hook", async () => {
|
|
195
|
-
const { DriftTracker } = await import("../tracking/drift-tracker.js");
|
|
196
|
-
// Create a minimal mock
|
|
197
|
-
const mockGraph = {
|
|
198
|
-
getEntitiesByFile: () => [],
|
|
199
|
-
getDriftEntitiesForFile: () => [],
|
|
200
|
-
upsertDriftEntity: vi.fn(),
|
|
201
|
-
getCallersOf: () => [],
|
|
202
|
-
findEntityByName: () => null,
|
|
203
|
-
upsertDriftEdge: vi.fn(),
|
|
204
|
-
hasRules: () => false,
|
|
205
|
-
getRules: () => [],
|
|
206
|
-
getDriftSummary: () => ({ total: 0, added: 0, modified: 0, deleted: 0 }),
|
|
207
|
-
clearDriftOverlay: vi.fn(),
|
|
208
|
-
};
|
|
209
|
-
const mockHashManager = {
|
|
210
|
-
shouldProcess: () => "process",
|
|
211
|
-
markProcessed: vi.fn(),
|
|
212
|
-
save: vi.fn(),
|
|
213
|
-
clearAll: vi.fn(),
|
|
214
|
-
getState: () => ({}),
|
|
215
|
-
restoreState: vi.fn(),
|
|
216
|
-
};
|
|
217
|
-
const tracker = new DriftTracker({
|
|
218
|
-
projectRoot: "/tmp/test",
|
|
219
|
-
repoId: "test-repo",
|
|
220
|
-
unerrDir: "/tmp/test/.unerr",
|
|
221
|
-
}, mockGraph, mockHashManager);
|
|
222
|
-
const mockReindex = vi.fn().mockResolvedValue({ entities: 5, edges: 3 });
|
|
223
|
-
tracker.setLocalReindex(mockReindex);
|
|
224
|
-
// The hook is stored internally — we can't directly verify it,
|
|
225
|
-
// but the fact that setLocalReindex doesn't throw is the test.
|
|
226
|
-
expect(mockReindex).not.toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
});
|
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint L3 Tests: Local Project Stats (L3.3) & entity_embeddings relation (L3.4).
|
|
3
|
-
*
|
|
4
|
-
* Uses an in-memory mock CozoDB (real cozo-node's run() is async/Promise-based,
|
|
5
|
-
* but the CozoDb interface expects synchronous returns).
|
|
6
|
-
*/
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
class MockCozoDb {
|
|
9
|
-
entities = [];
|
|
10
|
-
edges = [];
|
|
11
|
-
fileIndex = [];
|
|
12
|
-
rules = [];
|
|
13
|
-
driftOverlay = [];
|
|
14
|
-
communities = [];
|
|
15
|
-
corrections = [];
|
|
16
|
-
embeddings = new Map();
|
|
17
|
-
async run(query, params) {
|
|
18
|
-
// ── :create — no-op
|
|
19
|
-
if (query.includes(":create ")) {
|
|
20
|
-
return { rows: [] };
|
|
21
|
-
}
|
|
22
|
-
// ── :put entity_embeddings
|
|
23
|
-
if (query.includes(":put entity_embeddings")) {
|
|
24
|
-
const ek = this.extractInlineOrParam(query, params, "entity_key", 0);
|
|
25
|
-
const vj = this.extractInlineOrParam(query, params, "vector_json", 1);
|
|
26
|
-
const model = this.extractInlineOrParam(query, params, "model", 2);
|
|
27
|
-
const dims = this.extractInlineOrParam(query, params, "dimensions", 3);
|
|
28
|
-
const ca = this.extractInlineOrParam(query, params, "computed_at", 4);
|
|
29
|
-
this.embeddings.set(ek, {
|
|
30
|
-
entityKey: ek,
|
|
31
|
-
vectorJson: vj,
|
|
32
|
-
model,
|
|
33
|
-
dimensions: dims,
|
|
34
|
-
computedAt: ca,
|
|
35
|
-
});
|
|
36
|
-
return { rows: [] };
|
|
37
|
-
}
|
|
38
|
-
// ── :rm entity_embeddings
|
|
39
|
-
if (query.includes(":rm entity_embeddings")) {
|
|
40
|
-
const ek = this.extractInlineOrParam(query, params, "entity_key", 0);
|
|
41
|
-
this.embeddings.delete(ek);
|
|
42
|
-
return { rows: [] };
|
|
43
|
-
}
|
|
44
|
-
// ── :put (other relations) — no-op, data seeded via addXxx methods
|
|
45
|
-
if (query.includes(":put ")) {
|
|
46
|
-
return { rows: [] };
|
|
47
|
-
}
|
|
48
|
-
// ── Aggregation queries for getLocalProjectStats
|
|
49
|
-
// NOTE: More specific patterns (group-by) must come BEFORE simple counts
|
|
50
|
-
// to avoid false matches (e.g., "kind, count(key)" also contains "count(key)").
|
|
51
|
-
// kind, count(key) from entities (group by kind)
|
|
52
|
-
if (query.includes("kind, count(key)") && query.includes("*entities")) {
|
|
53
|
-
const grouped = new Map();
|
|
54
|
-
for (const e of this.entities) {
|
|
55
|
-
grouped.set(e.kind, (grouped.get(e.kind) ?? 0) + 1);
|
|
56
|
-
}
|
|
57
|
-
return { rows: Array.from(grouped.entries()) };
|
|
58
|
-
}
|
|
59
|
-
// type, count(from_key) from edges (group by type)
|
|
60
|
-
if (query.includes("type, count(from_key)") && query.includes("*edges")) {
|
|
61
|
-
const grouped = new Map();
|
|
62
|
-
for (const e of this.edges) {
|
|
63
|
-
grouped.set(e.type, (grouped.get(e.type) ?? 0) + 1);
|
|
64
|
-
}
|
|
65
|
-
return { rows: Array.from(grouped.entries()) };
|
|
66
|
-
}
|
|
67
|
-
// file_path, count(entity_key) from file_index (top files)
|
|
68
|
-
if (query.includes("file_path, count(entity_key)") &&
|
|
69
|
-
query.includes("*file_index")) {
|
|
70
|
-
const grouped = new Map();
|
|
71
|
-
for (const fi of this.fileIndex) {
|
|
72
|
-
grouped.set(fi.filePath, (grouped.get(fi.filePath) ?? 0) + 1);
|
|
73
|
-
}
|
|
74
|
-
const sorted = Array.from(grouped.entries()).sort((a, b) => b[1] - a[1]);
|
|
75
|
-
return { rows: sorted.slice(0, 10) };
|
|
76
|
-
}
|
|
77
|
-
// count_unique(file_path) from entities
|
|
78
|
-
if (query.includes("count_unique(file_path)") &&
|
|
79
|
-
query.includes("*entities")) {
|
|
80
|
-
const unique = new Set(this.entities.map((e) => e.filePath));
|
|
81
|
-
return { rows: [[unique.size]] };
|
|
82
|
-
}
|
|
83
|
-
// count(key) from entities
|
|
84
|
-
if (query.includes("count(key)") && query.includes("*entities")) {
|
|
85
|
-
return { rows: [[this.entities.length]] };
|
|
86
|
-
}
|
|
87
|
-
// count(from_key) from edges
|
|
88
|
-
if (query.includes("count(from_key)") && query.includes("*edges")) {
|
|
89
|
-
return { rows: [[this.edges.length]] };
|
|
90
|
-
}
|
|
91
|
-
// count(key) from rules where enabled = true
|
|
92
|
-
if (query.includes("count(key)") &&
|
|
93
|
-
query.includes("*rules") &&
|
|
94
|
-
query.includes("enabled")) {
|
|
95
|
-
return { rows: [[this.rules.filter((r) => r.enabled).length]] };
|
|
96
|
-
}
|
|
97
|
-
// count(key) from drift_overlay
|
|
98
|
-
if (query.includes("count(key)") && query.includes("*drift_overlay")) {
|
|
99
|
-
return { rows: [[this.driftOverlay.length]] };
|
|
100
|
-
}
|
|
101
|
-
// count(id) from communities
|
|
102
|
-
if (query.includes("count(id)") && query.includes("*communities")) {
|
|
103
|
-
return { rows: [[this.communities.length]] };
|
|
104
|
-
}
|
|
105
|
-
// count(entity_key) from corrections
|
|
106
|
-
if (query.includes("count(entity_key)") && query.includes("*corrections")) {
|
|
107
|
-
return { rows: [[this.corrections.length]] };
|
|
108
|
-
}
|
|
109
|
-
// ── entity_embeddings queries
|
|
110
|
-
// Read all: entity_key, vector_json, model, dimensions
|
|
111
|
-
if (query.includes("entity_key, vector_json, model, dimensions") &&
|
|
112
|
-
query.includes("*entity_embeddings")) {
|
|
113
|
-
const rows = [];
|
|
114
|
-
for (const emb of this.embeddings.values()) {
|
|
115
|
-
rows.push([emb.entityKey, emb.vectorJson, emb.model, emb.dimensions]);
|
|
116
|
-
}
|
|
117
|
-
return { rows };
|
|
118
|
-
}
|
|
119
|
-
// Count after delete
|
|
120
|
-
if (query.includes("entity_key") && query.includes("*entity_embeddings")) {
|
|
121
|
-
const rows = [];
|
|
122
|
-
for (const key of this.embeddings.keys()) {
|
|
123
|
-
rows.push([key]);
|
|
124
|
-
}
|
|
125
|
-
return { rows };
|
|
126
|
-
}
|
|
127
|
-
// Default
|
|
128
|
-
return { rows: [] };
|
|
129
|
-
}
|
|
130
|
-
extractInlineOrParam(query, params, _field, _index) {
|
|
131
|
-
// For the entity_embeddings CRUD test, values come from the <- [[...]] clause
|
|
132
|
-
// Parse the values from the inline array
|
|
133
|
-
const match = query.match(/<-\s*\[\[(.*?)\]\]/);
|
|
134
|
-
if (match) {
|
|
135
|
-
const values = this.parseInlineValues(match[1] ?? "");
|
|
136
|
-
if (_index < values.length)
|
|
137
|
-
return values[_index];
|
|
138
|
-
}
|
|
139
|
-
return params?.[_field] ?? "";
|
|
140
|
-
}
|
|
141
|
-
parseInlineValues(str) {
|
|
142
|
-
const values = [];
|
|
143
|
-
let i = 0;
|
|
144
|
-
while (i < str.length) {
|
|
145
|
-
// Skip whitespace and commas
|
|
146
|
-
while (i < str.length && (str[i] === " " || str[i] === ","))
|
|
147
|
-
i++;
|
|
148
|
-
if (i >= str.length)
|
|
149
|
-
break;
|
|
150
|
-
if (str[i] === '"') {
|
|
151
|
-
// String literal
|
|
152
|
-
i++;
|
|
153
|
-
let val = "";
|
|
154
|
-
while (i < str.length && str[i] !== '"') {
|
|
155
|
-
val += str[i];
|
|
156
|
-
i++;
|
|
157
|
-
}
|
|
158
|
-
i++; // skip closing quote
|
|
159
|
-
values.push(val);
|
|
160
|
-
}
|
|
161
|
-
else if (str[i] === "$") {
|
|
162
|
-
// Parameter reference — skip for now
|
|
163
|
-
while (i < str.length && str[i] !== "," && str[i] !== "]")
|
|
164
|
-
i++;
|
|
165
|
-
values.push(null); // placeholder
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
// Number or boolean
|
|
169
|
-
let val = "";
|
|
170
|
-
while (i < str.length &&
|
|
171
|
-
str[i] !== "," &&
|
|
172
|
-
str[i] !== "]" &&
|
|
173
|
-
str[i] !== " ") {
|
|
174
|
-
val += str[i];
|
|
175
|
-
i++;
|
|
176
|
-
}
|
|
177
|
-
if (val === "true")
|
|
178
|
-
values.push(true);
|
|
179
|
-
else if (val === "false")
|
|
180
|
-
values.push(false);
|
|
181
|
-
else
|
|
182
|
-
values.push(Number(val));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return values;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
function createTestDb() {
|
|
189
|
-
return new MockCozoDb();
|
|
190
|
-
}
|
|
191
|
-
function seedGraph(db) {
|
|
192
|
-
// Seed entities
|
|
193
|
-
db.entities.push({
|
|
194
|
-
key: "fn::auth::login",
|
|
195
|
-
kind: "function",
|
|
196
|
-
name: "login",
|
|
197
|
-
filePath: "src/auth.ts",
|
|
198
|
-
}, {
|
|
199
|
-
key: "fn::auth::logout",
|
|
200
|
-
kind: "function",
|
|
201
|
-
name: "logout",
|
|
202
|
-
filePath: "src/auth.ts",
|
|
203
|
-
}, {
|
|
204
|
-
key: "cls::auth::AuthService",
|
|
205
|
-
kind: "class",
|
|
206
|
-
name: "AuthService",
|
|
207
|
-
filePath: "src/auth.ts",
|
|
208
|
-
}, {
|
|
209
|
-
key: "fn::db::connect",
|
|
210
|
-
kind: "function",
|
|
211
|
-
name: "connect",
|
|
212
|
-
filePath: "src/db.ts",
|
|
213
|
-
}, {
|
|
214
|
-
key: "fn::db::query",
|
|
215
|
-
kind: "function",
|
|
216
|
-
name: "query",
|
|
217
|
-
filePath: "src/db.ts",
|
|
218
|
-
}, {
|
|
219
|
-
key: "file::src/auth.ts",
|
|
220
|
-
kind: "file",
|
|
221
|
-
name: "auth.ts",
|
|
222
|
-
filePath: "src/auth.ts",
|
|
223
|
-
}, {
|
|
224
|
-
key: "file::src/db.ts",
|
|
225
|
-
kind: "file",
|
|
226
|
-
name: "db.ts",
|
|
227
|
-
filePath: "src/db.ts",
|
|
228
|
-
});
|
|
229
|
-
// Seed file index
|
|
230
|
-
for (const e of db.entities) {
|
|
231
|
-
db.fileIndex.push({ filePath: e.filePath, entityKey: e.key });
|
|
232
|
-
}
|
|
233
|
-
// Seed edges
|
|
234
|
-
db.edges.push({ fromKey: "fn::auth::login", toKey: "fn::db::query", type: "calls" }, {
|
|
235
|
-
fromKey: "cls::auth::AuthService",
|
|
236
|
-
toKey: "fn::auth::login",
|
|
237
|
-
type: "contains",
|
|
238
|
-
}, {
|
|
239
|
-
fromKey: "cls::auth::AuthService",
|
|
240
|
-
toKey: "fn::auth::logout",
|
|
241
|
-
type: "contains",
|
|
242
|
-
}, { fromKey: "fn::db::connect", toKey: "fn::db::query", type: "calls" });
|
|
243
|
-
// Seed a rule
|
|
244
|
-
db.rules.push({ key: "rule::no-any", name: "no-any", enabled: true });
|
|
245
|
-
// Seed a community
|
|
246
|
-
db.communities.push({
|
|
247
|
-
id: 0,
|
|
248
|
-
label: "auth-cluster",
|
|
249
|
-
size: 3,
|
|
250
|
-
cohesion: 0.85,
|
|
251
|
-
});
|
|
252
|
-
// Seed a drift entity
|
|
253
|
-
db.driftOverlay.push({ key: "fn::auth::login" });
|
|
254
|
-
// Seed a correction
|
|
255
|
-
db.corrections.push({
|
|
256
|
-
entityKey: "fn::auth::login",
|
|
257
|
-
errorType: "null-check",
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
// ── L3.3: getLocalProjectStats ──────────────────────────────
|
|
261
|
-
describe("CozoGraphStore.getLocalProjectStats", () => {
|
|
262
|
-
it("returns aggregated stats from graph", async () => {
|
|
263
|
-
const db = createTestDb();
|
|
264
|
-
seedGraph(db);
|
|
265
|
-
const { CozoGraphStore } = await import("../intelligence/local-graph.js");
|
|
266
|
-
const store = await CozoGraphStore.create(db);
|
|
267
|
-
const stats = await store.getLocalProjectStats();
|
|
268
|
-
expect(stats.entityCount).toBe(7);
|
|
269
|
-
expect(stats.edgeCount).toBe(4);
|
|
270
|
-
expect(stats.fileCount).toBe(2); // src/auth.ts and src/db.ts
|
|
271
|
-
expect(stats.ruleCount).toBe(1);
|
|
272
|
-
expect(stats.driftCount).toBe(1);
|
|
273
|
-
expect(stats.communityCount).toBe(1);
|
|
274
|
-
expect(stats.correctionCount).toBe(1);
|
|
275
|
-
// Entity breakdown by kind
|
|
276
|
-
expect(stats.entityByKind.function).toBe(4);
|
|
277
|
-
expect(stats.entityByKind.class).toBe(1);
|
|
278
|
-
expect(stats.entityByKind.file).toBe(2);
|
|
279
|
-
// Edge breakdown by type
|
|
280
|
-
expect(stats.edgeByType.calls).toBe(2);
|
|
281
|
-
expect(stats.edgeByType.contains).toBe(2);
|
|
282
|
-
// Top files
|
|
283
|
-
expect(stats.topFiles.length).toBeGreaterThan(0);
|
|
284
|
-
// src/auth.ts has 4 entities (3 + 1 file), src/db.ts has 3
|
|
285
|
-
const authFile = stats.topFiles.find((f) => f.filePath === "src/auth.ts");
|
|
286
|
-
expect(authFile).toBeDefined();
|
|
287
|
-
expect(authFile?.entityCount).toBeGreaterThanOrEqual(3);
|
|
288
|
-
});
|
|
289
|
-
it("returns zeros for empty graph", async () => {
|
|
290
|
-
const db = createTestDb();
|
|
291
|
-
const { CozoGraphStore } = await import("../intelligence/local-graph.js");
|
|
292
|
-
const store = await CozoGraphStore.create(db);
|
|
293
|
-
const stats = await store.getLocalProjectStats();
|
|
294
|
-
expect(stats.entityCount).toBe(0);
|
|
295
|
-
expect(stats.edgeCount).toBe(0);
|
|
296
|
-
expect(stats.fileCount).toBe(0);
|
|
297
|
-
expect(stats.ruleCount).toBe(0);
|
|
298
|
-
expect(stats.driftCount).toBe(0);
|
|
299
|
-
expect(stats.entityByKind).toEqual({});
|
|
300
|
-
expect(stats.edgeByType).toEqual({});
|
|
301
|
-
expect(stats.topFiles).toEqual([]);
|
|
302
|
-
});
|
|
303
|
-
it("completes under 10ms for modest graph", async () => {
|
|
304
|
-
const db = createTestDb();
|
|
305
|
-
seedGraph(db);
|
|
306
|
-
const { CozoGraphStore } = await import("../intelligence/local-graph.js");
|
|
307
|
-
const store = await CozoGraphStore.create(db);
|
|
308
|
-
const t0 = performance.now();
|
|
309
|
-
await store.getLocalProjectStats();
|
|
310
|
-
const elapsed = performance.now() - t0;
|
|
311
|
-
expect(elapsed).toBeLessThan(50); // generous for CI; typically <5ms
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
// ── L3.4: entity_embeddings relation ────────────────────────
|
|
315
|
-
describe("entity_embeddings relation", () => {
|
|
316
|
-
it("exists in schema and supports CRUD", async () => {
|
|
317
|
-
const db = createTestDb();
|
|
318
|
-
// Insert via mock's run method (simulates CozoDB :put)
|
|
319
|
-
await db.run(`?[entity_key, vector_json, model, dimensions, computed_at] <- [["fn::test", "[0.1,0.2,0.3]", "test-model", 3, "2026-04-17"]]
|
|
320
|
-
:put entity_embeddings {entity_key => vector_json, model, dimensions, computed_at}`);
|
|
321
|
-
// Read
|
|
322
|
-
const result = await db.run("?[entity_key, vector_json, model, dimensions] := *entity_embeddings{entity_key, vector_json, model, dimensions}");
|
|
323
|
-
expect(result.rows).toHaveLength(1);
|
|
324
|
-
expect(result.rows[0]?.[0]).toBe("fn::test");
|
|
325
|
-
expect(result.rows[0]?.[2]).toBe("test-model");
|
|
326
|
-
expect(result.rows[0]?.[3]).toBe(3);
|
|
327
|
-
// Delete
|
|
328
|
-
await db.run(`?[entity_key] <- [["fn::test"]] :rm entity_embeddings {entity_key}`);
|
|
329
|
-
const afterDelete = await db.run("?[entity_key] := *entity_embeddings{entity_key}");
|
|
330
|
-
expect(afterDelete.rows).toHaveLength(0);
|
|
331
|
-
});
|
|
332
|
-
});
|