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