@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,422 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for search-index.ts — tokenization, index building, and IDF-weighted search ranking.
|
|
3
|
-
*
|
|
4
|
-
* Tests validate:
|
|
5
|
-
* - tokenize(): camelCase/PascalCase/snake_case splitting algorithm
|
|
6
|
-
* - buildSearchIndex(): entity name tokenization + IDF weight computation
|
|
7
|
-
* - searchLocal(): IDF-weighted scoring (rare tokens rank higher than common ones)
|
|
8
|
-
*/
|
|
9
|
-
import { describe, expect, it } from "vitest";
|
|
10
|
-
import { buildSearchIndex, searchLocal, tokenize, } from "../intelligence/search-index.js";
|
|
11
|
-
// ── In-memory CozoDB mock ─────────────────────────────────────────
|
|
12
|
-
// Implements enough Datalog semantics for search_tokens + token_doc_frequency queries.
|
|
13
|
-
function createMockDb() {
|
|
14
|
-
const entities = [];
|
|
15
|
-
const searchTokens = [];
|
|
16
|
-
const tokenDocFreq = []; // [token, doc_count, idf]
|
|
17
|
-
return {
|
|
18
|
-
async run(query, params) {
|
|
19
|
-
// Handle :create (schema init)
|
|
20
|
-
if (query.includes(":create"))
|
|
21
|
-
return { rows: [] };
|
|
22
|
-
// Handle :put search_tokens
|
|
23
|
-
if (query.includes(":put search_tokens")) {
|
|
24
|
-
const token = params?.token;
|
|
25
|
-
const key = params?.key;
|
|
26
|
-
const exists = searchTokens.some(([t, k]) => t === token && k === key);
|
|
27
|
-
if (!exists)
|
|
28
|
-
searchTokens.push([token, key]);
|
|
29
|
-
return { rows: [] };
|
|
30
|
-
}
|
|
31
|
-
// Handle :put token_doc_frequency
|
|
32
|
-
if (query.includes(":put token_doc_frequency")) {
|
|
33
|
-
const token = params?.token;
|
|
34
|
-
const dc = params?.dc;
|
|
35
|
-
const idf = params?.idf;
|
|
36
|
-
// Upsert
|
|
37
|
-
const idx = tokenDocFreq.findIndex(([t]) => t === token);
|
|
38
|
-
if (idx >= 0) {
|
|
39
|
-
tokenDocFreq[idx] = [token, dc, idf];
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
tokenDocFreq.push([token, dc, idf]);
|
|
43
|
-
}
|
|
44
|
-
return { rows: [] };
|
|
45
|
-
}
|
|
46
|
-
// Handle :put entities
|
|
47
|
-
if (query.includes(":put entities")) {
|
|
48
|
-
const key = params?.key;
|
|
49
|
-
const kind = params?.kind;
|
|
50
|
-
const name = params?.name;
|
|
51
|
-
const fp = params?.fp;
|
|
52
|
-
entities.push([key, kind, name, fp, 0, "", ""]);
|
|
53
|
-
return { rows: [] };
|
|
54
|
-
}
|
|
55
|
-
// Handle entity read for buildSearchIndex
|
|
56
|
-
if (query.includes("*entities") && query.includes("?[key, name]")) {
|
|
57
|
-
return { rows: entities.map(([key, _k, name]) => [key, name]) };
|
|
58
|
-
}
|
|
59
|
-
// Handle searchLocal Datalog query (IDF-weighted token intersection)
|
|
60
|
-
if (query.includes("search_tokens") && query.includes("matched")) {
|
|
61
|
-
// Parse token list from query — format: [["token1"], ["token2"], ...]
|
|
62
|
-
const tokenMatches = [...query.matchAll(/\["([^"]+)"\]/g)];
|
|
63
|
-
if (tokenMatches.length === 0)
|
|
64
|
-
return { rows: [] };
|
|
65
|
-
const queryTokens = tokenMatches.map((m) => m[1]);
|
|
66
|
-
// Build IDF lookup
|
|
67
|
-
const idfMap = new Map();
|
|
68
|
-
for (const [token, _dc, idf] of tokenDocFreq) {
|
|
69
|
-
idfMap.set(token, idf);
|
|
70
|
-
}
|
|
71
|
-
// Sum IDF weights per entity key
|
|
72
|
-
const entityScores = new Map();
|
|
73
|
-
for (const [token, entityKey] of searchTokens) {
|
|
74
|
-
if (queryTokens.includes(token)) {
|
|
75
|
-
const idf = idfMap.get(token) ?? 0;
|
|
76
|
-
entityScores.set(entityKey, (entityScores.get(entityKey) ?? 0) + idf);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// Join with entities, sort by score desc
|
|
80
|
-
const results = [];
|
|
81
|
-
for (const [entityKey, score] of entityScores) {
|
|
82
|
-
const entity = entities.find(([k]) => k === entityKey);
|
|
83
|
-
if (entity) {
|
|
84
|
-
results.push([entityKey, score, entity[2], entity[1], entity[3]]);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
results.sort((a, b) => b[1] - a[1]);
|
|
88
|
-
// Parse limit
|
|
89
|
-
const limitMatch = query.match(/:limit (\d+)/);
|
|
90
|
-
const limit = limitMatch?.[1] ? Number.parseInt(limitMatch[1], 10) : 20;
|
|
91
|
-
return { rows: results.slice(0, limit) };
|
|
92
|
-
}
|
|
93
|
-
return { rows: [] };
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
async function seedEntities(db, items) {
|
|
98
|
-
for (const item of items) {
|
|
99
|
-
await db.run("?[key, kind, name, fp] <- [[$key, $kind, $name, $fp]] :put entities { key => kind, name, file_path: fp }", { key: item.key, kind: item.kind, name: item.name, fp: item.file_path });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// ── tokenize ──────────────────────────────────────────────────────
|
|
103
|
-
describe("search-index", () => {
|
|
104
|
-
describe("tokenize", () => {
|
|
105
|
-
it("splits camelCase", () => {
|
|
106
|
-
expect(tokenize("doSomething")).toEqual(["do", "something"]);
|
|
107
|
-
});
|
|
108
|
-
it("splits PascalCase", () => {
|
|
109
|
-
expect(tokenize("MyClassName")).toEqual(["my", "class", "name"]);
|
|
110
|
-
});
|
|
111
|
-
it("splits snake_case", () => {
|
|
112
|
-
expect(tokenize("get_user_name")).toEqual(["get", "user", "name"]);
|
|
113
|
-
});
|
|
114
|
-
it("splits kebab-case", () => {
|
|
115
|
-
expect(tokenize("my-component")).toEqual(["my", "component"]);
|
|
116
|
-
});
|
|
117
|
-
it("handles mixed patterns", () => {
|
|
118
|
-
const tokens = tokenize("getUserName_v2");
|
|
119
|
-
expect(tokens).toContain("get");
|
|
120
|
-
expect(tokens).toContain("user");
|
|
121
|
-
expect(tokens).toContain("name");
|
|
122
|
-
expect(tokens).toContain("v2");
|
|
123
|
-
});
|
|
124
|
-
it("deduplicates tokens", () => {
|
|
125
|
-
const tokens = tokenize("test_test");
|
|
126
|
-
expect(tokens).toEqual(["test"]);
|
|
127
|
-
});
|
|
128
|
-
it("lowercases all tokens", () => {
|
|
129
|
-
const tokens = tokenize("HTTPRequest");
|
|
130
|
-
for (const t of tokens) {
|
|
131
|
-
expect(t).toBe(t.toLowerCase());
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
it("handles single word", () => {
|
|
135
|
-
expect(tokenize("hello")).toEqual(["hello"]);
|
|
136
|
-
});
|
|
137
|
-
it("handles empty string", () => {
|
|
138
|
-
expect(tokenize("")).toEqual([]);
|
|
139
|
-
});
|
|
140
|
-
it("splits consecutive uppercase (acronyms)", () => {
|
|
141
|
-
const tokens = tokenize("XMLHTTPRequest");
|
|
142
|
-
expect(tokens).toContain("request");
|
|
143
|
-
// Acronym splitting: XML, HTTP split from Request
|
|
144
|
-
expect(tokens.length).toBeGreaterThanOrEqual(2);
|
|
145
|
-
});
|
|
146
|
-
it("handles numeric suffixes", () => {
|
|
147
|
-
const tokens = tokenize("processV2");
|
|
148
|
-
expect(tokens).toContain("process");
|
|
149
|
-
expect(tokens).toContain("v2");
|
|
150
|
-
});
|
|
151
|
-
it("handles dots and special chars", () => {
|
|
152
|
-
const tokens = tokenize("file.name.ext");
|
|
153
|
-
expect(tokens).toContain("file");
|
|
154
|
-
expect(tokens).toContain("name");
|
|
155
|
-
expect(tokens).toContain("ext");
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
// ── buildSearchIndex ────────────────────────────────────────────
|
|
159
|
-
describe("buildSearchIndex", () => {
|
|
160
|
-
it("tokenizes entity names and stores all tokens", async () => {
|
|
161
|
-
const db = createMockDb();
|
|
162
|
-
await seedEntities(db, [
|
|
163
|
-
{
|
|
164
|
-
key: "fn1",
|
|
165
|
-
kind: "function",
|
|
166
|
-
name: "processPayment",
|
|
167
|
-
file_path: "src/billing.ts",
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
key: "cls1",
|
|
171
|
-
kind: "class",
|
|
172
|
-
name: "UserService",
|
|
173
|
-
file_path: "src/user.ts",
|
|
174
|
-
},
|
|
175
|
-
]);
|
|
176
|
-
await buildSearchIndex(db);
|
|
177
|
-
// processPayment → ["process", "payment"]
|
|
178
|
-
// UserService → ["user", "service"]
|
|
179
|
-
// Verify by searching — tokens should be indexed
|
|
180
|
-
const results = await searchLocal(db, "process");
|
|
181
|
-
expect(results).toHaveLength(1);
|
|
182
|
-
expect(results[0]?.key).toBe("fn1");
|
|
183
|
-
expect(results[0]?.name).toBe("processPayment");
|
|
184
|
-
});
|
|
185
|
-
it("handles empty entity set without error", async () => {
|
|
186
|
-
const db = createMockDb();
|
|
187
|
-
// No entities seeded
|
|
188
|
-
await expect(buildSearchIndex(db)).resolves.not.toThrow();
|
|
189
|
-
const results = await searchLocal(db, "anything");
|
|
190
|
-
expect(results).toHaveLength(0);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
// ── searchLocal — IDF-weighted ranking ─────────────────────────
|
|
194
|
-
describe("searchLocal", () => {
|
|
195
|
-
it("returns empty array for empty query", async () => {
|
|
196
|
-
const db = createMockDb();
|
|
197
|
-
await seedEntities(db, [
|
|
198
|
-
{ key: "fn1", kind: "function", name: "doStuff", file_path: "a.ts" },
|
|
199
|
-
]);
|
|
200
|
-
await buildSearchIndex(db);
|
|
201
|
-
const results = await searchLocal(db, "");
|
|
202
|
-
expect(results).toHaveLength(0);
|
|
203
|
-
});
|
|
204
|
-
it("ranks rare tokens higher than common tokens (IDF)", async () => {
|
|
205
|
-
const db = createMockDb();
|
|
206
|
-
await seedEntities(db, [
|
|
207
|
-
// "get" appears in 3 entities (very common → low IDF)
|
|
208
|
-
{ key: "fn1", kind: "function", name: "getUser", file_path: "a.ts" },
|
|
209
|
-
{ key: "fn2", kind: "function", name: "getOrder", file_path: "b.ts" },
|
|
210
|
-
{
|
|
211
|
-
key: "fn3",
|
|
212
|
-
kind: "function",
|
|
213
|
-
name: "getPayment",
|
|
214
|
-
file_path: "c.ts",
|
|
215
|
-
},
|
|
216
|
-
// "process" appears in 1 entity (rare → high IDF)
|
|
217
|
-
{
|
|
218
|
-
key: "fn4",
|
|
219
|
-
kind: "function",
|
|
220
|
-
name: "processPayment",
|
|
221
|
-
file_path: "d.ts",
|
|
222
|
-
},
|
|
223
|
-
]);
|
|
224
|
-
await buildSearchIndex(db);
|
|
225
|
-
// Search for "payment" — appears in fn3 (getPayment) and fn4 (processPayment)
|
|
226
|
-
// fn4 has "process" (rare, IDF=ln(4/1)) + "payment" (doc_count=2, IDF=ln(4/2))
|
|
227
|
-
// fn3 has "get" (common, IDF=ln(4/3)) + "payment" (IDF=ln(4/2))
|
|
228
|
-
// fn4 should rank higher because "process" is rarer than "get"
|
|
229
|
-
const results = await searchLocal(db, "processPayment");
|
|
230
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
231
|
-
expect(results[0]?.key).toBe("fn4");
|
|
232
|
-
});
|
|
233
|
-
it("IDF scores: single rare token outranks single common token", async () => {
|
|
234
|
-
const db = createMockDb();
|
|
235
|
-
await seedEntities(db, [
|
|
236
|
-
// "handle" in 5 entities
|
|
237
|
-
{
|
|
238
|
-
key: "fn1",
|
|
239
|
-
kind: "function",
|
|
240
|
-
name: "handleRequest",
|
|
241
|
-
file_path: "a.ts",
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
key: "fn2",
|
|
245
|
-
kind: "function",
|
|
246
|
-
name: "handleResponse",
|
|
247
|
-
file_path: "b.ts",
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
key: "fn3",
|
|
251
|
-
kind: "function",
|
|
252
|
-
name: "handleError",
|
|
253
|
-
file_path: "c.ts",
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
key: "fn4",
|
|
257
|
-
kind: "function",
|
|
258
|
-
name: "handleTimeout",
|
|
259
|
-
file_path: "d.ts",
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
key: "fn5",
|
|
263
|
-
kind: "function",
|
|
264
|
-
name: "handleRetry",
|
|
265
|
-
file_path: "e.ts",
|
|
266
|
-
},
|
|
267
|
-
// "orchestrate" in 1 entity (unique)
|
|
268
|
-
{
|
|
269
|
-
key: "fn6",
|
|
270
|
-
kind: "function",
|
|
271
|
-
name: "orchestrateWorkflow",
|
|
272
|
-
file_path: "f.ts",
|
|
273
|
-
},
|
|
274
|
-
]);
|
|
275
|
-
await buildSearchIndex(db);
|
|
276
|
-
// Searching "orchestrate" should return fn6 with higher IDF than searching "handle"
|
|
277
|
-
const rareResults = await searchLocal(db, "orchestrate");
|
|
278
|
-
const commonResults = await searchLocal(db, "handle");
|
|
279
|
-
expect(rareResults).toHaveLength(1);
|
|
280
|
-
expect(commonResults).toHaveLength(5);
|
|
281
|
-
// The rare token's IDF weight should be higher
|
|
282
|
-
expect(rareResults[0]?.score).toBeGreaterThan(commonResults[0]?.score ?? 0);
|
|
283
|
-
});
|
|
284
|
-
it("multi-token query sums IDF weights correctly", async () => {
|
|
285
|
-
const db = createMockDb();
|
|
286
|
-
await seedEntities(db, [
|
|
287
|
-
// Matches 1 token: "user"
|
|
288
|
-
{ key: "fn1", kind: "function", name: "getUser", file_path: "a.ts" },
|
|
289
|
-
// Matches 2 tokens: "user" + "name"
|
|
290
|
-
{
|
|
291
|
-
key: "fn2",
|
|
292
|
-
kind: "function",
|
|
293
|
-
name: "getUserName",
|
|
294
|
-
file_path: "b.ts",
|
|
295
|
-
},
|
|
296
|
-
// Matches 0 tokens
|
|
297
|
-
{
|
|
298
|
-
key: "fn3",
|
|
299
|
-
kind: "function",
|
|
300
|
-
name: "processPayment",
|
|
301
|
-
file_path: "c.ts",
|
|
302
|
-
},
|
|
303
|
-
]);
|
|
304
|
-
await buildSearchIndex(db);
|
|
305
|
-
const results = await searchLocal(db, "userName");
|
|
306
|
-
// fn2 matches both tokens (sum of 2 IDF weights), fn1 matches one
|
|
307
|
-
expect(results.length).toBeGreaterThanOrEqual(2);
|
|
308
|
-
expect(results[0]?.key).toBe("fn2");
|
|
309
|
-
expect(results[0]?.score).toBeGreaterThan(results[1]?.score ?? 0);
|
|
310
|
-
expect(results[1]?.key).toBe("fn1");
|
|
311
|
-
});
|
|
312
|
-
it("respects limit parameter", async () => {
|
|
313
|
-
const db = createMockDb();
|
|
314
|
-
// Use names that all share the same token "action"
|
|
315
|
-
const entities = Array.from({ length: 30 }, (_, i) => ({
|
|
316
|
-
key: `fn${i}`,
|
|
317
|
-
kind: "function",
|
|
318
|
-
name: "handleAction",
|
|
319
|
-
file_path: `src/f${i}.ts`,
|
|
320
|
-
}));
|
|
321
|
-
await seedEntities(db, entities);
|
|
322
|
-
await buildSearchIndex(db);
|
|
323
|
-
const results = await searchLocal(db, "action", 5);
|
|
324
|
-
expect(results).toHaveLength(5);
|
|
325
|
-
});
|
|
326
|
-
it("uses default limit of 20", async () => {
|
|
327
|
-
const db = createMockDb();
|
|
328
|
-
const entities = Array.from({ length: 30 }, (_, i) => ({
|
|
329
|
-
key: `fn${i}`,
|
|
330
|
-
kind: "function",
|
|
331
|
-
name: "handleRequest",
|
|
332
|
-
file_path: `src/f${i}.ts`,
|
|
333
|
-
}));
|
|
334
|
-
await seedEntities(db, entities);
|
|
335
|
-
await buildSearchIndex(db);
|
|
336
|
-
const results = await searchLocal(db, "handle");
|
|
337
|
-
expect(results.length).toBeLessThanOrEqual(20);
|
|
338
|
-
});
|
|
339
|
-
it("returns correct entity metadata in results", async () => {
|
|
340
|
-
const db = createMockDb();
|
|
341
|
-
await seedEntities(db, [
|
|
342
|
-
{
|
|
343
|
-
key: "cls1",
|
|
344
|
-
kind: "class",
|
|
345
|
-
name: "PaymentService",
|
|
346
|
-
file_path: "src/billing/payment.ts",
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
key: "fn1",
|
|
350
|
-
kind: "function",
|
|
351
|
-
name: "handleRequest",
|
|
352
|
-
file_path: "src/server.ts",
|
|
353
|
-
},
|
|
354
|
-
]);
|
|
355
|
-
await buildSearchIndex(db);
|
|
356
|
-
const results = await searchLocal(db, "payment");
|
|
357
|
-
expect(results).toHaveLength(1);
|
|
358
|
-
expect(results[0]?.key).toBe("cls1");
|
|
359
|
-
expect(results[0]?.name).toBe("PaymentService");
|
|
360
|
-
expect(results[0]?.kind).toBe("class");
|
|
361
|
-
expect(results[0]?.file_path).toBe("src/billing/payment.ts");
|
|
362
|
-
// IDF > 0 because "payment" appears in 1 of 2 entities
|
|
363
|
-
expect(results[0]?.score).toBeGreaterThan(0);
|
|
364
|
-
});
|
|
365
|
-
it("matches across different naming conventions", async () => {
|
|
366
|
-
const db = createMockDb();
|
|
367
|
-
await seedEntities(db, [
|
|
368
|
-
{
|
|
369
|
-
key: "fn1",
|
|
370
|
-
kind: "function",
|
|
371
|
-
name: "getUserById",
|
|
372
|
-
file_path: "a.ts",
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
key: "fn2",
|
|
376
|
-
kind: "function",
|
|
377
|
-
name: "get_user_by_id",
|
|
378
|
-
file_path: "b.py",
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
key: "fn3",
|
|
382
|
-
kind: "function",
|
|
383
|
-
name: "GetUserById",
|
|
384
|
-
file_path: "c.go",
|
|
385
|
-
},
|
|
386
|
-
]);
|
|
387
|
-
await buildSearchIndex(db);
|
|
388
|
-
// All three should match "user" query regardless of casing convention
|
|
389
|
-
const results = await searchLocal(db, "user");
|
|
390
|
-
expect(results).toHaveLength(3);
|
|
391
|
-
});
|
|
392
|
-
it("single-token query matches all entities with that token", async () => {
|
|
393
|
-
const db = createMockDb();
|
|
394
|
-
await seedEntities(db, [
|
|
395
|
-
{
|
|
396
|
-
key: "fn1",
|
|
397
|
-
kind: "function",
|
|
398
|
-
name: "processOrder",
|
|
399
|
-
file_path: "a.ts",
|
|
400
|
-
},
|
|
401
|
-
{
|
|
402
|
-
key: "fn2",
|
|
403
|
-
kind: "function",
|
|
404
|
-
name: "processPayment",
|
|
405
|
-
file_path: "b.ts",
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
key: "fn3",
|
|
409
|
-
kind: "function",
|
|
410
|
-
name: "handleOrder",
|
|
411
|
-
file_path: "c.ts",
|
|
412
|
-
},
|
|
413
|
-
]);
|
|
414
|
-
await buildSearchIndex(db);
|
|
415
|
-
const results = await searchLocal(db, "process");
|
|
416
|
-
expect(results).toHaveLength(2);
|
|
417
|
-
const keys = results.map((r) => r.key);
|
|
418
|
-
expect(keys).toContain("fn1");
|
|
419
|
-
expect(keys).toContain("fn2");
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
});
|