agentikit 0.0.9 → 0.0.13
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 +139 -208
- package/dist/index.d.ts +8 -2
- package/dist/index.js +4 -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 +201 -75
- 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 +19 -6
- package/dist/src/config.js +139 -29
- 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 +0 -1
- package/dist/src/metadata.js +6 -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 +2 -2
- package/dist/src/registry-install.js +142 -35
- package/dist/src/registry-resolve.js +90 -22
- package/dist/src/registry-search.d.ts +22 -0
- package/dist/src/registry-search.js +231 -97
- package/dist/src/registry-types.d.ts +9 -2
- package/dist/src/stash-add.js +4 -4
- 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.js +12 -12
- package/dist/src/stash-resolve.js +3 -0
- package/dist/src/stash-search.js +168 -164
- package/dist/src/stash-show.d.ts +1 -1
- package/dist/src/stash-show.js +28 -96
- package/dist/src/stash-source.d.ts +24 -0
- package/dist/src/stash-source.js +81 -0
- package/dist/src/stash-types.d.ts +14 -4
- package/dist/src/stash.d.ts +6 -0
- package/dist/src/stash.js +3 -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 +213 -82
- package/src/common.ts +23 -5
- package/src/config-cli.ts +499 -0
- package/src/config.ts +160 -38
- 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 +5 -65
- package/src/origin-resolve.ts +67 -0
- package/src/registry-install.ts +158 -42
- package/src/registry-resolve.ts +92 -23
- package/src/registry-search.ts +288 -98
- package/src/registry-types.ts +10 -2
- package/src/stash-add.ts +14 -17
- package/src/stash-clone.ts +127 -0
- package/src/stash-ref.ts +84 -26
- package/src/stash-registry.ts +12 -12
- package/src/stash-resolve.ts +3 -0
- package/src/stash-search.ts +202 -184
- package/src/stash-show.ts +33 -90
- package/src/stash-source.ts +103 -0
- package/src/stash-types.ts +14 -4
- package/src/stash.ts +8 -0
- package/src/tool-runner.ts +18 -5
- package/dist/src/similarity.d.ts +0 -34
- package/dist/src/similarity.js +0 -211
- package/src/similarity.ts +0 -271
|
@@ -6,7 +6,7 @@ import { installRegistryRef, removeInstalledRegistryEntry, upsertInstalledRegist
|
|
|
6
6
|
import { parseRegistryRef } from "./registry-resolve";
|
|
7
7
|
export async function agentikitList(input) {
|
|
8
8
|
const stashDir = input?.stashDir ?? resolveStashDir();
|
|
9
|
-
const config = loadConfig(
|
|
9
|
+
const config = loadConfig();
|
|
10
10
|
const installed = config.registry?.installed ?? [];
|
|
11
11
|
return {
|
|
12
12
|
stashDir,
|
|
@@ -25,10 +25,10 @@ export async function agentikitRemove(input) {
|
|
|
25
25
|
if (!target)
|
|
26
26
|
throw new Error("Target is required.");
|
|
27
27
|
const stashDir = input.stashDir ?? resolveStashDir();
|
|
28
|
-
const config = loadConfig(
|
|
28
|
+
const config = loadConfig();
|
|
29
29
|
const installed = config.registry?.installed ?? [];
|
|
30
30
|
const entry = resolveInstalledTarget(installed, target);
|
|
31
|
-
const updatedConfig = removeInstalledRegistryEntry(entry.id
|
|
31
|
+
const updatedConfig = removeInstalledRegistryEntry(entry.id);
|
|
32
32
|
cleanupDirectoryBestEffort(entry.cacheDir);
|
|
33
33
|
const index = await agentikitIndex({ stashDir });
|
|
34
34
|
return {
|
|
@@ -42,7 +42,7 @@ export async function agentikitRemove(input) {
|
|
|
42
42
|
stashRoot: entry.stashRoot,
|
|
43
43
|
},
|
|
44
44
|
config: {
|
|
45
|
-
|
|
45
|
+
mountedStashDirs: updatedConfig.mountedStashDirs,
|
|
46
46
|
installedRegistryCount: updatedConfig.registry?.installed.length ?? 0,
|
|
47
47
|
},
|
|
48
48
|
index: {
|
|
@@ -57,12 +57,12 @@ export async function agentikitReinstall(input) {
|
|
|
57
57
|
const stashDir = input?.stashDir ?? resolveStashDir();
|
|
58
58
|
const target = input?.target?.trim();
|
|
59
59
|
const all = input?.all === true;
|
|
60
|
-
const installedEntries = loadConfig(
|
|
60
|
+
const installedEntries = loadConfig().registry?.installed ?? [];
|
|
61
61
|
const selectedEntries = selectTargets(installedEntries, target, all);
|
|
62
62
|
const processed = [];
|
|
63
63
|
for (const entry of selectedEntries) {
|
|
64
64
|
const installed = await installRegistryRef(entry.ref);
|
|
65
|
-
upsertInstalledRegistryEntry(toInstalledEntry(installed)
|
|
65
|
+
upsertInstalledRegistryEntry(toInstalledEntry(installed));
|
|
66
66
|
if (entry.cacheDir !== installed.cacheDir) {
|
|
67
67
|
cleanupDirectoryBestEffort(entry.cacheDir);
|
|
68
68
|
}
|
|
@@ -75,14 +75,14 @@ export async function agentikitReinstall(input) {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
const index = await agentikitIndex({ stashDir });
|
|
78
|
-
const config = loadConfig(
|
|
78
|
+
const config = loadConfig();
|
|
79
79
|
return {
|
|
80
80
|
stashDir,
|
|
81
81
|
target,
|
|
82
82
|
all,
|
|
83
83
|
processed,
|
|
84
84
|
config: {
|
|
85
|
-
|
|
85
|
+
mountedStashDirs: config.mountedStashDirs,
|
|
86
86
|
installedRegistryCount: config.registry?.installed.length ?? 0,
|
|
87
87
|
},
|
|
88
88
|
index: {
|
|
@@ -97,12 +97,12 @@ export async function agentikitUpdate(input) {
|
|
|
97
97
|
const stashDir = input?.stashDir ?? resolveStashDir();
|
|
98
98
|
const target = input?.target?.trim();
|
|
99
99
|
const all = input?.all === true;
|
|
100
|
-
const installedEntries = loadConfig(
|
|
100
|
+
const installedEntries = loadConfig().registry?.installed ?? [];
|
|
101
101
|
const selectedEntries = selectTargets(installedEntries, target, all);
|
|
102
102
|
const processed = [];
|
|
103
103
|
for (const entry of selectedEntries) {
|
|
104
104
|
const installed = await installRegistryRef(entry.ref);
|
|
105
|
-
upsertInstalledRegistryEntry(toInstalledEntry(installed)
|
|
105
|
+
upsertInstalledRegistryEntry(toInstalledEntry(installed));
|
|
106
106
|
if (entry.cacheDir !== installed.cacheDir) {
|
|
107
107
|
cleanupDirectoryBestEffort(entry.cacheDir);
|
|
108
108
|
}
|
|
@@ -126,14 +126,14 @@ export async function agentikitUpdate(input) {
|
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
const index = await agentikitIndex({ stashDir });
|
|
129
|
-
const config = loadConfig(
|
|
129
|
+
const config = loadConfig();
|
|
130
130
|
return {
|
|
131
131
|
stashDir,
|
|
132
132
|
target,
|
|
133
133
|
all,
|
|
134
134
|
processed,
|
|
135
135
|
config: {
|
|
136
|
-
|
|
136
|
+
mountedStashDirs: config.mountedStashDirs,
|
|
137
137
|
installedRegistryCount: config.registry?.installed.length ?? 0,
|
|
138
138
|
},
|
|
139
139
|
index: {
|
|
@@ -21,6 +21,9 @@ export function resolveAssetPath(stashDir, type, name) {
|
|
|
21
21
|
if (type === "tool") {
|
|
22
22
|
throw new Error("Tool ref must resolve to a .sh, .ts, .js, .ps1, .cmd, or .bat file.");
|
|
23
23
|
}
|
|
24
|
+
if (type === "script") {
|
|
25
|
+
throw new Error("Script ref must resolve to a file with a supported script extension. Refer to the Agentikit documentation for the complete list of supported script extensions.");
|
|
26
|
+
}
|
|
24
27
|
throw new Error(`Stash asset not found for ref: ${type}:${name}`);
|
|
25
28
|
}
|
|
26
29
|
return realTarget;
|
package/dist/src/stash-search.js
CHANGED
|
@@ -1,37 +1,14 @@
|
|
|
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";
|
|
11
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";
|
|
12
11
|
const DEFAULT_LIMIT = 20;
|
|
13
|
-
const DEFAULT_USAGE_GUIDE_BY_TYPE = {
|
|
14
|
-
tool: [
|
|
15
|
-
"Use the hit's runCmd for execution so runtime and working directory stay correct.",
|
|
16
|
-
"Use `akm show <openRef>` to inspect the tool before running it.",
|
|
17
|
-
],
|
|
18
|
-
skill: [
|
|
19
|
-
"Read and apply the skill instructions as written, then adapt examples to your current repo state and task.",
|
|
20
|
-
"Use `akm show <openRef>` to read the full SKILL.md for required steps and constraints.",
|
|
21
|
-
],
|
|
22
|
-
command: [
|
|
23
|
-
"Read the .md file, fill placeholders, and run it in the current repo context.",
|
|
24
|
-
"Use `akm show <openRef>` to retrieve the command template body.",
|
|
25
|
-
],
|
|
26
|
-
agent: [
|
|
27
|
-
"Read the .md file and dispatch and agent using the content of the file. Use modelHint/toolPolicy when present to run the agent with compatible settings.",
|
|
28
|
-
"Use with `akm show <openRef>` to get the full prompt payload.",
|
|
29
|
-
],
|
|
30
|
-
knowledge: [
|
|
31
|
-
"Use `akm show <openRef>` to read the document; start with `--view toc` for large files.",
|
|
32
|
-
"Use `--view section` or `--view lines` to load only the part you need.",
|
|
33
|
-
],
|
|
34
|
-
};
|
|
35
12
|
export async function agentikitSearch(input) {
|
|
36
13
|
const t0 = Date.now();
|
|
37
14
|
const query = input.query.trim();
|
|
@@ -40,7 +17,8 @@ export async function agentikitSearch(input) {
|
|
|
40
17
|
const limit = normalizeLimit(input.limit);
|
|
41
18
|
const usageMode = parseSearchUsageMode(input.usage);
|
|
42
19
|
const source = parseSearchSource(input.source);
|
|
43
|
-
const
|
|
20
|
+
const sources = resolveStashSources();
|
|
21
|
+
const stashDir = sources[0].path;
|
|
44
22
|
const localResult = source === "registry"
|
|
45
23
|
? undefined
|
|
46
24
|
: await searchLocal({
|
|
@@ -49,10 +27,12 @@ export async function agentikitSearch(input) {
|
|
|
49
27
|
limit,
|
|
50
28
|
usageMode,
|
|
51
29
|
stashDir,
|
|
30
|
+
sources,
|
|
52
31
|
});
|
|
32
|
+
const config = loadConfig();
|
|
53
33
|
const registryResult = source === "local"
|
|
54
34
|
? undefined
|
|
55
|
-
: await searchRegistry(query, { limit });
|
|
35
|
+
: await searchRegistry(query, { limit, registryUrls: config.registryUrls });
|
|
56
36
|
if (source === "local") {
|
|
57
37
|
return {
|
|
58
38
|
stashDir,
|
|
@@ -76,6 +56,7 @@ export async function agentikitSearch(input) {
|
|
|
76
56
|
homepage: hit.homepage,
|
|
77
57
|
score: hit.score,
|
|
78
58
|
metadata: hit.metadata,
|
|
59
|
+
curated: hit.curated,
|
|
79
60
|
installRef,
|
|
80
61
|
installCmd: `akm add ${installRef}`,
|
|
81
62
|
};
|
|
@@ -103,32 +84,39 @@ export async function agentikitSearch(input) {
|
|
|
103
84
|
};
|
|
104
85
|
}
|
|
105
86
|
async function searchLocal(input) {
|
|
106
|
-
const { query, searchType, limit, usageMode, stashDir } = input;
|
|
107
|
-
const config = loadConfig(
|
|
108
|
-
const allStashDirs =
|
|
109
|
-
|
|
110
|
-
|
|
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);
|
|
111
96
|
try {
|
|
112
|
-
|
|
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
|
+
}
|
|
113
109
|
}
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
finally {
|
|
111
|
+
closeDatabase(db);
|
|
116
112
|
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const { hits, usageGuide, embedMs, rankMs } = await searchIndex(index, query, searchType, limit, stashDir, allStashDirs, config, usageMode);
|
|
122
|
-
return {
|
|
123
|
-
hits,
|
|
124
|
-
usageGuide,
|
|
125
|
-
tip: hits.length === 0 ? "No matching stash assets were found. Try running 'akm index' to rebuild." : undefined,
|
|
126
|
-
embedMs,
|
|
127
|
-
rankMs,
|
|
128
|
-
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.warn("Search index unavailable, falling back to substring search:", error instanceof Error ? error.message : String(error));
|
|
129
117
|
}
|
|
130
118
|
const hits = allStashDirs
|
|
131
|
-
.flatMap((dir) => substringSearch(query, searchType, limit, dir))
|
|
119
|
+
.flatMap((dir) => substringSearch(query, searchType, limit, dir, sources))
|
|
132
120
|
.slice(0, limit);
|
|
133
121
|
const usageGuide = shouldIncludeUsageGuide(usageMode) ? buildUsageGuide(hits.map((hit) => hit.type), searchType) : undefined;
|
|
134
122
|
return {
|
|
@@ -137,71 +125,126 @@ async function searchLocal(input) {
|
|
|
137
125
|
tip: hits.length === 0 ? "No matching stash assets were found. Try running 'akm index' to rebuild." : undefined,
|
|
138
126
|
};
|
|
139
127
|
}
|
|
140
|
-
// ──
|
|
141
|
-
async function
|
|
142
|
-
//
|
|
143
|
-
let candidates = index.entries;
|
|
144
|
-
if (searchType !== "any") {
|
|
145
|
-
candidates = candidates.filter((ie) => ie.entry.type === searchType);
|
|
146
|
-
}
|
|
147
|
-
if (candidates.length === 0) {
|
|
148
|
-
return {
|
|
149
|
-
hits: [],
|
|
150
|
-
usageGuide: shouldIncludeUsageGuide(usageMode) ? buildUsageGuide([], searchType) : undefined,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
// 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
|
|
154
131
|
if (!query) {
|
|
155
|
-
const
|
|
156
|
-
const
|
|
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({
|
|
157
136
|
entry: ie.entry,
|
|
158
|
-
path: ie.
|
|
137
|
+
path: ie.filePath,
|
|
159
138
|
score: 1,
|
|
160
139
|
query,
|
|
161
|
-
rankingMode: "
|
|
140
|
+
rankingMode: "fts",
|
|
162
141
|
defaultStashDir: stashDir,
|
|
163
142
|
allStashDirs,
|
|
143
|
+
sources,
|
|
164
144
|
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
165
145
|
}));
|
|
166
146
|
return {
|
|
167
147
|
hits,
|
|
168
148
|
usageGuide: shouldIncludeUsageGuide(usageMode)
|
|
169
|
-
? buildUsageGuideFromEntries(
|
|
149
|
+
? buildUsageGuideFromEntries(selected.map((e) => e.entry), searchType)
|
|
170
150
|
: undefined,
|
|
171
151
|
};
|
|
172
152
|
}
|
|
173
|
-
// Score
|
|
153
|
+
// Score using FTS5 (BM25) and optionally sqlite-vec
|
|
174
154
|
const tEmbed0 = Date.now();
|
|
175
|
-
const embeddingScores = await
|
|
155
|
+
const embeddingScores = await tryVecScores(db, query, limit * 3, config);
|
|
176
156
|
const embedMs = Date.now() - tEmbed0;
|
|
177
157
|
const tRank0 = Date.now();
|
|
178
|
-
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
|
|
179
169
|
const scored = [];
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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);
|
|
184
175
|
if (embScore !== undefined) {
|
|
185
|
-
|
|
186
|
-
const blended = embScore * 0.7 + tfidfScore * 0.3;
|
|
176
|
+
const blended = embScore * 0.7 + ftsScore * 0.3;
|
|
187
177
|
if (blended > 0)
|
|
188
|
-
scored.push({
|
|
178
|
+
scored.push({ id, entry: result.entry, filePath: result.filePath, score: blended, rankingMode: "semantic" });
|
|
179
|
+
}
|
|
180
|
+
else if (ftsScore > 0) {
|
|
181
|
+
scored.push({ id, entry: result.entry, filePath: result.filePath, score: ftsScore, rankingMode: "fts" });
|
|
189
182
|
}
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
}
|
|
192
201
|
}
|
|
193
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
|
+
}
|
|
194
236
|
scored.sort((a, b) => b.score - a.score);
|
|
195
237
|
const rankMs = Date.now() - tRank0;
|
|
196
238
|
const selected = scored.slice(0, limit);
|
|
197
|
-
const hits = selected.map(({
|
|
198
|
-
entry
|
|
199
|
-
path:
|
|
239
|
+
const hits = selected.map(({ entry, filePath, score, rankingMode }) => buildDbHit({
|
|
240
|
+
entry,
|
|
241
|
+
path: filePath,
|
|
200
242
|
score: Math.round(score * 1000) / 1000,
|
|
201
243
|
query,
|
|
202
244
|
rankingMode,
|
|
203
245
|
defaultStashDir: stashDir,
|
|
204
246
|
allStashDirs,
|
|
247
|
+
sources,
|
|
205
248
|
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
206
249
|
}));
|
|
207
250
|
return {
|
|
@@ -209,83 +252,62 @@ async function searchIndex(index, query, searchType, limit, stashDir, allStashDi
|
|
|
209
252
|
rankMs,
|
|
210
253
|
hits,
|
|
211
254
|
usageGuide: shouldIncludeUsageGuide(usageMode)
|
|
212
|
-
? buildUsageGuideFromEntries(selected.map((item) => item.
|
|
255
|
+
? buildUsageGuideFromEntries(selected.map((item) => item.entry), searchType)
|
|
213
256
|
: undefined,
|
|
214
257
|
};
|
|
215
258
|
}
|
|
216
|
-
// ──
|
|
217
|
-
async function
|
|
218
|
-
if (!config.semanticSearch)
|
|
259
|
+
// ── Vector scorer ───────────────────────────────────────────────────────────
|
|
260
|
+
async function tryVecScores(db, query, k, config) {
|
|
261
|
+
if (!config.semanticSearch || !isVecAvailable())
|
|
219
262
|
return null;
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
263
|
+
const hasEmbeddings = getMeta(db, "hasEmbeddings");
|
|
264
|
+
if (hasEmbeddings !== "1")
|
|
222
265
|
return null;
|
|
223
266
|
try {
|
|
224
|
-
const { embed
|
|
267
|
+
const { embed } = await import("./embedder.js");
|
|
225
268
|
const queryEmbedding = await embed(query, config.embedding);
|
|
269
|
+
const vecResults = searchVec(db, queryEmbedding, k);
|
|
226
270
|
const scores = new Map();
|
|
227
|
-
for (const
|
|
228
|
-
|
|
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));
|
|
229
275
|
}
|
|
230
276
|
return scores;
|
|
231
277
|
}
|
|
232
|
-
catch {
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.warn("Vector search failed, skipping:", error instanceof Error ? error.message : String(error));
|
|
233
280
|
return null;
|
|
234
281
|
}
|
|
235
282
|
}
|
|
236
|
-
// ── TF-IDF scorer ───────────────────────────────────────────────────────────
|
|
237
|
-
function computeTfidfScores(index, candidates, query, searchType) {
|
|
238
|
-
const candidateScoredEntries = toScoredEntries(candidates);
|
|
239
|
-
let adapter;
|
|
240
|
-
if (index.tfidf) {
|
|
241
|
-
const allScored = toScoredEntries(index.entries);
|
|
242
|
-
adapter = TfIdfAdapter.deserialize(index.tfidf, allScored);
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
adapter = new TfIdfAdapter();
|
|
246
|
-
adapter.buildIndex(candidateScoredEntries);
|
|
247
|
-
}
|
|
248
|
-
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
249
|
-
const results = adapter.search(query, candidates.length, typeFilter);
|
|
250
|
-
const scores = new Map();
|
|
251
|
-
for (const r of results) {
|
|
252
|
-
scores.set(r.path, r.score);
|
|
253
|
-
}
|
|
254
|
-
return scores;
|
|
255
|
-
}
|
|
256
283
|
// ── Substring fallback (no index) ───────────────────────────────────────────
|
|
257
|
-
function substringSearch(query, searchType, limit, stashDir) {
|
|
284
|
+
function substringSearch(query, searchType, limit, stashDir, sources) {
|
|
258
285
|
const assets = indexAssets(stashDir, searchType);
|
|
259
286
|
return assets
|
|
260
287
|
.filter((asset) => asset.name.toLowerCase().includes(query))
|
|
261
288
|
.sort(compareAssets)
|
|
262
289
|
.slice(0, limit)
|
|
263
|
-
.map((asset) => assetToSearchHit(asset, stashDir));
|
|
290
|
+
.map((asset) => assetToSearchHit(asset, stashDir, sources));
|
|
264
291
|
}
|
|
265
292
|
// ── Hit building ────────────────────────────────────────────────────────────
|
|
266
|
-
function
|
|
267
|
-
const
|
|
268
|
-
for (const dir of stashDirs) {
|
|
269
|
-
if (resolved.startsWith(path.resolve(dir) + path.sep))
|
|
270
|
-
return dir;
|
|
271
|
-
}
|
|
272
|
-
return undefined;
|
|
273
|
-
}
|
|
274
|
-
function buildIndexedHit(input) {
|
|
275
|
-
const entryStashDir = findStashDirForPath(input.path, input.allStashDirs) ?? input.defaultStashDir;
|
|
293
|
+
function buildDbHit(input) {
|
|
294
|
+
const entryStashDir = findSourceForPath(input.path, input.sources)?.path ?? input.defaultStashDir;
|
|
276
295
|
const typeRoot = path.join(entryStashDir, TYPE_DIRS[input.entry.type]);
|
|
277
296
|
const openRefName = deriveCanonicalAssetName(input.entry.type, typeRoot, input.path)
|
|
278
297
|
?? input.entry.name;
|
|
279
298
|
const qualityBoost = input.entry.generated === true ? 0 : 0.05;
|
|
280
299
|
const confidenceBoost = typeof input.entry.confidence === "number" ? Math.min(0.05, Math.max(0, input.entry.confidence) * 0.05) : 0;
|
|
281
|
-
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);
|
|
282
301
|
const whyMatched = buildWhyMatched(input.entry, input.query, input.rankingMode, qualityBoost, confidenceBoost);
|
|
302
|
+
const source = findSourceForPath(input.path, input.sources);
|
|
283
303
|
const hit = {
|
|
284
304
|
hitSource: "local",
|
|
285
305
|
type: input.entry.type,
|
|
286
306
|
name: input.entry.name,
|
|
287
307
|
path: input.path,
|
|
288
|
-
openRef:
|
|
308
|
+
openRef: makeAssetRef(input.entry.type, openRefName, source?.registryId),
|
|
309
|
+
registryId: source?.registryId,
|
|
310
|
+
editable: source?.writable ?? false,
|
|
289
311
|
description: input.entry.description,
|
|
290
312
|
tags: input.entry.tags,
|
|
291
313
|
score,
|
|
@@ -294,21 +316,14 @@ function buildIndexedHit(input) {
|
|
|
294
316
|
if (input.includeItemUsage && input.entry.usage && input.entry.usage.length > 0) {
|
|
295
317
|
hit.usage = input.entry.usage;
|
|
296
318
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
hit.runCmd = toolInfo.runCmd;
|
|
301
|
-
hit.kind = toolInfo.kind;
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
if (!hasErrnoCode(error, "ENOENT"))
|
|
305
|
-
throw error;
|
|
306
|
-
}
|
|
319
|
+
const handler = tryGetHandler(input.entry.type);
|
|
320
|
+
if (handler?.enrichSearchHit) {
|
|
321
|
+
handler.enrichSearchHit(hit, entryStashDir);
|
|
307
322
|
}
|
|
308
323
|
return hit;
|
|
309
324
|
}
|
|
310
325
|
function buildWhyMatched(entry, query, rankingMode, qualityBoost, confidenceBoost) {
|
|
311
|
-
const reasons = [rankingMode === "semantic" ? "semantic similarity" : "
|
|
326
|
+
const reasons = [rankingMode === "semantic" ? "semantic similarity" : "fts bm25 relevance"];
|
|
312
327
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
313
328
|
const name = entry.name.toLowerCase();
|
|
314
329
|
const tags = entry.tags?.join(" ").toLowerCase() ?? "";
|
|
@@ -329,34 +344,22 @@ function buildWhyMatched(entry, query, rankingMode, qualityBoost, confidenceBoos
|
|
|
329
344
|
return reasons;
|
|
330
345
|
}
|
|
331
346
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
text: buildSearchText(ie.entry),
|
|
336
|
-
entry: ie.entry,
|
|
337
|
-
path: ie.path,
|
|
338
|
-
}));
|
|
339
|
-
}
|
|
340
|
-
function assetToSearchHit(asset, stashDir) {
|
|
341
|
-
if (asset.type !== "tool") {
|
|
342
|
-
return {
|
|
343
|
-
hitSource: "local",
|
|
344
|
-
type: asset.type,
|
|
345
|
-
name: asset.name,
|
|
346
|
-
path: asset.path,
|
|
347
|
-
openRef: makeOpenRef(asset.type, asset.name),
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
const toolInfo = buildToolInfo(stashDir, asset.path);
|
|
351
|
-
return {
|
|
347
|
+
function assetToSearchHit(asset, stashDir, sources) {
|
|
348
|
+
const source = findSourceForPath(asset.path, sources);
|
|
349
|
+
const hit = {
|
|
352
350
|
hitSource: "local",
|
|
353
|
-
type:
|
|
351
|
+
type: asset.type,
|
|
354
352
|
name: asset.name,
|
|
355
353
|
path: asset.path,
|
|
356
|
-
openRef:
|
|
357
|
-
|
|
358
|
-
|
|
354
|
+
openRef: makeAssetRef(asset.type, asset.name, source?.registryId),
|
|
355
|
+
registryId: source?.registryId,
|
|
356
|
+
editable: source?.writable ?? false,
|
|
359
357
|
};
|
|
358
|
+
const handler = tryGetHandler(asset.type);
|
|
359
|
+
if (handler?.enrichSearchHit) {
|
|
360
|
+
handler.enrichSearchHit(hit, stashDir);
|
|
361
|
+
}
|
|
362
|
+
return hit;
|
|
360
363
|
}
|
|
361
364
|
function normalizeLimit(limit) {
|
|
362
365
|
if (typeof limit !== "number" || Number.isNaN(limit) || limit <= 0) {
|
|
@@ -453,7 +456,8 @@ function resolveGuideTypes(hitTypes, searchType) {
|
|
|
453
456
|
return Array.from(new Set(hitTypes));
|
|
454
457
|
}
|
|
455
458
|
function usageGuideByType(type) {
|
|
456
|
-
|
|
459
|
+
const handler = tryGetHandler(type);
|
|
460
|
+
return handler?.defaultUsageGuide ?? [];
|
|
457
461
|
}
|
|
458
462
|
function fileToAsset(assetType, root, file) {
|
|
459
463
|
const name = deriveCanonicalAssetName(assetType, root, file);
|
package/dist/src/stash-show.d.ts
CHANGED