agentikit 0.0.8 → 0.0.12
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 +135 -117
- package/dist/index.d.ts +13 -3
- package/dist/index.js +7 -1
- package/dist/src/asset-spec.d.ts +2 -0
- package/dist/src/asset-spec.js +22 -3
- package/dist/src/asset-type-handler.d.ts +27 -0
- package/dist/src/asset-type-handler.js +33 -0
- package/dist/src/cli.js +335 -100
- package/dist/src/common.d.ts +6 -1
- package/dist/src/common.js +18 -4
- package/dist/src/config-cli.d.ts +9 -0
- package/dist/src/config-cli.js +473 -0
- package/dist/src/config.d.ts +25 -6
- package/dist/src/config.js +188 -28
- package/dist/src/db.d.ts +46 -0
- package/dist/src/db.js +299 -0
- package/dist/src/embedder.js +12 -7
- package/dist/src/github.d.ts +4 -0
- package/dist/src/github.js +19 -0
- package/dist/src/handlers/agent-handler.d.ts +2 -0
- package/dist/src/handlers/agent-handler.js +26 -0
- package/dist/src/handlers/command-handler.d.ts +2 -0
- package/dist/src/handlers/command-handler.js +23 -0
- package/dist/src/handlers/index.d.ts +6 -0
- package/dist/src/handlers/index.js +23 -0
- package/dist/src/handlers/knowledge-handler.d.ts +2 -0
- package/dist/src/handlers/knowledge-handler.js +56 -0
- package/dist/src/handlers/markdown-helpers.d.ts +7 -0
- package/dist/src/handlers/markdown-helpers.js +15 -0
- package/dist/src/handlers/script-handler.d.ts +2 -0
- package/dist/src/handlers/script-handler.js +78 -0
- package/dist/src/handlers/skill-handler.d.ts +2 -0
- package/dist/src/handlers/skill-handler.js +30 -0
- package/dist/src/handlers/tool-handler.d.ts +2 -0
- package/dist/src/handlers/tool-handler.js +58 -0
- package/dist/src/indexer.d.ts +1 -23
- package/dist/src/indexer.js +162 -155
- package/dist/src/init.d.ts +2 -2
- package/dist/src/init.js +21 -9
- package/dist/src/llm.js +4 -3
- package/dist/src/metadata.d.ts +1 -1
- package/dist/src/metadata.js +22 -64
- package/dist/src/origin-resolve.d.ts +19 -0
- package/dist/src/origin-resolve.js +53 -0
- package/dist/src/registry-install.d.ts +11 -0
- package/dist/src/registry-install.js +315 -0
- package/dist/src/registry-resolve.d.ts +3 -0
- package/dist/src/registry-resolve.js +299 -0
- package/dist/src/registry-search.d.ts +27 -0
- package/dist/src/registry-search.js +263 -0
- package/dist/src/registry-types.d.ts +62 -0
- package/dist/src/registry-types.js +1 -0
- package/dist/src/stash-add.d.ts +4 -0
- package/dist/src/stash-add.js +59 -0
- package/dist/src/stash-clone.d.ts +22 -0
- package/dist/src/stash-clone.js +83 -0
- package/dist/src/stash-ref.d.ts +27 -3
- package/dist/src/stash-ref.js +63 -24
- package/dist/src/stash-registry.d.ts +18 -0
- package/dist/src/stash-registry.js +221 -0
- package/dist/src/stash-resolve.js +3 -0
- package/dist/src/stash-search.d.ts +3 -1
- package/dist/src/stash-search.js +357 -138
- package/dist/src/stash-show.d.ts +1 -1
- package/dist/src/stash-show.js +28 -89
- package/dist/src/stash-source.d.ts +24 -0
- package/dist/src/stash-source.js +81 -0
- package/dist/src/stash-types.d.ts +175 -1
- package/dist/src/stash.d.ts +9 -1
- package/dist/src/stash.js +5 -0
- package/dist/src/tool-runner.d.ts +1 -1
- package/dist/src/tool-runner.js +18 -5
- package/package.json +7 -2
- package/src/asset-spec.ts +20 -4
- package/src/asset-type-handler.ts +77 -0
- package/src/cli.ts +354 -103
- package/src/common.ts +23 -5
- package/src/config-cli.ts +499 -0
- package/src/config.ts +218 -37
- package/src/db.ts +411 -0
- package/src/embedder.ts +22 -11
- package/src/github.ts +21 -0
- package/src/handlers/agent-handler.ts +32 -0
- package/src/handlers/command-handler.ts +29 -0
- package/src/handlers/index.ts +25 -0
- package/src/handlers/knowledge-handler.ts +62 -0
- package/src/handlers/markdown-helpers.ts +19 -0
- package/src/handlers/script-handler.ts +92 -0
- package/src/handlers/skill-handler.ts +37 -0
- package/src/handlers/tool-handler.ts +71 -0
- package/src/indexer.ts +208 -187
- package/src/init.ts +17 -9
- package/src/llm.ts +4 -3
- package/src/metadata.ts +21 -65
- package/src/origin-resolve.ts +67 -0
- package/src/registry-install.ts +361 -0
- package/src/registry-resolve.ts +341 -0
- package/src/registry-search.ts +335 -0
- package/src/registry-types.ts +72 -0
- package/src/stash-add.ts +63 -0
- package/src/stash-clone.ts +127 -0
- package/src/stash-ref.ts +84 -26
- package/src/stash-registry.ts +259 -0
- package/src/stash-resolve.ts +3 -0
- package/src/stash-search.ts +425 -155
- package/src/stash-show.ts +33 -82
- package/src/stash-source.ts +103 -0
- package/src/stash-types.ts +186 -1
- package/src/stash.ts +23 -0
- package/src/tool-runner.ts +18 -5
- package/dist/src/similarity.d.ts +0 -34
- package/src/similarity.ts +0 -271
package/dist/src/stash-search.js
CHANGED
|
@@ -1,192 +1,329 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { hasErrnoCode, resolveStashDir } from "./common";
|
|
4
3
|
import { ASSET_TYPES, TYPE_DIRS, deriveCanonicalAssetName } from "./asset-spec";
|
|
5
|
-
import { loadSearchIndex, buildSearchText } from "./indexer";
|
|
6
|
-
import { TfIdfAdapter } from "./similarity";
|
|
7
|
-
import { buildToolInfo } from "./tool-runner";
|
|
8
4
|
import { walkStash } from "./walker";
|
|
9
|
-
import {
|
|
5
|
+
import { makeAssetRef } from "./stash-ref";
|
|
10
6
|
import { loadConfig } from "./config";
|
|
7
|
+
import { searchRegistry } from "./registry-search";
|
|
8
|
+
import { openDatabase, closeDatabase, getDbPath, getMeta, searchFts, searchVec, getAllEntries, getEntryCount, getEntryById, isVecAvailable, } from "./db";
|
|
9
|
+
import { tryGetHandler } from "./asset-type-handler";
|
|
10
|
+
import { resolveStashSources, findSourceForPath } from "./stash-source";
|
|
11
11
|
const DEFAULT_LIMIT = 20;
|
|
12
12
|
export async function agentikitSearch(input) {
|
|
13
13
|
const t0 = Date.now();
|
|
14
|
-
const query = input.query.trim()
|
|
14
|
+
const query = input.query.trim();
|
|
15
|
+
const normalizedQuery = query.toLowerCase();
|
|
15
16
|
const searchType = input.type ?? "any";
|
|
16
17
|
const limit = normalizeLimit(input.limit);
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
const usageMode = parseSearchUsageMode(input.usage);
|
|
19
|
+
const source = parseSearchSource(input.source);
|
|
20
|
+
const sources = resolveStashSources();
|
|
21
|
+
const stashDir = sources[0].path;
|
|
22
|
+
const localResult = source === "registry"
|
|
23
|
+
? undefined
|
|
24
|
+
: await searchLocal({
|
|
25
|
+
query: normalizedQuery,
|
|
26
|
+
searchType,
|
|
27
|
+
limit,
|
|
28
|
+
usageMode,
|
|
29
|
+
stashDir,
|
|
30
|
+
sources,
|
|
31
|
+
});
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
const registryResult = source === "local"
|
|
34
|
+
? undefined
|
|
35
|
+
: await searchRegistry(query, { limit, registryUrls: config.registryUrls });
|
|
36
|
+
if (source === "local") {
|
|
37
|
+
return {
|
|
38
|
+
stashDir,
|
|
39
|
+
source,
|
|
40
|
+
hits: localResult?.hits ?? [],
|
|
41
|
+
usageGuide: localResult?.usageGuide,
|
|
42
|
+
tip: localResult?.tip,
|
|
43
|
+
timing: { totalMs: Date.now() - t0, rankMs: localResult?.rankMs, embedMs: localResult?.embedMs },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const registryHits = (registryResult?.hits ?? []).map((hit) => {
|
|
47
|
+
const installRef = hit.source === "npm" ? `npm:${hit.ref}` : `github:${hit.ref}`;
|
|
48
|
+
return {
|
|
49
|
+
hitSource: "registry",
|
|
50
|
+
type: "registry",
|
|
51
|
+
name: hit.title,
|
|
52
|
+
id: hit.id,
|
|
53
|
+
registrySource: hit.source,
|
|
54
|
+
ref: hit.ref,
|
|
55
|
+
description: hit.description,
|
|
56
|
+
homepage: hit.homepage,
|
|
57
|
+
score: hit.score,
|
|
58
|
+
metadata: hit.metadata,
|
|
59
|
+
curated: hit.curated,
|
|
60
|
+
installRef,
|
|
61
|
+
installCmd: `akm add ${installRef}`,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
if (source === "registry") {
|
|
65
|
+
const hits = registryHits.slice(0, limit);
|
|
34
66
|
return {
|
|
35
67
|
stashDir,
|
|
68
|
+
source,
|
|
36
69
|
hits,
|
|
37
|
-
tip: hits.length === 0 ? "No matching
|
|
38
|
-
|
|
70
|
+
tip: hits.length === 0 ? "No matching registry entries were found." : undefined,
|
|
71
|
+
warnings: registryResult?.warnings.length ? registryResult.warnings : undefined,
|
|
72
|
+
timing: { totalMs: Date.now() - t0 },
|
|
39
73
|
};
|
|
40
74
|
}
|
|
41
|
-
|
|
75
|
+
const mergedHits = mergeSearchHits(localResult?.hits ?? [], registryHits, limit);
|
|
76
|
+
return {
|
|
77
|
+
stashDir,
|
|
78
|
+
source,
|
|
79
|
+
hits: mergedHits,
|
|
80
|
+
usageGuide: localResult?.usageGuide,
|
|
81
|
+
tip: mergedHits.length === 0 ? "No matching stash assets or registry entries were found." : undefined,
|
|
82
|
+
warnings: registryResult?.warnings.length ? registryResult.warnings : undefined,
|
|
83
|
+
timing: { totalMs: Date.now() - t0 },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function searchLocal(input) {
|
|
87
|
+
const { query, searchType, limit, usageMode, stashDir, sources } = input;
|
|
88
|
+
const config = loadConfig();
|
|
89
|
+
const allStashDirs = sources.map((s) => s.path);
|
|
90
|
+
// Try to open the database
|
|
91
|
+
const dbPath = getDbPath();
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync(dbPath)) {
|
|
94
|
+
const embeddingDim = config.embedding?.dimension;
|
|
95
|
+
const db = openDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined);
|
|
96
|
+
try {
|
|
97
|
+
const entryCount = getEntryCount(db);
|
|
98
|
+
const storedStashDir = getMeta(db, "stashDir");
|
|
99
|
+
if (entryCount > 0 && storedStashDir === stashDir) {
|
|
100
|
+
const { hits, usageGuide, embedMs, rankMs } = await searchDatabase(db, query, searchType, limit, stashDir, allStashDirs, config, usageMode, sources);
|
|
101
|
+
return {
|
|
102
|
+
hits,
|
|
103
|
+
usageGuide,
|
|
104
|
+
tip: hits.length === 0 ? "No matching stash assets were found. Try running 'akm index' to rebuild." : undefined,
|
|
105
|
+
embedMs,
|
|
106
|
+
rankMs,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
closeDatabase(db);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.warn("Search index unavailable, falling back to substring search:", error instanceof Error ? error.message : String(error));
|
|
117
|
+
}
|
|
42
118
|
const hits = allStashDirs
|
|
43
|
-
.flatMap((dir) => substringSearch(query, searchType, limit, dir))
|
|
119
|
+
.flatMap((dir) => substringSearch(query, searchType, limit, dir, sources))
|
|
44
120
|
.slice(0, limit);
|
|
121
|
+
const usageGuide = shouldIncludeUsageGuide(usageMode) ? buildUsageGuide(hits.map((hit) => hit.type), searchType) : undefined;
|
|
45
122
|
return {
|
|
46
|
-
stashDir,
|
|
47
123
|
hits,
|
|
124
|
+
usageGuide,
|
|
48
125
|
tip: hits.length === 0 ? "No matching stash assets were found. Try running 'akm index' to rebuild." : undefined,
|
|
49
|
-
timing: { totalMs: Date.now() - t0 },
|
|
50
126
|
};
|
|
51
127
|
}
|
|
52
|
-
// ──
|
|
53
|
-
async function
|
|
54
|
-
//
|
|
55
|
-
let candidates = index.entries;
|
|
56
|
-
if (searchType !== "any") {
|
|
57
|
-
candidates = candidates.filter((ie) => ie.entry.type === searchType);
|
|
58
|
-
}
|
|
59
|
-
if (candidates.length === 0)
|
|
60
|
-
return { hits: [] };
|
|
61
|
-
// Empty query: return all entries (no scoring needed)
|
|
128
|
+
// ── Database search ─────────────────────────────────────────────────────────
|
|
129
|
+
async function searchDatabase(db, query, searchType, limit, stashDir, allStashDirs, config, usageMode, sources) {
|
|
130
|
+
// Empty query: return all entries
|
|
62
131
|
if (!query) {
|
|
63
|
-
|
|
132
|
+
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
133
|
+
const allEntries = getAllEntries(db, typeFilter);
|
|
134
|
+
const selected = allEntries.slice(0, limit);
|
|
135
|
+
const hits = selected.map((ie) => buildDbHit({
|
|
136
|
+
entry: ie.entry,
|
|
137
|
+
path: ie.filePath,
|
|
138
|
+
score: 1,
|
|
139
|
+
query,
|
|
140
|
+
rankingMode: "fts",
|
|
141
|
+
defaultStashDir: stashDir,
|
|
142
|
+
allStashDirs,
|
|
143
|
+
sources,
|
|
144
|
+
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
145
|
+
}));
|
|
146
|
+
return {
|
|
147
|
+
hits,
|
|
148
|
+
usageGuide: shouldIncludeUsageGuide(usageMode)
|
|
149
|
+
? buildUsageGuideFromEntries(selected.map((e) => e.entry), searchType)
|
|
150
|
+
: undefined,
|
|
151
|
+
};
|
|
64
152
|
}
|
|
65
|
-
// Score
|
|
153
|
+
// Score using FTS5 (BM25) and optionally sqlite-vec
|
|
66
154
|
const tEmbed0 = Date.now();
|
|
67
|
-
const embeddingScores = await
|
|
155
|
+
const embeddingScores = await tryVecScores(db, query, limit * 3, config);
|
|
68
156
|
const embedMs = Date.now() - tEmbed0;
|
|
69
157
|
const tRank0 = Date.now();
|
|
70
|
-
const
|
|
158
|
+
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
159
|
+
const ftsResults = searchFts(db, query, limit * 3, typeFilter);
|
|
160
|
+
// Build score map from FTS results (normalize BM25 scores)
|
|
161
|
+
const ftsScoreMap = new Map();
|
|
162
|
+
for (const r of ftsResults) {
|
|
163
|
+
// BM25 returns negative scores (more negative = better match), normalize to 0-1
|
|
164
|
+
const absScore = Math.abs(r.bm25Score);
|
|
165
|
+
const normalized = absScore / (1 + absScore);
|
|
166
|
+
ftsScoreMap.set(r.id, { score: normalized, result: r });
|
|
167
|
+
}
|
|
168
|
+
// Blend scores
|
|
71
169
|
const scored = [];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
170
|
+
const seenIds = new Set();
|
|
171
|
+
// Process FTS results
|
|
172
|
+
for (const [id, { score: ftsScore, result }] of ftsScoreMap) {
|
|
173
|
+
seenIds.add(id);
|
|
174
|
+
const embScore = embeddingScores?.get(id);
|
|
76
175
|
if (embScore !== undefined) {
|
|
77
|
-
|
|
78
|
-
const blended = embScore * 0.7 + tfidfScore * 0.3;
|
|
176
|
+
const blended = embScore * 0.7 + ftsScore * 0.3;
|
|
79
177
|
if (blended > 0)
|
|
80
|
-
scored.push({
|
|
178
|
+
scored.push({ id, entry: result.entry, filePath: result.filePath, score: blended, rankingMode: "semantic" });
|
|
81
179
|
}
|
|
82
|
-
else if (
|
|
83
|
-
scored.push({
|
|
180
|
+
else if (ftsScore > 0) {
|
|
181
|
+
scored.push({ id, entry: result.entry, filePath: result.filePath, score: ftsScore, rankingMode: "fts" });
|
|
84
182
|
}
|
|
85
183
|
}
|
|
184
|
+
// Add vec-only results not already in FTS results
|
|
185
|
+
if (embeddingScores) {
|
|
186
|
+
for (const [id, embScore] of embeddingScores) {
|
|
187
|
+
if (seenIds.has(id))
|
|
188
|
+
continue;
|
|
189
|
+
const found = getEntryById(db, id);
|
|
190
|
+
if (found) {
|
|
191
|
+
if (typeFilter && found.entry.type !== typeFilter)
|
|
192
|
+
continue;
|
|
193
|
+
scored.push({
|
|
194
|
+
id,
|
|
195
|
+
entry: found.entry,
|
|
196
|
+
filePath: found.filePath,
|
|
197
|
+
score: embScore,
|
|
198
|
+
rankingMode: "semantic",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Apply boosts (tag, intent, name matches)
|
|
204
|
+
const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
205
|
+
for (const item of scored) {
|
|
206
|
+
const entry = item.entry;
|
|
207
|
+
// Tag boost
|
|
208
|
+
if (entry.tags) {
|
|
209
|
+
for (const tag of entry.tags) {
|
|
210
|
+
if (queryTokens.some((t) => tag.toLowerCase() === t)) {
|
|
211
|
+
item.score += 0.15;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Intent boost
|
|
216
|
+
if (entry.intents) {
|
|
217
|
+
for (const intent of entry.intents) {
|
|
218
|
+
const intentLower = intent.toLowerCase();
|
|
219
|
+
for (const token of queryTokens) {
|
|
220
|
+
if (intentLower.includes(token)) {
|
|
221
|
+
item.score += 0.12;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Name boost
|
|
228
|
+
const nameLower = entry.name.toLowerCase().replace(/[-_]/g, " ");
|
|
229
|
+
if (queryTokens.some((t) => nameLower.includes(t))) {
|
|
230
|
+
item.score += 0.1;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const item of scored) {
|
|
234
|
+
item.score = Math.min(item.score, 1.0);
|
|
235
|
+
}
|
|
86
236
|
scored.sort((a, b) => b.score - a.score);
|
|
87
237
|
const rankMs = Date.now() - tRank0;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
238
|
+
const selected = scored.slice(0, limit);
|
|
239
|
+
const hits = selected.map(({ entry, filePath, score, rankingMode }) => buildDbHit({
|
|
240
|
+
entry,
|
|
241
|
+
path: filePath,
|
|
242
|
+
score: Math.round(score * 1000) / 1000,
|
|
243
|
+
query,
|
|
244
|
+
rankingMode,
|
|
245
|
+
defaultStashDir: stashDir,
|
|
246
|
+
allStashDirs,
|
|
247
|
+
sources,
|
|
248
|
+
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
249
|
+
}));
|
|
250
|
+
return {
|
|
251
|
+
embedMs,
|
|
252
|
+
rankMs,
|
|
253
|
+
hits,
|
|
254
|
+
usageGuide: shouldIncludeUsageGuide(usageMode)
|
|
255
|
+
? buildUsageGuideFromEntries(selected.map((item) => item.entry), searchType)
|
|
256
|
+
: undefined,
|
|
257
|
+
};
|
|
97
258
|
}
|
|
98
|
-
// ──
|
|
99
|
-
async function
|
|
100
|
-
if (!config.semanticSearch)
|
|
259
|
+
// ── Vector scorer ───────────────────────────────────────────────────────────
|
|
260
|
+
async function tryVecScores(db, query, k, config) {
|
|
261
|
+
if (!config.semanticSearch || !isVecAvailable())
|
|
101
262
|
return null;
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
263
|
+
const hasEmbeddings = getMeta(db, "hasEmbeddings");
|
|
264
|
+
if (hasEmbeddings !== "1")
|
|
104
265
|
return null;
|
|
105
266
|
try {
|
|
106
|
-
const { embed
|
|
267
|
+
const { embed } = await import("./embedder.js");
|
|
107
268
|
const queryEmbedding = await embed(query, config.embedding);
|
|
269
|
+
const vecResults = searchVec(db, queryEmbedding, k);
|
|
108
270
|
const scores = new Map();
|
|
109
|
-
for (const
|
|
110
|
-
|
|
271
|
+
for (const { id, distance } of vecResults) {
|
|
272
|
+
// Convert L2 distance to cosine similarity (vectors are normalized)
|
|
273
|
+
const cosineSim = 1 - (distance * distance) / 2;
|
|
274
|
+
scores.set(id, Math.max(0, cosineSim));
|
|
111
275
|
}
|
|
112
276
|
return scores;
|
|
113
277
|
}
|
|
114
|
-
catch {
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.warn("Vector search failed, skipping:", error instanceof Error ? error.message : String(error));
|
|
115
280
|
return null;
|
|
116
281
|
}
|
|
117
282
|
}
|
|
118
|
-
// ── TF-IDF scorer ───────────────────────────────────────────────────────────
|
|
119
|
-
function computeTfidfScores(index, candidates, query, searchType) {
|
|
120
|
-
const candidateScoredEntries = toScoredEntries(candidates);
|
|
121
|
-
let adapter;
|
|
122
|
-
if (index.tfidf) {
|
|
123
|
-
const allScored = toScoredEntries(index.entries);
|
|
124
|
-
adapter = TfIdfAdapter.deserialize(index.tfidf, allScored);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
adapter = new TfIdfAdapter();
|
|
128
|
-
adapter.buildIndex(candidateScoredEntries);
|
|
129
|
-
}
|
|
130
|
-
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
131
|
-
const results = adapter.search(query, candidates.length, typeFilter);
|
|
132
|
-
const scores = new Map();
|
|
133
|
-
for (const r of results) {
|
|
134
|
-
scores.set(r.path, r.score);
|
|
135
|
-
}
|
|
136
|
-
return scores;
|
|
137
|
-
}
|
|
138
283
|
// ── Substring fallback (no index) ───────────────────────────────────────────
|
|
139
|
-
function substringSearch(query, searchType, limit, stashDir) {
|
|
284
|
+
function substringSearch(query, searchType, limit, stashDir, sources) {
|
|
140
285
|
const assets = indexAssets(stashDir, searchType);
|
|
141
286
|
return assets
|
|
142
287
|
.filter((asset) => asset.name.toLowerCase().includes(query))
|
|
143
288
|
.sort(compareAssets)
|
|
144
289
|
.slice(0, limit)
|
|
145
|
-
.map((asset) => assetToSearchHit(asset, stashDir));
|
|
290
|
+
.map((asset) => assetToSearchHit(asset, stashDir, sources));
|
|
146
291
|
}
|
|
147
292
|
// ── Hit building ────────────────────────────────────────────────────────────
|
|
148
|
-
function
|
|
149
|
-
const
|
|
150
|
-
for (const dir of stashDirs) {
|
|
151
|
-
if (resolved.startsWith(path.resolve(dir) + path.sep))
|
|
152
|
-
return dir;
|
|
153
|
-
}
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
function buildIndexedHit(input) {
|
|
157
|
-
const entryStashDir = findStashDirForPath(input.path, input.allStashDirs) ?? input.defaultStashDir;
|
|
293
|
+
function buildDbHit(input) {
|
|
294
|
+
const entryStashDir = findSourceForPath(input.path, input.sources)?.path ?? input.defaultStashDir;
|
|
158
295
|
const typeRoot = path.join(entryStashDir, TYPE_DIRS[input.entry.type]);
|
|
159
296
|
const openRefName = deriveCanonicalAssetName(input.entry.type, typeRoot, input.path)
|
|
160
297
|
?? input.entry.name;
|
|
161
298
|
const qualityBoost = input.entry.generated === true ? 0 : 0.05;
|
|
162
299
|
const confidenceBoost = typeof input.entry.confidence === "number" ? Math.min(0.05, Math.max(0, input.entry.confidence) * 0.05) : 0;
|
|
163
|
-
const score = Math.round((input.score + qualityBoost + confidenceBoost) * 1000) / 1000;
|
|
300
|
+
const score = Math.min(Math.round((input.score + qualityBoost + confidenceBoost) * 1000) / 1000, 1.0);
|
|
164
301
|
const whyMatched = buildWhyMatched(input.entry, input.query, input.rankingMode, qualityBoost, confidenceBoost);
|
|
302
|
+
const source = findSourceForPath(input.path, input.sources);
|
|
165
303
|
const hit = {
|
|
304
|
+
hitSource: "local",
|
|
166
305
|
type: input.entry.type,
|
|
167
306
|
name: input.entry.name,
|
|
168
307
|
path: input.path,
|
|
169
|
-
openRef:
|
|
308
|
+
openRef: makeAssetRef(input.entry.type, openRefName, source?.registryId),
|
|
309
|
+
registryId: source?.registryId,
|
|
310
|
+
editable: source?.writable ?? false,
|
|
170
311
|
description: input.entry.description,
|
|
171
312
|
tags: input.entry.tags,
|
|
172
313
|
score,
|
|
173
314
|
whyMatched,
|
|
174
315
|
};
|
|
175
|
-
if (input.entry.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
catch (error) {
|
|
182
|
-
if (!hasErrnoCode(error, "ENOENT"))
|
|
183
|
-
throw error;
|
|
184
|
-
}
|
|
316
|
+
if (input.includeItemUsage && input.entry.usage && input.entry.usage.length > 0) {
|
|
317
|
+
hit.usage = input.entry.usage;
|
|
318
|
+
}
|
|
319
|
+
const handler = tryGetHandler(input.entry.type);
|
|
320
|
+
if (handler?.enrichSearchHit) {
|
|
321
|
+
handler.enrichSearchHit(hit, entryStashDir);
|
|
185
322
|
}
|
|
186
323
|
return hit;
|
|
187
324
|
}
|
|
188
325
|
function buildWhyMatched(entry, query, rankingMode, qualityBoost, confidenceBoost) {
|
|
189
|
-
const reasons = [rankingMode === "semantic" ? "semantic similarity" : "
|
|
326
|
+
const reasons = [rankingMode === "semantic" ? "semantic similarity" : "fts bm25 relevance"];
|
|
190
327
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
191
328
|
const name = entry.name.toLowerCase();
|
|
192
329
|
const tags = entry.tags?.join(" ").toLowerCase() ?? "";
|
|
@@ -207,32 +344,22 @@ function buildWhyMatched(entry, query, rankingMode, qualityBoost, confidenceBoos
|
|
|
207
344
|
return reasons;
|
|
208
345
|
}
|
|
209
346
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
path: ie.path,
|
|
216
|
-
}));
|
|
217
|
-
}
|
|
218
|
-
function assetToSearchHit(asset, stashDir) {
|
|
219
|
-
if (asset.type !== "tool") {
|
|
220
|
-
return {
|
|
221
|
-
type: asset.type,
|
|
222
|
-
name: asset.name,
|
|
223
|
-
path: asset.path,
|
|
224
|
-
openRef: makeOpenRef(asset.type, asset.name),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
const toolInfo = buildToolInfo(stashDir, asset.path);
|
|
228
|
-
return {
|
|
229
|
-
type: "tool",
|
|
347
|
+
function assetToSearchHit(asset, stashDir, sources) {
|
|
348
|
+
const source = findSourceForPath(asset.path, sources);
|
|
349
|
+
const hit = {
|
|
350
|
+
hitSource: "local",
|
|
351
|
+
type: asset.type,
|
|
230
352
|
name: asset.name,
|
|
231
353
|
path: asset.path,
|
|
232
|
-
openRef:
|
|
233
|
-
|
|
234
|
-
|
|
354
|
+
openRef: makeAssetRef(asset.type, asset.name, source?.registryId),
|
|
355
|
+
registryId: source?.registryId,
|
|
356
|
+
editable: source?.writable ?? false,
|
|
235
357
|
};
|
|
358
|
+
const handler = tryGetHandler(asset.type);
|
|
359
|
+
if (handler?.enrichSearchHit) {
|
|
360
|
+
handler.enrichSearchHit(hit, stashDir);
|
|
361
|
+
}
|
|
362
|
+
return hit;
|
|
236
363
|
}
|
|
237
364
|
function normalizeLimit(limit) {
|
|
238
365
|
if (typeof limit !== "number" || Number.isNaN(limit) || limit <= 0) {
|
|
@@ -240,6 +367,98 @@ function normalizeLimit(limit) {
|
|
|
240
367
|
}
|
|
241
368
|
return Math.min(Math.floor(limit), 200);
|
|
242
369
|
}
|
|
370
|
+
function parseSearchUsageMode(mode) {
|
|
371
|
+
if (mode === "none" || mode === "both" || mode === "item" || mode === "guide") {
|
|
372
|
+
return mode;
|
|
373
|
+
}
|
|
374
|
+
if (typeof mode === "undefined")
|
|
375
|
+
return "both";
|
|
376
|
+
throw new Error(`Invalid usage mode: ${String(mode)}. Expected one of: none|both|item|guide`);
|
|
377
|
+
}
|
|
378
|
+
function parseSearchSource(source) {
|
|
379
|
+
if (source === "local" || source === "registry" || source === "both")
|
|
380
|
+
return source;
|
|
381
|
+
if (typeof source === "undefined")
|
|
382
|
+
return "local";
|
|
383
|
+
throw new Error(`Invalid search source: ${String(source)}. Expected one of: local|registry|both`);
|
|
384
|
+
}
|
|
385
|
+
function mergeSearchHits(localHits, registryHits, limit) {
|
|
386
|
+
const merged = [];
|
|
387
|
+
let localIndex = 0;
|
|
388
|
+
let registryIndex = 0;
|
|
389
|
+
while (merged.length < limit && (localIndex < localHits.length || registryIndex < registryHits.length)) {
|
|
390
|
+
if (localIndex < localHits.length) {
|
|
391
|
+
merged.push(localHits[localIndex]);
|
|
392
|
+
localIndex += 1;
|
|
393
|
+
if (merged.length >= limit)
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
if (registryIndex < registryHits.length) {
|
|
397
|
+
merged.push(registryHits[registryIndex]);
|
|
398
|
+
registryIndex += 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return merged;
|
|
402
|
+
}
|
|
403
|
+
function shouldIncludeUsageGuide(mode) {
|
|
404
|
+
return mode === "both" || mode === "guide";
|
|
405
|
+
}
|
|
406
|
+
function shouldIncludeItemUsage(mode) {
|
|
407
|
+
return mode === "both" || mode === "item";
|
|
408
|
+
}
|
|
409
|
+
function buildUsageGuideFromEntries(entries, searchType) {
|
|
410
|
+
const types = entries.map((entry) => entry.type);
|
|
411
|
+
const fallbackGuide = buildUsageGuide(types, searchType);
|
|
412
|
+
const metadataByType = new Map();
|
|
413
|
+
for (const entry of entries) {
|
|
414
|
+
if (!entry.usage || entry.usage.length === 0)
|
|
415
|
+
continue;
|
|
416
|
+
const current = metadataByType.get(entry.type) ?? [];
|
|
417
|
+
for (const item of entry.usage) {
|
|
418
|
+
const trimmed = item.trim();
|
|
419
|
+
if (trimmed && !current.includes(trimmed))
|
|
420
|
+
current.push(trimmed);
|
|
421
|
+
}
|
|
422
|
+
if (current.length > 0)
|
|
423
|
+
metadataByType.set(entry.type, current);
|
|
424
|
+
}
|
|
425
|
+
if (!fallbackGuide && metadataByType.size === 0)
|
|
426
|
+
return undefined;
|
|
427
|
+
const result = {};
|
|
428
|
+
for (const assetType of resolveGuideTypes(types, searchType)) {
|
|
429
|
+
const lines = [];
|
|
430
|
+
const metadataLines = metadataByType.get(assetType);
|
|
431
|
+
if (metadataLines && metadataLines.length > 0) {
|
|
432
|
+
lines.push(...metadataLines);
|
|
433
|
+
}
|
|
434
|
+
const fallbackLines = fallbackGuide?.[assetType];
|
|
435
|
+
if (fallbackLines && fallbackLines.length > 0) {
|
|
436
|
+
for (const line of fallbackLines) {
|
|
437
|
+
if (!lines.includes(line))
|
|
438
|
+
lines.push(line);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (lines.length > 0)
|
|
442
|
+
result[assetType] = lines;
|
|
443
|
+
}
|
|
444
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
445
|
+
}
|
|
446
|
+
function buildUsageGuide(hitTypes, searchType) {
|
|
447
|
+
const result = {};
|
|
448
|
+
for (const assetType of resolveGuideTypes(hitTypes, searchType)) {
|
|
449
|
+
result[assetType] = usageGuideByType(assetType);
|
|
450
|
+
}
|
|
451
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
452
|
+
}
|
|
453
|
+
function resolveGuideTypes(hitTypes, searchType) {
|
|
454
|
+
if (searchType !== "any")
|
|
455
|
+
return [searchType];
|
|
456
|
+
return Array.from(new Set(hitTypes));
|
|
457
|
+
}
|
|
458
|
+
function usageGuideByType(type) {
|
|
459
|
+
const handler = tryGetHandler(type);
|
|
460
|
+
return handler?.defaultUsageGuide ?? [];
|
|
461
|
+
}
|
|
243
462
|
function fileToAsset(assetType, root, file) {
|
|
244
463
|
const name = deriveCanonicalAssetName(assetType, root, file);
|
|
245
464
|
if (!name)
|
package/dist/src/stash-show.d.ts
CHANGED