@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,111 +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("Ephemeral Sandbox (P5.6-ADV-02)", () => {
|
|
6
|
-
let tmpDir;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
tmpDir = path.join(os.tmpdir(), `unerr-ephemeral-${Date.now()}`);
|
|
9
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
13
|
-
});
|
|
14
|
-
it("connect --ephemeral creates ephemeral repo config", () => {
|
|
15
|
-
// Simulate ephemeral repo creation: a config with ephemeral: true and a TTL
|
|
16
|
-
const unerrDir = path.join(tmpDir, ".unerr");
|
|
17
|
-
fs.mkdirSync(unerrDir, { recursive: true });
|
|
18
|
-
const ttlHours = 24;
|
|
19
|
-
const expiresAt = new Date(Date.now() + ttlHours * 60 * 60 * 1000).toISOString();
|
|
20
|
-
const ephemeralConfig = {
|
|
21
|
-
repoId: "eph-repo-123",
|
|
22
|
-
orgId: "org-1",
|
|
23
|
-
branch: "main",
|
|
24
|
-
ephemeral: true,
|
|
25
|
-
expiresAt,
|
|
26
|
-
};
|
|
27
|
-
fs.writeFileSync(path.join(unerrDir, "config.json"), `${JSON.stringify(ephemeralConfig, null, 2)}\n`);
|
|
28
|
-
const raw = fs.readFileSync(path.join(unerrDir, "config.json"), "utf-8");
|
|
29
|
-
const config = JSON.parse(raw);
|
|
30
|
-
expect(config.ephemeral).toBe(true);
|
|
31
|
-
expect(config.repoId).toBe("eph-repo-123");
|
|
32
|
-
expect(new Date(config.expiresAt).getTime()).toBeGreaterThan(Date.now());
|
|
33
|
-
});
|
|
34
|
-
it("promote converts ephemeral to permanent config", () => {
|
|
35
|
-
const unerrDir = path.join(tmpDir, ".unerr");
|
|
36
|
-
fs.mkdirSync(unerrDir, { recursive: true });
|
|
37
|
-
// Start with ephemeral config
|
|
38
|
-
const ephemeralConfig = {
|
|
39
|
-
repoId: "eph-repo-456",
|
|
40
|
-
orgId: "org-1",
|
|
41
|
-
branch: "main",
|
|
42
|
-
ephemeral: true,
|
|
43
|
-
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
44
|
-
};
|
|
45
|
-
const configPath = path.join(unerrDir, "config.json");
|
|
46
|
-
fs.writeFileSync(configPath, JSON.stringify(ephemeralConfig, null, 2));
|
|
47
|
-
// Simulate "promote" — remove ephemeral flag and expiry
|
|
48
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
49
|
-
const config = JSON.parse(raw);
|
|
50
|
-
config.ephemeral = undefined;
|
|
51
|
-
config.expiresAt = undefined;
|
|
52
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
53
|
-
const promoted = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
54
|
-
expect(promoted.repoId).toBe("eph-repo-456");
|
|
55
|
-
expect(promoted.ephemeral).toBeUndefined();
|
|
56
|
-
expect(promoted.expiresAt).toBeUndefined();
|
|
57
|
-
});
|
|
58
|
-
it("ephemeral repo has TTL that can be checked", () => {
|
|
59
|
-
const ttlHours = 2;
|
|
60
|
-
const expiresAt = new Date(Date.now() + ttlHours * 60 * 60 * 1000);
|
|
61
|
-
// Check if expired
|
|
62
|
-
const isExpired = expiresAt.getTime() < Date.now();
|
|
63
|
-
expect(isExpired).toBe(false);
|
|
64
|
-
// Simulate an already-expired ephemeral repo
|
|
65
|
-
const pastExpiry = new Date(Date.now() - 1000);
|
|
66
|
-
const isPastExpired = pastExpiry.getTime() < Date.now();
|
|
67
|
-
expect(isPastExpired).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
it("ephemeral config includes all required fields", () => {
|
|
70
|
-
const ephemeralConfig = {
|
|
71
|
-
repoId: "eph-repo-789",
|
|
72
|
-
orgId: "org-1",
|
|
73
|
-
branch: "main",
|
|
74
|
-
ephemeral: true,
|
|
75
|
-
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
76
|
-
};
|
|
77
|
-
// Validate required fields
|
|
78
|
-
expect(ephemeralConfig.repoId).toBeTruthy();
|
|
79
|
-
expect(ephemeralConfig.orgId).toBeTruthy();
|
|
80
|
-
expect(ephemeralConfig.branch).toBeTruthy();
|
|
81
|
-
expect(ephemeralConfig.ephemeral).toBe(true);
|
|
82
|
-
expect(ephemeralConfig.expiresAt).toBeTruthy();
|
|
83
|
-
});
|
|
84
|
-
it("expired ephemeral repos are detectable for cleanup", () => {
|
|
85
|
-
// Simulate multiple ephemeral configs, some expired
|
|
86
|
-
const configs = [
|
|
87
|
-
{
|
|
88
|
-
repoId: "eph-1",
|
|
89
|
-
expiresAt: new Date(Date.now() + 1000).toISOString(),
|
|
90
|
-
ephemeral: true,
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
repoId: "eph-2",
|
|
94
|
-
expiresAt: new Date(Date.now() - 1000).toISOString(),
|
|
95
|
-
ephemeral: true,
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
repoId: "eph-3",
|
|
99
|
-
expiresAt: new Date(Date.now() - 5000).toISOString(),
|
|
100
|
-
ephemeral: true,
|
|
101
|
-
},
|
|
102
|
-
{ repoId: "perm-1", ephemeral: false, expiresAt: undefined },
|
|
103
|
-
];
|
|
104
|
-
const expired = configs.filter((c) => c.ephemeral &&
|
|
105
|
-
c.expiresAt &&
|
|
106
|
-
new Date(c.expiresAt).getTime() < Date.now());
|
|
107
|
-
expect(expired.length).toBe(2);
|
|
108
|
-
expect(expired.map((c) => c.repoId)).toContain("eph-2");
|
|
109
|
-
expect(expired.map((c) => c.repoId)).toContain("eph-3");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { estimateExplorationCost } from "../intelligence/exploration-cost.js";
|
|
3
|
-
describe("exploration-cost — MCP tool alias resolution", () => {
|
|
4
|
-
it("get_references resolves to find_callers rule (not DEFAULT_RULE)", () => {
|
|
5
|
-
const aliased = estimateExplorationCost("get_references", 5);
|
|
6
|
-
const target = estimateExplorationCost("find_callers", 5);
|
|
7
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
8
|
-
expect(aliased.counterfactualMethod).toBe(target.counterfactualMethod);
|
|
9
|
-
});
|
|
10
|
-
it("search_code resolves to search_entities rule", () => {
|
|
11
|
-
const aliased = estimateExplorationCost("search_code", 8);
|
|
12
|
-
const target = estimateExplorationCost("search_entities", 8);
|
|
13
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
14
|
-
expect(aliased.counterfactualMethod).toBe(target.counterfactualMethod);
|
|
15
|
-
});
|
|
16
|
-
it("get_conventions resolves to show_conventions rule", () => {
|
|
17
|
-
const aliased = estimateExplorationCost("get_conventions", 0, 12);
|
|
18
|
-
const target = estimateExplorationCost("show_conventions", 0, 12);
|
|
19
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
20
|
-
});
|
|
21
|
-
it("get_critical_nodes resolves to risk_assessment rule", () => {
|
|
22
|
-
const aliased = estimateExplorationCost("get_critical_nodes", 4);
|
|
23
|
-
const target = estimateExplorationCost("risk_assessment", 4);
|
|
24
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
25
|
-
});
|
|
26
|
-
it("get_cross_boundary_links resolves to risk_assessment rule", () => {
|
|
27
|
-
const aliased = estimateExplorationCost("get_cross_boundary_links", 6);
|
|
28
|
-
const target = estimateExplorationCost("risk_assessment", 6);
|
|
29
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
30
|
-
});
|
|
31
|
-
it("file_connections resolves to risk_assessment rule", () => {
|
|
32
|
-
const aliased = estimateExplorationCost("file_connections", 3);
|
|
33
|
-
const target = estimateExplorationCost("risk_assessment", 3);
|
|
34
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
35
|
-
});
|
|
36
|
-
it("file_outline falls through to DEFAULT_RULE (returns N entities, not 1)", () => {
|
|
37
|
-
// Intentionally NOT aliased: get_entity assumes 1 returned entity and yields
|
|
38
|
-
// negative savings as resultSize grows. DEFAULT_RULE scales positively with N.
|
|
39
|
-
const result = estimateExplorationCost("file_outline", 15);
|
|
40
|
-
// DEFAULT_RULE: 150 + ceil(15/2)*400 = 150 + 8*400 = 3350
|
|
41
|
-
expect(result.tokensWithout).toBe(150 + 8 * 400);
|
|
42
|
-
expect(result.counterfactualMethod).toBe("generic file exploration");
|
|
43
|
-
});
|
|
44
|
-
it("get_imports resolves to find_callers rule", () => {
|
|
45
|
-
const aliased = estimateExplorationCost("get_imports", 10);
|
|
46
|
-
const target = estimateExplorationCost("find_callers", 10);
|
|
47
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
48
|
-
});
|
|
49
|
-
it("get_test_coverage resolves to find_callers rule", () => {
|
|
50
|
-
const aliased = estimateExplorationCost("get_test_coverage", 7);
|
|
51
|
-
const target = estimateExplorationCost("find_callers", 7);
|
|
52
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
53
|
-
});
|
|
54
|
-
it("get_file resolves to get_entity rule", () => {
|
|
55
|
-
const aliased = estimateExplorationCost("get_file", 1);
|
|
56
|
-
const target = estimateExplorationCost("get_entity", 1);
|
|
57
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
58
|
-
});
|
|
59
|
-
it("file_read falls through to DEFAULT_RULE (has its own dedicated mechanism)", () => {
|
|
60
|
-
// file_read is tracked via mechanism="file_read" with full-file counterfactual.
|
|
61
|
-
// Letting it also alias graph_query would double-count. Keep it on DEFAULT_RULE
|
|
62
|
-
// so graph_query events fire (when positive) without inheriting get_entity math.
|
|
63
|
-
const result = estimateExplorationCost("file_read", 4);
|
|
64
|
-
// DEFAULT_RULE: 150 + ceil(4/2)*400 = 150 + 2*400 = 950
|
|
65
|
-
expect(result.tokensWithout).toBe(150 + 2 * 400);
|
|
66
|
-
expect(result.counterfactualMethod).toBe("generic file exploration");
|
|
67
|
-
});
|
|
68
|
-
it("get_project_stats resolves to health_grade rule", () => {
|
|
69
|
-
const aliased = estimateExplorationCost("get_project_stats", 0, 50);
|
|
70
|
-
const target = estimateExplorationCost("health_grade", 0, 50);
|
|
71
|
-
expect(aliased.tokensWithout).toBe(target.tokensWithout);
|
|
72
|
-
});
|
|
73
|
-
it("legacy keys still resolve directly (not regressed by alias layer)", () => {
|
|
74
|
-
// blast_radius and find_callers are still used internally — must remain stable
|
|
75
|
-
const blast = estimateExplorationCost("blast_radius", 4);
|
|
76
|
-
expect(blast.counterfactualMethod).toContain("read each caller file");
|
|
77
|
-
const callers = estimateExplorationCost("find_callers", 4);
|
|
78
|
-
expect(callers.counterfactualMethod).toContain("grep + read");
|
|
79
|
-
});
|
|
80
|
-
it("unknown tool name still falls through to DEFAULT_RULE", () => {
|
|
81
|
-
const result = estimateExplorationCost("totally_unknown_tool", 4);
|
|
82
|
-
// DEFAULT_RULE: fileMultiplier = ceil(resultSize / 2) = 2, tokensPerFile = 400, baseTokens = 150
|
|
83
|
-
expect(result.tokensWithout).toBe(150 + 2 * 400);
|
|
84
|
-
expect(result.counterfactualMethod).toBe("generic file exploration");
|
|
85
|
-
});
|
|
86
|
-
it("aliased tools beat DEFAULT_RULE on at least one shape", () => {
|
|
87
|
-
// search_code with 10 results: search_entities gives 200 + min(10,20)*500 = 5200
|
|
88
|
-
// DEFAULT_RULE would give 150 + ceil(10/2)*400 = 2150
|
|
89
|
-
// Confirms the alias is actually changing the calculation
|
|
90
|
-
const aliased = estimateExplorationCost("search_code", 10);
|
|
91
|
-
expect(aliased.tokensWithout).toBe(200 + 10 * 500);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, 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 { generateFromCausalBridge, generateFromConventions, generateFromNegativeKnowledge, generateFromSessionAnalysis, } from "../intelligence/fact-generator.js";
|
|
6
|
-
import { initFactsSchema } from "../intelligence/facts-schema.js";
|
|
7
|
-
import { TemporalFactStore } from "../intelligence/temporal-facts.js";
|
|
8
|
-
async function createTestDb() {
|
|
9
|
-
const cozoModule = await import("cozo-node");
|
|
10
|
-
const CozoDbConstructor = cozoModule.default
|
|
11
|
-
? cozoModule.default.CozoDb
|
|
12
|
-
: cozoModule.CozoDb;
|
|
13
|
-
return new CozoDbConstructor("mem", "");
|
|
14
|
-
}
|
|
15
|
-
function makeSessionRecord(overrides = {}) {
|
|
16
|
-
return {
|
|
17
|
-
session_id: `sess-${Math.random().toString(36).slice(2, 8)}`,
|
|
18
|
-
written_at: new Date().toISOString(),
|
|
19
|
-
started_at: new Date(Date.now() - 3600000).toISOString(),
|
|
20
|
-
ended_at: new Date().toISOString(),
|
|
21
|
-
duration_ms: 3600000,
|
|
22
|
-
tool_calls: 20,
|
|
23
|
-
chains: 5,
|
|
24
|
-
files_modified: ["src/proxy.ts", "src/auth.ts"],
|
|
25
|
-
entities_touched: ["src/proxy.ts::startProxy"],
|
|
26
|
-
tools_used: { get_function: 10, file_read: 10 },
|
|
27
|
-
feature_areas: ["src/proxy"],
|
|
28
|
-
facts_recorded: 0,
|
|
29
|
-
facts_surfaced: [],
|
|
30
|
-
revert_count: 0,
|
|
31
|
-
rot_score: 0.1,
|
|
32
|
-
token_estimate: 10000,
|
|
33
|
-
branch: "main",
|
|
34
|
-
...overrides,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
describe("fact-generator", () => {
|
|
38
|
-
let db;
|
|
39
|
-
let store;
|
|
40
|
-
let testDir;
|
|
41
|
-
beforeEach(async () => {
|
|
42
|
-
db = await createTestDb();
|
|
43
|
-
await initFactsSchema(db);
|
|
44
|
-
store = TemporalFactStore.fromDb(db);
|
|
45
|
-
testDir = join(tmpdir(), `unerr-factgen-${Date.now()}`);
|
|
46
|
-
mkdirSync(testDir, { recursive: true });
|
|
47
|
-
});
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
try {
|
|
50
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// best-effort
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
describe("generateFromConventions", () => {
|
|
57
|
-
it("creates facts for conventions with >70% adherence", async () => {
|
|
58
|
-
const conventions = [
|
|
59
|
-
{
|
|
60
|
-
key: "conv-1",
|
|
61
|
-
kind: "naming",
|
|
62
|
-
name: "camelCase functions",
|
|
63
|
-
detail: "^[a-z][a-zA-Z]*$",
|
|
64
|
-
exemplarKeys: ["src/proxy/startProxy.ts"],
|
|
65
|
-
frequency: 20,
|
|
66
|
-
confidence: 0.85,
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
const result = await generateFromConventions(store, conventions);
|
|
70
|
-
expect(result.created).toBe(1);
|
|
71
|
-
expect(result.source).toBe("convention_detector");
|
|
72
|
-
expect(result.details[0]).toContain("camelCase functions");
|
|
73
|
-
});
|
|
74
|
-
it("skips conventions below 70% adherence", async () => {
|
|
75
|
-
const conventions = [
|
|
76
|
-
{
|
|
77
|
-
key: "conv-low",
|
|
78
|
-
kind: "naming",
|
|
79
|
-
name: "snake_case",
|
|
80
|
-
detail: ".*",
|
|
81
|
-
exemplarKeys: [],
|
|
82
|
-
frequency: 10,
|
|
83
|
-
confidence: 0.5,
|
|
84
|
-
},
|
|
85
|
-
];
|
|
86
|
-
const result = await generateFromConventions(store, conventions);
|
|
87
|
-
expect(result.created).toBe(0);
|
|
88
|
-
});
|
|
89
|
-
it("skips conventions with too few entities", async () => {
|
|
90
|
-
const conventions = [
|
|
91
|
-
{
|
|
92
|
-
key: "conv-few",
|
|
93
|
-
kind: "structure",
|
|
94
|
-
name: "rare pattern",
|
|
95
|
-
detail: ".*",
|
|
96
|
-
exemplarKeys: [],
|
|
97
|
-
frequency: 2,
|
|
98
|
-
confidence: 0.95,
|
|
99
|
-
},
|
|
100
|
-
];
|
|
101
|
-
const result = await generateFromConventions(store, conventions);
|
|
102
|
-
expect(result.created).toBe(0);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
describe("generateFromNegativeKnowledge", () => {
|
|
106
|
-
it("creates negative facts from correction entries", async () => {
|
|
107
|
-
const corrections = [
|
|
108
|
-
{
|
|
109
|
-
id: "corr-1",
|
|
110
|
-
entityKey: "src/auth.ts",
|
|
111
|
-
pattern: "modified-then-reverted",
|
|
112
|
-
reason: "File src/auth.ts was modified and reverted. The approach was incorrect.",
|
|
113
|
-
detectedAt: new Date().toISOString(),
|
|
114
|
-
rewindEntryId: "rewind-001",
|
|
115
|
-
confidence: 0.7,
|
|
116
|
-
},
|
|
117
|
-
];
|
|
118
|
-
const result = await generateFromNegativeKnowledge(store, corrections);
|
|
119
|
-
expect(result.created).toBe(1);
|
|
120
|
-
expect(result.source).toBe("negative_knowledge");
|
|
121
|
-
const recalled = await store.recallByScope("src/auth.ts", 0);
|
|
122
|
-
expect(recalled.length).toBe(1);
|
|
123
|
-
expect(recalled[0].fact_type).toBe("negative");
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
describe("generateFromCausalBridge", () => {
|
|
127
|
-
it("creates episodic facts for survived changes", async () => {
|
|
128
|
-
const events = [
|
|
129
|
-
{
|
|
130
|
-
session_id: "sess-1",
|
|
131
|
-
entity_key: "src/proxy.ts::startProxy",
|
|
132
|
-
action: "survived",
|
|
133
|
-
branch: "main",
|
|
134
|
-
timestamp: Date.now() - 86400000,
|
|
135
|
-
},
|
|
136
|
-
];
|
|
137
|
-
const result = await generateFromCausalBridge(store, events);
|
|
138
|
-
expect(result.created).toBe(1);
|
|
139
|
-
expect(result.details[0]).toContain("survived");
|
|
140
|
-
});
|
|
141
|
-
it("creates negative facts for reverted changes", async () => {
|
|
142
|
-
const events = [
|
|
143
|
-
{
|
|
144
|
-
session_id: "sess-2",
|
|
145
|
-
entity_key: "src/config.ts",
|
|
146
|
-
action: "reverted",
|
|
147
|
-
branch: "main",
|
|
148
|
-
timestamp: Date.now() - 86400000,
|
|
149
|
-
},
|
|
150
|
-
];
|
|
151
|
-
const result = await generateFromCausalBridge(store, events);
|
|
152
|
-
expect(result.created).toBe(1);
|
|
153
|
-
expect(result.details[0]).toContain("reverted");
|
|
154
|
-
const recalled = await store.recallByScope("src/config.ts", 0);
|
|
155
|
-
expect(recalled[0].fact_type).toBe("negative");
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
describe("generateFromSessionAnalysis", () => {
|
|
159
|
-
it("detects hot files from multiple sessions", async () => {
|
|
160
|
-
const sessionsDir = join(testDir, "sessions");
|
|
161
|
-
mkdirSync(sessionsDir, { recursive: true });
|
|
162
|
-
for (let i = 0; i < 5; i++) {
|
|
163
|
-
const record = makeSessionRecord({
|
|
164
|
-
session_id: `s${i}`,
|
|
165
|
-
files_modified: ["src/proxy.ts", "src/auth.ts", `src/other${i}.ts`],
|
|
166
|
-
});
|
|
167
|
-
writeFileSync(join(sessionsDir, `s${i}.jsonl`), `${JSON.stringify(record)}\n`, "utf-8");
|
|
168
|
-
}
|
|
169
|
-
const result = await generateFromSessionAnalysis(store, testDir);
|
|
170
|
-
expect(result.created).toBeGreaterThan(0);
|
|
171
|
-
expect(result.details.some((d) => d.includes("src/proxy.ts"))).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
it("detects high-revert files", async () => {
|
|
174
|
-
const sessionsDir = join(testDir, "sessions");
|
|
175
|
-
mkdirSync(sessionsDir, { recursive: true });
|
|
176
|
-
for (let i = 0; i < 5; i++) {
|
|
177
|
-
const record = makeSessionRecord({
|
|
178
|
-
session_id: `s${i}`,
|
|
179
|
-
files_modified: ["src/fragile.ts"],
|
|
180
|
-
revert_count: i < 3 ? 1 : 0, // 3 out of 5 sessions have reverts
|
|
181
|
-
});
|
|
182
|
-
writeFileSync(join(sessionsDir, `s${i}.jsonl`), `${JSON.stringify(record)}\n`, "utf-8");
|
|
183
|
-
}
|
|
184
|
-
const result = await generateFromSessionAnalysis(store, testDir);
|
|
185
|
-
const fragileDetail = result.details.find((d) => d.includes("src/fragile.ts"));
|
|
186
|
-
expect(fragileDetail).toBeDefined();
|
|
187
|
-
});
|
|
188
|
-
it("returns empty when fewer than 3 sessions", async () => {
|
|
189
|
-
const sessionsDir = join(testDir, "sessions");
|
|
190
|
-
mkdirSync(sessionsDir, { recursive: true });
|
|
191
|
-
const record = makeSessionRecord();
|
|
192
|
-
writeFileSync(join(sessionsDir, "s1.jsonl"), `${JSON.stringify(record)}\n`, "utf-8");
|
|
193
|
-
const result = await generateFromSessionAnalysis(store, testDir);
|
|
194
|
-
expect(result.created).toBe(0);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint R.10: File-as-L0 Graph Enrichment Tests
|
|
3
|
-
*
|
|
4
|
-
* Verifies:
|
|
5
|
-
* - File entities created for every indexed file
|
|
6
|
-
* - Contains edges from file→entity guarantee zero orphans
|
|
7
|
-
* - File→file import edges created from cross-file resolution
|
|
8
|
-
* - Co-change edge computation from git history
|
|
9
|
-
* - Community detection handles weighted contains edges
|
|
10
|
-
* - Blast radius falls back to file siblings when no callers exist
|
|
11
|
-
* - File-level queries (getFileEntities, getFileNeighbors) work correctly
|
|
12
|
-
*/
|
|
13
|
-
import { describe, expect, it } from "vitest";
|
|
14
|
-
import { resolveCrossFileEdges } from "../intelligence/indexer/cross-file-resolver.js";
|
|
15
|
-
import { computeCoChangeEdges } from "../intelligence/indexer/git-cochange.js";
|
|
16
|
-
import { resolveImportSourceToFile } from "../intelligence/local-indexer.js";
|
|
17
|
-
describe("Sprint R: File-as-L0 Graph Enrichment", () => {
|
|
18
|
-
describe("R.3: Cross-file resolver emits file import edges", () => {
|
|
19
|
-
it("produces file→file import edges alongside entity resolution", () => {
|
|
20
|
-
const fileResults = new Map([
|
|
21
|
-
[
|
|
22
|
-
"src/a.ts",
|
|
23
|
-
{
|
|
24
|
-
entities: [
|
|
25
|
-
{
|
|
26
|
-
key: "src/a.ts::fnA",
|
|
27
|
-
name: "fnA",
|
|
28
|
-
exported: true,
|
|
29
|
-
kind: "function",
|
|
30
|
-
file_path: "src/a.ts",
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
edges: [
|
|
34
|
-
{
|
|
35
|
-
from_key: "src/a.ts::fnA",
|
|
36
|
-
to_key: "unresolved:fnB",
|
|
37
|
-
type: "calls",
|
|
38
|
-
file_path: "src/a.ts",
|
|
39
|
-
line: 5,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
imports: [
|
|
43
|
-
{
|
|
44
|
-
source: "src/b.ts",
|
|
45
|
-
symbols: ["fnB"],
|
|
46
|
-
isDefault: false,
|
|
47
|
-
isNamespace: false,
|
|
48
|
-
localName: undefined,
|
|
49
|
-
line: 1,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
[
|
|
55
|
-
"src/b.ts",
|
|
56
|
-
{
|
|
57
|
-
entities: [
|
|
58
|
-
{
|
|
59
|
-
key: "src/b.ts::fnB",
|
|
60
|
-
name: "fnB",
|
|
61
|
-
exported: true,
|
|
62
|
-
kind: "function",
|
|
63
|
-
file_path: "src/b.ts",
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
edges: [],
|
|
67
|
-
imports: [],
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
]);
|
|
71
|
-
const result = resolveCrossFileEdges(fileResults);
|
|
72
|
-
// Should have file→file import edges
|
|
73
|
-
expect(result.fileImportEdges.length).toBeGreaterThan(0);
|
|
74
|
-
const fileEdge = result.fileImportEdges.find((e) => e.from_key === "file:src/a.ts" && e.to_key === "file:src/b.ts");
|
|
75
|
-
expect(fileEdge).toBeDefined();
|
|
76
|
-
expect(fileEdge.type).toBe("imports");
|
|
77
|
-
});
|
|
78
|
-
it("deduplicates file import edges (multiple imports from same file)", () => {
|
|
79
|
-
const fileResults = new Map([
|
|
80
|
-
[
|
|
81
|
-
"src/a.ts",
|
|
82
|
-
{
|
|
83
|
-
entities: [
|
|
84
|
-
{
|
|
85
|
-
key: "src/a.ts::fnA",
|
|
86
|
-
name: "fnA",
|
|
87
|
-
exported: true,
|
|
88
|
-
kind: "function",
|
|
89
|
-
file_path: "src/a.ts",
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
edges: [],
|
|
93
|
-
imports: [
|
|
94
|
-
{
|
|
95
|
-
source: "src/b.ts",
|
|
96
|
-
symbols: ["fnB"],
|
|
97
|
-
isDefault: false,
|
|
98
|
-
isNamespace: false,
|
|
99
|
-
localName: undefined,
|
|
100
|
-
line: 1,
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
source: "src/b.ts",
|
|
104
|
-
symbols: ["fnC"],
|
|
105
|
-
isDefault: false,
|
|
106
|
-
isNamespace: false,
|
|
107
|
-
localName: undefined,
|
|
108
|
-
line: 1,
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
[
|
|
114
|
-
"src/b.ts",
|
|
115
|
-
{
|
|
116
|
-
entities: [
|
|
117
|
-
{
|
|
118
|
-
key: "src/b.ts::fnB",
|
|
119
|
-
name: "fnB",
|
|
120
|
-
exported: true,
|
|
121
|
-
kind: "function",
|
|
122
|
-
file_path: "src/b.ts",
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
key: "src/b.ts::fnC",
|
|
126
|
-
name: "fnC",
|
|
127
|
-
exported: true,
|
|
128
|
-
kind: "function",
|
|
129
|
-
file_path: "src/b.ts",
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
edges: [],
|
|
133
|
-
imports: [],
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
]);
|
|
137
|
-
const result = resolveCrossFileEdges(fileResults);
|
|
138
|
-
// Only one file→file edge despite two import declarations
|
|
139
|
-
const fileEdges = result.fileImportEdges.filter((e) => e.from_key === "file:src/a.ts" && e.to_key === "file:src/b.ts");
|
|
140
|
-
expect(fileEdges).toHaveLength(1);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
describe("R.4: Git co-change computation", () => {
|
|
144
|
-
it("computes co-change edges from the current repo", () => {
|
|
145
|
-
// This test runs against the actual unerr-cli repo
|
|
146
|
-
const edges = computeCoChangeEdges(process.cwd(), 50, 10, 2);
|
|
147
|
-
// Should find at least some co-change pairs in a real repo
|
|
148
|
-
expect(edges.length).toBeGreaterThanOrEqual(0);
|
|
149
|
-
// If there are results, verify structure
|
|
150
|
-
if (edges.length > 0) {
|
|
151
|
-
const first = edges[0];
|
|
152
|
-
expect(first.from_file).toBeTruthy();
|
|
153
|
-
expect(first.to_file).toBeTruthy();
|
|
154
|
-
expect(first.co_occurrences).toBeGreaterThanOrEqual(2);
|
|
155
|
-
expect(first.correlation).toBeGreaterThan(0);
|
|
156
|
-
expect(first.correlation).toBeLessThanOrEqual(1);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
it("returns empty for non-git directory", () => {
|
|
160
|
-
const edges = computeCoChangeEdges("/tmp", 50, 10, 2);
|
|
161
|
-
expect(edges).toHaveLength(0);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
describe("R.1/R.2: File entity and contains edge verification (integration)", () => {
|
|
165
|
-
it("ingestIndexResult creates file entities for each unique file path", async () => {
|
|
166
|
-
// This is a unit-level check of the ingest function's contract
|
|
167
|
-
// Full integration test requires CozoDB instance
|
|
168
|
-
const { basename } = await import("node:path");
|
|
169
|
-
// Verify the file key convention
|
|
170
|
-
const filePath = "src/utils/exec.ts";
|
|
171
|
-
const fileKey = `file:${filePath}`;
|
|
172
|
-
expect(fileKey).toBe("file:src/utils/exec.ts");
|
|
173
|
-
expect(basename(filePath)).toBe("exec.ts");
|
|
174
|
-
});
|
|
175
|
-
it("file entity keys use file: prefix to avoid collisions", () => {
|
|
176
|
-
// Entity keys use filepath::name format (e.g., "src/a.ts::fnA")
|
|
177
|
-
// File keys use file:filepath format (e.g., "file:src/a.ts")
|
|
178
|
-
const entityKey = "src/a.ts::fnA";
|
|
179
|
-
const fileKey = "file:src/a.ts";
|
|
180
|
-
// They must not collide
|
|
181
|
-
expect(entityKey).not.toBe(fileKey);
|
|
182
|
-
expect(fileKey.startsWith("file:")).toBe(true);
|
|
183
|
-
expect(entityKey.startsWith("file:")).toBe(false);
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
describe("R.3 Multi-language: resolveImportSourceToFile", () => {
|
|
187
|
-
const projectFiles = new Set([
|
|
188
|
-
"src/utils/helper.ts",
|
|
189
|
-
"src/utils/index.ts",
|
|
190
|
-
"src/commands/status.ts",
|
|
191
|
-
"app/models/user.py",
|
|
192
|
-
"app/models/__init__.py",
|
|
193
|
-
"app/services/auth.py",
|
|
194
|
-
"pkg/handlers/auth.go",
|
|
195
|
-
"pkg/models/user.go",
|
|
196
|
-
"internal/db/connection.go",
|
|
197
|
-
"com/example/service/UserService.java",
|
|
198
|
-
"com/example/models/User.java",
|
|
199
|
-
"src/module/sub.rs",
|
|
200
|
-
"src/module/mod.rs",
|
|
201
|
-
"src/lib.rs",
|
|
202
|
-
"lib/models/user.rb",
|
|
203
|
-
"lib/services/auth.rb",
|
|
204
|
-
"Services/UserService.cs",
|
|
205
|
-
"Models/User.cs",
|
|
206
|
-
]);
|
|
207
|
-
it("resolves TS/JS relative imports", () => {
|
|
208
|
-
expect(resolveImportSourceToFile("./helper", "src/utils/status.ts", projectFiles)).toBe("src/utils/helper.ts");
|
|
209
|
-
expect(resolveImportSourceToFile("../commands/status", "src/utils/helper.ts", projectFiles)).toBe("src/commands/status.ts");
|
|
210
|
-
// Index file resolution (../utils from commands/ → src/utils/index.ts)
|
|
211
|
-
expect(resolveImportSourceToFile("../utils", "src/commands/status.ts", projectFiles)).toBe("src/utils/index.ts");
|
|
212
|
-
});
|
|
213
|
-
it("resolves Python dotted module paths", () => {
|
|
214
|
-
expect(resolveImportSourceToFile("models.user", "app/services/auth.py", projectFiles)).toBe("app/models/user.py");
|
|
215
|
-
// __init__.py as directory index
|
|
216
|
-
expect(resolveImportSourceToFile("models", "app/services/auth.py", projectFiles)).toBe("app/models/__init__.py");
|
|
217
|
-
});
|
|
218
|
-
it("resolves Go package paths by suffix matching", () => {
|
|
219
|
-
expect(resolveImportSourceToFile("github.com/myapp/pkg/handlers", "cmd/main.go", projectFiles)).toBe("pkg/handlers/auth.go");
|
|
220
|
-
});
|
|
221
|
-
it("resolves Java dot-separated package paths", () => {
|
|
222
|
-
expect(resolveImportSourceToFile("com.example.models.User", "com/example/service/UserService.java", projectFiles)).toBe("com/example/models/User.java");
|
|
223
|
-
});
|
|
224
|
-
it("resolves Rust crate paths", () => {
|
|
225
|
-
expect(resolveImportSourceToFile("crate::module::sub", "src/lib.rs", projectFiles)).toBe("src/module/sub.rs");
|
|
226
|
-
// Rust mod.rs as directory index
|
|
227
|
-
expect(resolveImportSourceToFile("crate::module", "src/lib.rs", projectFiles)).toBe("src/module/mod.rs");
|
|
228
|
-
});
|
|
229
|
-
it("resolves Ruby require paths", () => {
|
|
230
|
-
expect(resolveImportSourceToFile("models/user", "lib/services/auth.rb", projectFiles)).toBe("lib/models/user.rb");
|
|
231
|
-
});
|
|
232
|
-
it("resolves C# namespace paths", () => {
|
|
233
|
-
expect(resolveImportSourceToFile("Models.User", "Services/UserService.cs", projectFiles)).toBe("Models/User.cs");
|
|
234
|
-
});
|
|
235
|
-
it("returns null for external/unresolvable imports", () => {
|
|
236
|
-
// npm package
|
|
237
|
-
expect(resolveImportSourceToFile("lodash", "src/utils/helper.ts", projectFiles)).toBeNull();
|
|
238
|
-
// Python stdlib
|
|
239
|
-
expect(resolveImportSourceToFile("os.path", "app/services/auth.py", projectFiles)).toBeNull();
|
|
240
|
-
// Go stdlib
|
|
241
|
-
expect(resolveImportSourceToFile("fmt", "pkg/handlers/auth.go", projectFiles)).toBeNull();
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
});
|