agentikit 0.0.13 → 0.0.15
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/LICENSE +385 -0
- package/README.md +187 -110
- package/dist/{src/asset-spec.js → asset-spec.js} +11 -2
- package/dist/{src/asset-type-handler.js → asset-type-handler.js} +4 -3
- package/dist/cli.js +709 -0
- package/dist/common.js +192 -0
- package/dist/{src/config-cli.js → config-cli.js} +36 -30
- package/dist/{src/config.js → config.js} +95 -25
- package/dist/{src/db.js → db.js} +123 -51
- package/dist/{src/embedder.js → embedder.js} +57 -2
- package/dist/errors.js +28 -0
- package/dist/file-context.js +188 -0
- package/dist/{src/frontmatter.js → frontmatter.js} +1 -1
- package/dist/{src/github.js → github.js} +1 -3
- package/dist/handlers/agent-handler.js +19 -0
- package/dist/handlers/command-handler.js +20 -0
- package/dist/handlers/handler-bridge.js +51 -0
- package/dist/handlers/index.js +19 -0
- package/dist/handlers/knowledge-handler.js +32 -0
- package/dist/handlers/script-handler.js +42 -0
- package/dist/{src/handlers → handlers}/skill-handler.js +5 -6
- package/dist/{src/handlers → handlers}/tool-handler.js +8 -24
- package/dist/{src/indexer.js → indexer.js} +50 -26
- package/dist/init.js +43 -0
- package/dist/{src/llm.js → llm.js} +6 -11
- package/dist/lockfile.js +60 -0
- package/dist/matchers.js +163 -0
- package/dist/{src/metadata.js → metadata.js} +36 -16
- package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
- package/dist/paths.js +83 -0
- package/dist/{src/registry-install.js → registry-install.js} +151 -19
- package/dist/{src/registry-resolve.js → registry-resolve.js} +190 -26
- package/dist/{src/registry-search.js → registry-search.js} +13 -21
- package/dist/renderers.js +286 -0
- package/dist/{src/ripgrep-install.js → ripgrep-install.js} +8 -27
- package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
- package/dist/ripgrep.js +2 -0
- package/dist/self-update.js +226 -0
- package/dist/{src/stash-add.js → stash-add.js} +14 -4
- package/dist/stash-clone.js +115 -0
- package/dist/{src/stash-ref.js → stash-ref.js} +10 -9
- package/dist/{src/stash-registry.js → stash-registry.js} +21 -46
- package/dist/{src/stash-resolve.js → stash-resolve.js} +10 -9
- package/dist/{src/stash-search.js → stash-search.js} +89 -74
- package/dist/stash-show.js +74 -0
- package/dist/stash-source.js +127 -0
- package/dist/submit.js +557 -0
- package/dist/{src/tool-runner.js → tool-runner.js} +1 -5
- package/dist/{src/walker.js → walker.js} +38 -0
- package/dist/warn.js +20 -0
- package/package.json +13 -18
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -15
- package/dist/src/asset-spec.d.ts +0 -16
- package/dist/src/asset-type-handler.d.ts +0 -27
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.js +0 -399
- package/dist/src/common.d.ts +0 -13
- package/dist/src/common.js +0 -60
- package/dist/src/config-cli.d.ts +0 -9
- package/dist/src/config.d.ts +0 -50
- package/dist/src/db.d.ts +0 -46
- package/dist/src/embedder.d.ts +0 -10
- package/dist/src/frontmatter.d.ts +0 -30
- package/dist/src/github.d.ts +0 -4
- package/dist/src/handlers/agent-handler.d.ts +0 -2
- package/dist/src/handlers/agent-handler.js +0 -26
- package/dist/src/handlers/command-handler.d.ts +0 -2
- package/dist/src/handlers/command-handler.js +0 -23
- package/dist/src/handlers/index.d.ts +0 -6
- package/dist/src/handlers/index.js +0 -23
- package/dist/src/handlers/knowledge-handler.d.ts +0 -2
- package/dist/src/handlers/knowledge-handler.js +0 -56
- package/dist/src/handlers/markdown-helpers.d.ts +0 -7
- package/dist/src/handlers/script-handler.d.ts +0 -2
- package/dist/src/handlers/script-handler.js +0 -78
- package/dist/src/handlers/skill-handler.d.ts +0 -2
- package/dist/src/handlers/tool-handler.d.ts +0 -2
- package/dist/src/indexer.d.ts +0 -22
- package/dist/src/init.d.ts +0 -19
- package/dist/src/init.js +0 -99
- package/dist/src/llm.d.ts +0 -15
- package/dist/src/markdown.d.ts +0 -18
- package/dist/src/metadata.d.ts +0 -41
- package/dist/src/origin-resolve.d.ts +0 -19
- package/dist/src/registry-install.d.ts +0 -11
- package/dist/src/registry-resolve.d.ts +0 -3
- package/dist/src/registry-search.d.ts +0 -27
- package/dist/src/registry-types.d.ts +0 -62
- package/dist/src/ripgrep-install.d.ts +0 -12
- package/dist/src/ripgrep-resolve.d.ts +0 -13
- package/dist/src/ripgrep.d.ts +0 -3
- package/dist/src/ripgrep.js +0 -2
- package/dist/src/stash-add.d.ts +0 -4
- package/dist/src/stash-clone.d.ts +0 -22
- package/dist/src/stash-clone.js +0 -83
- package/dist/src/stash-ref.d.ts +0 -31
- package/dist/src/stash-registry.d.ts +0 -18
- package/dist/src/stash-resolve.d.ts +0 -2
- package/dist/src/stash-search.d.ts +0 -8
- package/dist/src/stash-show.d.ts +0 -5
- package/dist/src/stash-show.js +0 -46
- package/dist/src/stash-source.d.ts +0 -24
- package/dist/src/stash-source.js +0 -81
- package/dist/src/stash-types.d.ts +0 -227
- package/dist/src/stash.d.ts +0 -16
- package/dist/src/stash.js +0 -9
- package/dist/src/tool-runner.d.ts +0 -35
- package/dist/src/walker.d.ts +0 -19
- package/src/asset-spec.ts +0 -85
- package/src/asset-type-handler.ts +0 -77
- package/src/cli.ts +0 -427
- package/src/common.ts +0 -76
- package/src/config-cli.ts +0 -499
- package/src/config.ts +0 -305
- package/src/db.ts +0 -411
- package/src/embedder.ts +0 -128
- package/src/frontmatter.ts +0 -95
- package/src/github.ts +0 -21
- package/src/handlers/agent-handler.ts +0 -32
- package/src/handlers/command-handler.ts +0 -29
- package/src/handlers/index.ts +0 -25
- package/src/handlers/knowledge-handler.ts +0 -62
- package/src/handlers/markdown-helpers.ts +0 -19
- package/src/handlers/script-handler.ts +0 -92
- package/src/handlers/skill-handler.ts +0 -37
- package/src/handlers/tool-handler.ts +0 -71
- package/src/indexer.ts +0 -392
- package/src/init.ts +0 -114
- package/src/llm.ts +0 -125
- package/src/markdown.ts +0 -106
- package/src/metadata.ts +0 -333
- package/src/origin-resolve.ts +0 -67
- package/src/registry-install.ts +0 -361
- package/src/registry-resolve.ts +0 -341
- package/src/registry-search.ts +0 -335
- package/src/registry-types.ts +0 -72
- package/src/ripgrep-install.ts +0 -200
- package/src/ripgrep-resolve.ts +0 -72
- package/src/ripgrep.ts +0 -3
- package/src/stash-add.ts +0 -63
- package/src/stash-clone.ts +0 -127
- package/src/stash-ref.ts +0 -99
- package/src/stash-registry.ts +0 -259
- package/src/stash-resolve.ts +0 -50
- package/src/stash-search.ts +0 -613
- package/src/stash-show.ts +0 -55
- package/src/stash-source.ts +0 -103
- package/src/stash-types.ts +0 -231
- package/src/stash.ts +0 -39
- package/src/tool-runner.ts +0 -142
- package/src/walker.ts +0 -53
- /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
- /package/dist/{src/markdown.js → markdown.js} +0 -0
- /package/dist/{src/registry-types.js → registry-types.js} +0 -0
- /package/dist/{src/stash-types.js → stash-types.js} +0 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { ASSET_TYPES,
|
|
4
|
-
import {
|
|
5
|
-
import { makeAssetRef } from "./stash-ref";
|
|
3
|
+
import { ASSET_TYPES, deriveCanonicalAssetName, TYPE_DIRS } from "./asset-spec";
|
|
4
|
+
import { tryGetHandler } from "./asset-type-handler";
|
|
6
5
|
import { loadConfig } from "./config";
|
|
6
|
+
import { closeDatabase, getAllEntries, getEntryById, getEntryCount, getMeta, openDatabase, searchFts, searchVec, } from "./db";
|
|
7
|
+
import { UsageError } from "./errors";
|
|
8
|
+
import { getDbPath } from "./paths";
|
|
7
9
|
import { searchRegistry } from "./registry-search";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
10
|
+
import { makeAssetRef } from "./stash-ref";
|
|
11
|
+
import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./stash-source";
|
|
12
|
+
import { walkStash } from "./walker";
|
|
13
|
+
import { warn } from "./warn";
|
|
11
14
|
const DEFAULT_LIMIT = 20;
|
|
12
15
|
export async function agentikitSearch(input) {
|
|
13
16
|
const t0 = Date.now();
|
|
@@ -17,7 +20,17 @@ export async function agentikitSearch(input) {
|
|
|
17
20
|
const limit = normalizeLimit(input.limit);
|
|
18
21
|
const usageMode = parseSearchUsageMode(input.usage);
|
|
19
22
|
const source = parseSearchSource(input.source);
|
|
20
|
-
const
|
|
23
|
+
const config = loadConfig();
|
|
24
|
+
const sources = resolveStashSources(undefined, config);
|
|
25
|
+
if (sources.length === 0) {
|
|
26
|
+
return {
|
|
27
|
+
stashDir: "",
|
|
28
|
+
source: source ?? "all",
|
|
29
|
+
hits: [],
|
|
30
|
+
warnings: ["No stash sources configured. Run `akm init` first."],
|
|
31
|
+
timing: { totalMs: Date.now() - t0 },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
21
34
|
const stashDir = sources[0].path;
|
|
22
35
|
const localResult = source === "registry"
|
|
23
36
|
? undefined
|
|
@@ -28,11 +41,9 @@ export async function agentikitSearch(input) {
|
|
|
28
41
|
usageMode,
|
|
29
42
|
stashDir,
|
|
30
43
|
sources,
|
|
44
|
+
config,
|
|
31
45
|
});
|
|
32
|
-
const
|
|
33
|
-
const registryResult = source === "local"
|
|
34
|
-
? undefined
|
|
35
|
-
: await searchRegistry(query, { limit, registryUrls: config.registryUrls });
|
|
46
|
+
const registryResult = source === "local" ? undefined : await searchRegistry(query, { limit, registryUrls: config.registryUrls });
|
|
36
47
|
if (source === "local") {
|
|
37
48
|
return {
|
|
38
49
|
stashDir,
|
|
@@ -40,11 +51,12 @@ export async function agentikitSearch(input) {
|
|
|
40
51
|
hits: localResult?.hits ?? [],
|
|
41
52
|
usageGuide: localResult?.usageGuide,
|
|
42
53
|
tip: localResult?.tip,
|
|
54
|
+
warnings: localResult?.warnings,
|
|
43
55
|
timing: { totalMs: Date.now() - t0, rankMs: localResult?.rankMs, embedMs: localResult?.embedMs },
|
|
44
56
|
};
|
|
45
57
|
}
|
|
46
58
|
const registryHits = (registryResult?.hits ?? []).map((hit) => {
|
|
47
|
-
const installRef = hit.source === "npm" ? `npm:${hit.ref}` : `github:${hit.ref}`;
|
|
59
|
+
const installRef = hit.source === "npm" ? `npm:${hit.ref}` : hit.source === "git" ? `git+${hit.ref}` : `github:${hit.ref}`;
|
|
48
60
|
return {
|
|
49
61
|
hitSource: "registry",
|
|
50
62
|
type: "registry",
|
|
@@ -73,19 +85,19 @@ export async function agentikitSearch(input) {
|
|
|
73
85
|
};
|
|
74
86
|
}
|
|
75
87
|
const mergedHits = mergeSearchHits(localResult?.hits ?? [], registryHits, limit);
|
|
88
|
+
const warnings = [...(localResult?.warnings ?? []), ...(registryResult?.warnings ?? [])];
|
|
76
89
|
return {
|
|
77
90
|
stashDir,
|
|
78
91
|
source,
|
|
79
92
|
hits: mergedHits,
|
|
80
93
|
usageGuide: localResult?.usageGuide,
|
|
81
94
|
tip: mergedHits.length === 0 ? "No matching stash assets or registry entries were found." : undefined,
|
|
82
|
-
warnings:
|
|
95
|
+
warnings: warnings.length ? warnings : undefined,
|
|
83
96
|
timing: { totalMs: Date.now() - t0 },
|
|
84
97
|
};
|
|
85
98
|
}
|
|
86
99
|
async function searchLocal(input) {
|
|
87
|
-
const { query, searchType, limit, usageMode, stashDir, sources } = input;
|
|
88
|
-
const config = loadConfig();
|
|
100
|
+
const { query, searchType, limit, usageMode, stashDir, sources, config } = input;
|
|
89
101
|
const allStashDirs = sources.map((s) => s.path);
|
|
90
102
|
// Try to open the database
|
|
91
103
|
const dbPath = getDbPath();
|
|
@@ -101,7 +113,9 @@ async function searchLocal(input) {
|
|
|
101
113
|
return {
|
|
102
114
|
hits,
|
|
103
115
|
usageGuide,
|
|
104
|
-
tip: hits.length === 0
|
|
116
|
+
tip: hits.length === 0
|
|
117
|
+
? "No matching stash assets were found. Try running 'akm index' to rebuild."
|
|
118
|
+
: undefined,
|
|
105
119
|
embedMs,
|
|
106
120
|
rankMs,
|
|
107
121
|
};
|
|
@@ -113,12 +127,14 @@ async function searchLocal(input) {
|
|
|
113
127
|
}
|
|
114
128
|
}
|
|
115
129
|
catch (error) {
|
|
116
|
-
|
|
130
|
+
warn("Search index unavailable, falling back to substring search:", error instanceof Error ? error.message : String(error));
|
|
117
131
|
}
|
|
118
132
|
const hits = allStashDirs
|
|
119
|
-
.flatMap((dir) => substringSearch(query, searchType, limit, dir, sources))
|
|
133
|
+
.flatMap((dir) => substringSearch(query, searchType, limit, dir, sources, config))
|
|
120
134
|
.slice(0, limit);
|
|
121
|
-
const usageGuide = shouldIncludeUsageGuide(usageMode)
|
|
135
|
+
const usageGuide = shouldIncludeUsageGuide(usageMode)
|
|
136
|
+
? buildUsageGuide(hits.map((hit) => hit.type), searchType)
|
|
137
|
+
: undefined;
|
|
122
138
|
return {
|
|
123
139
|
hits,
|
|
124
140
|
usageGuide,
|
|
@@ -142,6 +158,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allStashDi
|
|
|
142
158
|
allStashDirs,
|
|
143
159
|
sources,
|
|
144
160
|
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
161
|
+
config,
|
|
145
162
|
}));
|
|
146
163
|
return {
|
|
147
164
|
hits,
|
|
@@ -157,58 +174,67 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allStashDi
|
|
|
157
174
|
const tRank0 = Date.now();
|
|
158
175
|
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
159
176
|
const ftsResults = searchFts(db, query, limit * 3, typeFilter);
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
// Reciprocal Rank Fusion (RRF) constant
|
|
178
|
+
const RRF_K = 60;
|
|
179
|
+
// Build FTS rank map: rank 1 = best BM25, rank 2 = second best, etc.
|
|
180
|
+
// FTS results are already sorted by bm25Score (ascending, more negative = better)
|
|
181
|
+
const ftsRankMap = new Map();
|
|
182
|
+
for (let i = 0; i < ftsResults.length; i++) {
|
|
183
|
+
const r = ftsResults[i];
|
|
184
|
+
ftsRankMap.set(r.id, { rank: i + 1, result: r });
|
|
185
|
+
}
|
|
186
|
+
// Build embedding rank map: sort by cosine similarity descending
|
|
187
|
+
const embedRankMap = new Map();
|
|
188
|
+
if (embeddingScores) {
|
|
189
|
+
const sortedEmbeddings = [...embeddingScores.entries()].sort((a, b) => b[1] - a[1]);
|
|
190
|
+
for (let i = 0; i < sortedEmbeddings.length; i++) {
|
|
191
|
+
embedRankMap.set(sortedEmbeddings[i][0], i + 1);
|
|
192
|
+
}
|
|
167
193
|
}
|
|
168
|
-
//
|
|
194
|
+
// Merge results using RRF
|
|
169
195
|
const scored = [];
|
|
170
196
|
const seenIds = new Set();
|
|
171
197
|
// Process FTS results
|
|
172
|
-
for (const [id, {
|
|
198
|
+
for (const [id, { rank, result }] of ftsRankMap) {
|
|
173
199
|
seenIds.add(id);
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
else if (ftsScore > 0) {
|
|
181
|
-
scored.push({ id, entry: result.entry, filePath: result.filePath, score: ftsScore, rankingMode: "fts" });
|
|
182
|
-
}
|
|
200
|
+
const ftsRrf = 1 / (RRF_K + rank);
|
|
201
|
+
const embedRank = embedRankMap.get(id);
|
|
202
|
+
const embedRrf = embedRank !== undefined ? 1 / (RRF_K + embedRank) : 0;
|
|
203
|
+
const rrfScore = ftsRrf + embedRrf;
|
|
204
|
+
const rankingMode = embedRrf > 0 ? "semantic" : "fts";
|
|
205
|
+
scored.push({ id, entry: result.entry, filePath: result.filePath, score: rrfScore, rankingMode });
|
|
183
206
|
}
|
|
184
207
|
// Add vec-only results not already in FTS results
|
|
185
208
|
if (embeddingScores) {
|
|
186
|
-
for (const [id
|
|
209
|
+
for (const [id] of embeddingScores) {
|
|
187
210
|
if (seenIds.has(id))
|
|
188
211
|
continue;
|
|
212
|
+
const embedRank = embedRankMap.get(id);
|
|
189
213
|
const found = getEntryById(db, id);
|
|
190
214
|
if (found) {
|
|
191
215
|
if (typeFilter && found.entry.type !== typeFilter)
|
|
192
216
|
continue;
|
|
217
|
+
const rrfScore = 1 / (RRF_K + embedRank);
|
|
193
218
|
scored.push({
|
|
194
219
|
id,
|
|
195
220
|
entry: found.entry,
|
|
196
221
|
filePath: found.filePath,
|
|
197
|
-
score:
|
|
222
|
+
score: rrfScore,
|
|
198
223
|
rankingMode: "semantic",
|
|
199
224
|
});
|
|
200
225
|
}
|
|
201
226
|
}
|
|
202
227
|
}
|
|
203
|
-
// Apply boosts
|
|
228
|
+
// Apply boosts as multiplicative factors
|
|
204
229
|
const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
205
230
|
for (const item of scored) {
|
|
206
231
|
const entry = item.entry;
|
|
232
|
+
let boostSum = 0;
|
|
207
233
|
// Tag boost
|
|
208
234
|
if (entry.tags) {
|
|
209
235
|
for (const tag of entry.tags) {
|
|
210
236
|
if (queryTokens.some((t) => tag.toLowerCase() === t)) {
|
|
211
|
-
|
|
237
|
+
boostSum += 0.15;
|
|
212
238
|
}
|
|
213
239
|
}
|
|
214
240
|
}
|
|
@@ -218,7 +244,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allStashDi
|
|
|
218
244
|
const intentLower = intent.toLowerCase();
|
|
219
245
|
for (const token of queryTokens) {
|
|
220
246
|
if (intentLower.includes(token)) {
|
|
221
|
-
|
|
247
|
+
boostSum += 0.12;
|
|
222
248
|
break;
|
|
223
249
|
}
|
|
224
250
|
}
|
|
@@ -227,11 +253,9 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allStashDi
|
|
|
227
253
|
// Name boost
|
|
228
254
|
const nameLower = entry.name.toLowerCase().replace(/[-_]/g, " ");
|
|
229
255
|
if (queryTokens.some((t) => nameLower.includes(t))) {
|
|
230
|
-
|
|
256
|
+
boostSum += 0.1;
|
|
231
257
|
}
|
|
232
|
-
|
|
233
|
-
for (const item of scored) {
|
|
234
|
-
item.score = Math.min(item.score, 1.0);
|
|
258
|
+
item.score = item.score * (1 + boostSum);
|
|
235
259
|
}
|
|
236
260
|
scored.sort((a, b) => b.score - a.score);
|
|
237
261
|
const rankMs = Date.now() - tRank0;
|
|
@@ -246,6 +270,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allStashDi
|
|
|
246
270
|
allStashDirs,
|
|
247
271
|
sources,
|
|
248
272
|
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
273
|
+
config,
|
|
249
274
|
}));
|
|
250
275
|
return {
|
|
251
276
|
embedMs,
|
|
@@ -258,7 +283,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allStashDi
|
|
|
258
283
|
}
|
|
259
284
|
// ── Vector scorer ───────────────────────────────────────────────────────────
|
|
260
285
|
async function tryVecScores(db, query, k, config) {
|
|
261
|
-
if (!config.semanticSearch
|
|
286
|
+
if (!config.semanticSearch)
|
|
262
287
|
return null;
|
|
263
288
|
const hasEmbeddings = getMeta(db, "hasEmbeddings");
|
|
264
289
|
if (hasEmbeddings !== "1")
|
|
@@ -276,30 +301,30 @@ async function tryVecScores(db, query, k, config) {
|
|
|
276
301
|
return scores;
|
|
277
302
|
}
|
|
278
303
|
catch (error) {
|
|
279
|
-
|
|
304
|
+
warn("Vector search failed, skipping:", error instanceof Error ? error.message : String(error));
|
|
280
305
|
return null;
|
|
281
306
|
}
|
|
282
307
|
}
|
|
283
308
|
// ── Substring fallback (no index) ───────────────────────────────────────────
|
|
284
|
-
function substringSearch(query, searchType, limit, stashDir, sources) {
|
|
309
|
+
function substringSearch(query, searchType, limit, stashDir, sources, config) {
|
|
285
310
|
const assets = indexAssets(stashDir, searchType);
|
|
286
311
|
return assets
|
|
287
312
|
.filter((asset) => asset.name.toLowerCase().includes(query))
|
|
288
313
|
.sort(compareAssets)
|
|
289
314
|
.slice(0, limit)
|
|
290
|
-
.map((asset) => assetToSearchHit(asset, stashDir, sources));
|
|
315
|
+
.map((asset) => assetToSearchHit(asset, stashDir, sources, config));
|
|
291
316
|
}
|
|
292
317
|
// ── Hit building ────────────────────────────────────────────────────────────
|
|
293
318
|
function buildDbHit(input) {
|
|
294
319
|
const entryStashDir = findSourceForPath(input.path, input.sources)?.path ?? input.defaultStashDir;
|
|
295
320
|
const typeRoot = path.join(entryStashDir, TYPE_DIRS[input.entry.type]);
|
|
296
|
-
const openRefName = deriveCanonicalAssetName(input.entry.type, typeRoot, input.path)
|
|
297
|
-
?? input.entry.name;
|
|
321
|
+
const openRefName = deriveCanonicalAssetName(input.entry.type, typeRoot, input.path) ?? input.entry.name;
|
|
298
322
|
const qualityBoost = input.entry.generated === true ? 0 : 0.05;
|
|
299
323
|
const confidenceBoost = typeof input.entry.confidence === "number" ? Math.min(0.05, Math.max(0, input.entry.confidence) * 0.05) : 0;
|
|
300
|
-
const score = Math.
|
|
324
|
+
const score = Math.round(input.score * (1 + qualityBoost + confidenceBoost) * 1000) / 1000;
|
|
301
325
|
const whyMatched = buildWhyMatched(input.entry, input.query, input.rankingMode, qualityBoost, confidenceBoost);
|
|
302
326
|
const source = findSourceForPath(input.path, input.sources);
|
|
327
|
+
const editable = isEditable(input.path, input.config);
|
|
303
328
|
const hit = {
|
|
304
329
|
hitSource: "local",
|
|
305
330
|
type: input.entry.type,
|
|
@@ -307,7 +332,8 @@ function buildDbHit(input) {
|
|
|
307
332
|
path: input.path,
|
|
308
333
|
openRef: makeAssetRef(input.entry.type, openRefName, source?.registryId),
|
|
309
334
|
registryId: source?.registryId,
|
|
310
|
-
editable
|
|
335
|
+
editable,
|
|
336
|
+
...(!editable ? { editHint: buildEditHint(input.path, input.entry.type, openRefName, source?.registryId) } : {}),
|
|
311
337
|
description: input.entry.description,
|
|
312
338
|
tags: input.entry.tags,
|
|
313
339
|
score,
|
|
@@ -344,8 +370,9 @@ function buildWhyMatched(entry, query, rankingMode, qualityBoost, confidenceBoos
|
|
|
344
370
|
return reasons;
|
|
345
371
|
}
|
|
346
372
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
347
|
-
function assetToSearchHit(asset, stashDir, sources) {
|
|
373
|
+
function assetToSearchHit(asset, stashDir, sources, config) {
|
|
348
374
|
const source = findSourceForPath(asset.path, sources);
|
|
375
|
+
const editable = isEditable(asset.path, config);
|
|
349
376
|
const hit = {
|
|
350
377
|
hitSource: "local",
|
|
351
378
|
type: asset.type,
|
|
@@ -353,7 +380,8 @@ function assetToSearchHit(asset, stashDir, sources) {
|
|
|
353
380
|
path: asset.path,
|
|
354
381
|
openRef: makeAssetRef(asset.type, asset.name, source?.registryId),
|
|
355
382
|
registryId: source?.registryId,
|
|
356
|
-
editable
|
|
383
|
+
editable,
|
|
384
|
+
...(!editable ? { editHint: buildEditHint(asset.path, asset.type, asset.name, source?.registryId) } : {}),
|
|
357
385
|
};
|
|
358
386
|
const handler = tryGetHandler(asset.type);
|
|
359
387
|
if (handler?.enrichSearchHit) {
|
|
@@ -373,32 +401,19 @@ function parseSearchUsageMode(mode) {
|
|
|
373
401
|
}
|
|
374
402
|
if (typeof mode === "undefined")
|
|
375
403
|
return "both";
|
|
376
|
-
throw new
|
|
404
|
+
throw new UsageError(`Invalid usage mode: ${String(mode)}. Expected one of: none|both|item|guide`);
|
|
377
405
|
}
|
|
378
406
|
function parseSearchSource(source) {
|
|
379
407
|
if (source === "local" || source === "registry" || source === "both")
|
|
380
408
|
return source;
|
|
381
409
|
if (typeof source === "undefined")
|
|
382
410
|
return "local";
|
|
383
|
-
throw new
|
|
411
|
+
throw new UsageError(`Invalid search source: ${String(source)}. Expected one of: local|registry|both`);
|
|
384
412
|
}
|
|
385
413
|
function mergeSearchHits(localHits, registryHits, limit) {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
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;
|
|
414
|
+
const all = [...localHits, ...registryHits];
|
|
415
|
+
all.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
416
|
+
return all.slice(0, limit);
|
|
402
417
|
}
|
|
403
418
|
function shouldIncludeUsageGuide(mode) {
|
|
404
419
|
return mode === "both" || mode === "guide";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { getHandler } from "./asset-type-handler";
|
|
3
|
+
import { loadConfig } from "./config";
|
|
4
|
+
import { NotFoundError } from "./errors";
|
|
5
|
+
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "./file-context";
|
|
6
|
+
import { resolveSourcesForOrigin } from "./origin-resolve";
|
|
7
|
+
import { parseAssetRef } from "./stash-ref";
|
|
8
|
+
import { resolveAssetPath } from "./stash-resolve";
|
|
9
|
+
import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./stash-source";
|
|
10
|
+
export async function agentikitShow(input) {
|
|
11
|
+
const parsed = parseAssetRef(input.ref);
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
const allSources = resolveStashSources();
|
|
14
|
+
const searchSources = resolveSourcesForOrigin(parsed.origin, allSources);
|
|
15
|
+
const allStashDirs = searchSources.map((s) => s.path);
|
|
16
|
+
let assetPath;
|
|
17
|
+
let lastError;
|
|
18
|
+
for (const dir of allStashDirs) {
|
|
19
|
+
try {
|
|
20
|
+
assetPath = resolveAssetPath(dir, parsed.type, parsed.name);
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!assetPath && parsed.origin && searchSources.length === 0) {
|
|
28
|
+
const installCmd = `akm add ${parsed.origin}`;
|
|
29
|
+
throw new NotFoundError(`Stash asset not found for ref: ${parsed.type}:${parsed.name}. ` +
|
|
30
|
+
`Kit "${parsed.origin}" is not installed. Run: ${installCmd}`);
|
|
31
|
+
}
|
|
32
|
+
if (!assetPath) {
|
|
33
|
+
throw lastError ?? new NotFoundError(`Stash asset not found for ref: ${parsed.type}:${parsed.name}`);
|
|
34
|
+
}
|
|
35
|
+
const source = findSourceForPath(assetPath, allSources);
|
|
36
|
+
const sourceStashDir = source?.path ?? allStashDirs[0];
|
|
37
|
+
// Try new renderer pipeline first
|
|
38
|
+
if (sourceStashDir) {
|
|
39
|
+
const fileCtx = buildFileContext(sourceStashDir, assetPath);
|
|
40
|
+
const match = runMatchers(fileCtx);
|
|
41
|
+
if (match) {
|
|
42
|
+
match.meta = { ...match.meta, name: parsed.name, view: input.view };
|
|
43
|
+
const renderer = getRenderer(match.renderer);
|
|
44
|
+
if (renderer) {
|
|
45
|
+
const renderCtx = buildRenderContext(fileCtx, match, allStashDirs);
|
|
46
|
+
const response = renderer.buildShowResponse(renderCtx);
|
|
47
|
+
const editable = isEditable(assetPath, config);
|
|
48
|
+
return {
|
|
49
|
+
...response,
|
|
50
|
+
registryId: source?.registryId,
|
|
51
|
+
editable,
|
|
52
|
+
...(!editable ? { editHint: buildEditHint(assetPath, parsed.type, parsed.name, source?.registryId) } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Fallback to legacy handler
|
|
58
|
+
const content = fs.readFileSync(assetPath, "utf8");
|
|
59
|
+
const handler = getHandler(parsed.type);
|
|
60
|
+
const response = handler.buildShowResponse({
|
|
61
|
+
name: parsed.name,
|
|
62
|
+
path: assetPath,
|
|
63
|
+
content,
|
|
64
|
+
view: input.view,
|
|
65
|
+
stashDirs: allStashDirs,
|
|
66
|
+
});
|
|
67
|
+
const editable = isEditable(assetPath, config);
|
|
68
|
+
return {
|
|
69
|
+
...response,
|
|
70
|
+
registryId: source?.registryId,
|
|
71
|
+
editable,
|
|
72
|
+
...(!editable ? { editHint: buildEditHint(assetPath, parsed.type, parsed.name, source?.registryId) } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveStashDir } from "./common";
|
|
4
|
+
import { loadConfig } from "./config";
|
|
5
|
+
import { warn } from "./warn";
|
|
6
|
+
// ── Resolution ──────────────────────────────────────────────────────────────
|
|
7
|
+
/**
|
|
8
|
+
* Build the ordered list of stash sources (search paths):
|
|
9
|
+
* 1. Primary stash dir (user's own, destination for clone)
|
|
10
|
+
* 2. Additional search paths (user-configured)
|
|
11
|
+
* 3. Installed kit paths (cache-managed, from registry)
|
|
12
|
+
*
|
|
13
|
+
* The first entry is always the primary stash. Additional entries come
|
|
14
|
+
* from `searchPaths` config and `registry.installed` entries.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
17
|
+
const stashDir = overrideStashDir ?? resolveStashDir();
|
|
18
|
+
const config = existingConfig ?? loadConfig();
|
|
19
|
+
const sources = [{ path: stashDir }];
|
|
20
|
+
for (const dir of config.searchPaths) {
|
|
21
|
+
if (isSuspiciousStashRoot(dir)) {
|
|
22
|
+
warn(`Warning: stash root "${dir}" appears to be a system directory. This may be unintentional.`);
|
|
23
|
+
}
|
|
24
|
+
if (isValidDirectory(dir)) {
|
|
25
|
+
sources.push({ path: dir });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const entry of config.registry?.installed ?? []) {
|
|
29
|
+
if (isSuspiciousStashRoot(entry.stashRoot)) {
|
|
30
|
+
warn(`Warning: stash root "${entry.stashRoot}" appears to be a system directory. This may be unintentional.`);
|
|
31
|
+
}
|
|
32
|
+
if (isValidDirectory(entry.stashRoot)) {
|
|
33
|
+
sources.push({
|
|
34
|
+
path: entry.stashRoot,
|
|
35
|
+
registryId: entry.id,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return sources;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convenience: returns just the directory paths, preserving priority order.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveAllStashDirs(overrideStashDir) {
|
|
45
|
+
return resolveStashSources(overrideStashDir).map((s) => s.path);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Find which source a file path belongs to.
|
|
49
|
+
*/
|
|
50
|
+
export function findSourceForPath(filePath, sources) {
|
|
51
|
+
const resolved = path.resolve(filePath);
|
|
52
|
+
for (const source of sources) {
|
|
53
|
+
if (resolved.startsWith(path.resolve(source.path) + path.sep))
|
|
54
|
+
return source;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Return the primary stash source (first entry in the list).
|
|
60
|
+
* This is the user's working stash and the default destination for clone.
|
|
61
|
+
*/
|
|
62
|
+
export function getPrimarySource(sources) {
|
|
63
|
+
return sources[0];
|
|
64
|
+
}
|
|
65
|
+
// ── Editability ─────────────────────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* Determine whether a file is safe to edit in place.
|
|
68
|
+
*
|
|
69
|
+
* The only files that are NOT editable are those inside a cache directory
|
|
70
|
+
* managed by the package manager (`registry.installed[].cacheDir`). These
|
|
71
|
+
* will be overwritten by `akm update` without warning.
|
|
72
|
+
*
|
|
73
|
+
* Everything else — working stash, search paths, local project dirs — is
|
|
74
|
+
* the user's domain to manage.
|
|
75
|
+
*/
|
|
76
|
+
export function isEditable(filePath, config) {
|
|
77
|
+
const cfg = config ?? loadConfig();
|
|
78
|
+
const resolved = path.resolve(filePath);
|
|
79
|
+
const cacheManaged = cfg.registry?.installed ?? [];
|
|
80
|
+
const isWin = process.platform === "win32";
|
|
81
|
+
for (const entry of cacheManaged) {
|
|
82
|
+
const cacheRoot = path.resolve(entry.cacheDir);
|
|
83
|
+
if (isWin) {
|
|
84
|
+
// Windows paths are case-insensitive — normalize both sides
|
|
85
|
+
if (resolved.toLowerCase().startsWith(cacheRoot.toLowerCase() + path.sep))
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
if (resolved.startsWith(cacheRoot + path.sep))
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build an actionable hint for the agent when a file is not editable.
|
|
97
|
+
* Callers must check `isEditable()` before calling — this function
|
|
98
|
+
* unconditionally returns the hint string.
|
|
99
|
+
*/
|
|
100
|
+
export function buildEditHint(filePath, assetType, assetName, origin) {
|
|
101
|
+
const ref = origin ? `${origin}//${assetType}:${assetName}` : `${assetType}:${assetName}`;
|
|
102
|
+
return `This asset is managed by akm and may be overwritten on update. To edit, run: akm clone ${ref}`;
|
|
103
|
+
}
|
|
104
|
+
// ── Validation ──────────────────────────────────────────────────────────────
|
|
105
|
+
const SUSPICIOUS_ROOTS = new Set(["/", "/etc", "/bin", "/sbin", "/usr", "/var", "/tmp", "/dev", "/proc", "/sys"]);
|
|
106
|
+
function isSuspiciousStashRoot(dir) {
|
|
107
|
+
const resolved = path.resolve(dir);
|
|
108
|
+
const normalized = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
109
|
+
if (SUSPICIOUS_ROOTS.has(normalized))
|
|
110
|
+
return true;
|
|
111
|
+
if (process.platform === "win32") {
|
|
112
|
+
// Check for Windows system directories
|
|
113
|
+
const winDir = (process.env.SystemRoot || "C:\\Windows").toLowerCase();
|
|
114
|
+
if (normalized === winDir || normalized.startsWith(winDir + path.sep))
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
120
|
+
function isValidDirectory(dir) {
|
|
121
|
+
try {
|
|
122
|
+
return fs.statSync(dir).isDirectory();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|