agentikit 0.0.7 → 0.0.9
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 +215 -76
- package/dist/index.d.ts +17 -3
- package/dist/index.js +10 -2
- package/dist/src/asset-spec.d.ts +14 -0
- package/dist/src/asset-spec.js +46 -0
- package/dist/src/cli.js +268 -57
- package/dist/src/common.d.ts +8 -0
- package/dist/src/common.js +46 -0
- package/dist/src/config.d.ts +37 -0
- package/dist/src/config.js +124 -0
- package/dist/src/embedder.d.ts +10 -0
- package/dist/src/embedder.js +87 -0
- package/dist/src/frontmatter.d.ts +30 -0
- package/dist/src/frontmatter.js +86 -0
- package/dist/src/indexer.d.ts +20 -2
- package/dist/src/indexer.js +212 -80
- package/dist/src/init.d.ts +19 -0
- package/dist/src/init.js +87 -0
- package/dist/src/llm.d.ts +15 -0
- package/dist/src/llm.js +91 -0
- package/dist/src/markdown.d.ts +18 -0
- package/dist/src/markdown.js +77 -0
- package/dist/src/metadata.d.ts +11 -2
- package/dist/src/metadata.js +161 -29
- package/dist/src/registry-install.d.ts +11 -0
- package/dist/src/registry-install.js +208 -0
- package/dist/src/registry-resolve.d.ts +3 -0
- package/dist/src/registry-resolve.js +231 -0
- package/dist/src/registry-search.d.ts +5 -0
- package/dist/src/registry-search.js +129 -0
- package/dist/src/registry-types.d.ts +55 -0
- package/dist/src/registry-types.js +1 -0
- package/dist/src/ripgrep-install.d.ts +12 -0
- package/dist/src/ripgrep-install.js +169 -0
- package/dist/src/ripgrep-resolve.d.ts +13 -0
- package/dist/src/ripgrep-resolve.js +68 -0
- package/dist/src/ripgrep.d.ts +3 -36
- package/dist/src/ripgrep.js +2 -262
- package/dist/src/similarity.d.ts +1 -2
- package/dist/src/similarity.js +11 -0
- package/dist/src/stash-add.d.ts +4 -0
- package/dist/src/stash-add.js +59 -0
- package/dist/src/stash-ref.d.ts +7 -0
- package/dist/src/stash-ref.js +33 -0
- package/dist/src/stash-registry.d.ts +18 -0
- package/dist/src/stash-registry.js +221 -0
- package/dist/src/stash-resolve.d.ts +2 -0
- package/dist/src/stash-resolve.js +45 -0
- package/dist/src/stash-search.d.ts +8 -0
- package/dist/src/stash-search.js +484 -0
- package/dist/src/stash-show.d.ts +5 -0
- package/dist/src/stash-show.js +114 -0
- package/dist/src/stash-types.d.ts +217 -0
- package/dist/src/stash-types.js +1 -0
- package/dist/src/stash.d.ts +10 -63
- package/dist/src/stash.js +6 -633
- package/dist/src/tool-runner.d.ts +35 -0
- package/dist/src/tool-runner.js +100 -0
- package/dist/src/walker.d.ts +19 -0
- package/dist/src/walker.js +47 -0
- package/package.json +8 -14
- package/src/asset-spec.ts +69 -0
- package/src/cli.ts +282 -46
- package/src/common.ts +58 -0
- package/src/config.ts +183 -0
- package/src/embedder.ts +117 -0
- package/src/frontmatter.ts +95 -0
- package/src/indexer.ts +244 -84
- package/src/init.ts +106 -0
- package/src/llm.ts +124 -0
- package/src/markdown.ts +106 -0
- package/src/metadata.ts +171 -27
- package/src/registry-install.ts +245 -0
- package/src/registry-resolve.ts +272 -0
- package/src/registry-search.ts +145 -0
- package/src/registry-types.ts +64 -0
- package/src/ripgrep-install.ts +200 -0
- package/src/ripgrep-resolve.ts +72 -0
- package/src/ripgrep.ts +3 -315
- package/src/similarity.ts +13 -1
- package/src/stash-add.ts +66 -0
- package/src/stash-ref.ts +41 -0
- package/src/stash-registry.ts +259 -0
- package/src/stash-resolve.ts +47 -0
- package/src/stash-search.ts +595 -0
- package/src/stash-show.ts +112 -0
- package/src/stash-types.ts +221 -0
- package/src/stash.ts +31 -760
- package/src/tool-runner.ts +129 -0
- package/src/walker.ts +53 -0
- package/.claude-plugin/plugin.json +0 -21
- package/commands/open.md +0 -11
- package/commands/run.md +0 -11
- package/commands/search.md +0 -11
- package/dist/src/plugin.d.ts +0 -2
- package/dist/src/plugin.js +0 -55
- package/skills/stash/SKILL.md +0 -73
- package/src/plugin.ts +0 -56
package/dist/src/llm.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
async function chatCompletion(config, messages) {
|
|
2
|
+
const headers = { "Content-Type": "application/json" };
|
|
3
|
+
if (config.apiKey) {
|
|
4
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
5
|
+
}
|
|
6
|
+
const response = await fetch(config.endpoint, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers,
|
|
9
|
+
body: JSON.stringify({
|
|
10
|
+
model: config.model,
|
|
11
|
+
messages,
|
|
12
|
+
temperature: 0.3,
|
|
13
|
+
max_tokens: 512,
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
const body = await response.text().catch(() => "");
|
|
18
|
+
throw new Error(`LLM request failed (${response.status}): ${body}`);
|
|
19
|
+
}
|
|
20
|
+
const json = (await response.json());
|
|
21
|
+
return json.choices?.[0]?.message?.content?.trim() ?? "";
|
|
22
|
+
}
|
|
23
|
+
// ── Metadata Enhancement ────────────────────────────────────────────────────
|
|
24
|
+
const SYSTEM_PROMPT = `You are a metadata generator for a developer tool registry. Given a tool/skill/command/agent entry, generate improved metadata. Respond with ONLY valid JSON, no markdown fencing.`;
|
|
25
|
+
/**
|
|
26
|
+
* Use an LLM to enhance a stash entry's metadata: improve description,
|
|
27
|
+
* generate intents, and suggest tags.
|
|
28
|
+
*/
|
|
29
|
+
export async function enhanceMetadata(config, entry, fileContent) {
|
|
30
|
+
const contextParts = [
|
|
31
|
+
`Name: ${entry.name}`,
|
|
32
|
+
`Type: ${entry.type}`,
|
|
33
|
+
];
|
|
34
|
+
if (entry.description)
|
|
35
|
+
contextParts.push(`Current description: ${entry.description}`);
|
|
36
|
+
if (entry.tags?.length)
|
|
37
|
+
contextParts.push(`Current tags: ${entry.tags.join(", ")}`);
|
|
38
|
+
if (fileContent) {
|
|
39
|
+
// Limit content to first 2000 chars to stay within token limits
|
|
40
|
+
const truncated = fileContent.length > 2000
|
|
41
|
+
? fileContent.slice(0, 2000) + "\n... (truncated)"
|
|
42
|
+
: fileContent;
|
|
43
|
+
contextParts.push(`File content:\n${truncated}`);
|
|
44
|
+
}
|
|
45
|
+
const userPrompt = `${contextParts.join("\n")}
|
|
46
|
+
|
|
47
|
+
Generate improved metadata for this ${entry.type}. Return JSON with these fields:
|
|
48
|
+
- "description": a clear, concise one-sentence description of what this does
|
|
49
|
+
- "intents": an array of 3-6 natural language task phrases an agent might use to find this (e.g. "deploy a docker container", "run database migrations")
|
|
50
|
+
- "tags": an array of 3-8 relevant keyword tags
|
|
51
|
+
|
|
52
|
+
Return ONLY the JSON object, no explanation.`;
|
|
53
|
+
const raw = await chatCompletion(config, [
|
|
54
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
55
|
+
{ role: "user", content: userPrompt },
|
|
56
|
+
]);
|
|
57
|
+
try {
|
|
58
|
+
// Strip markdown code fences if present
|
|
59
|
+
const cleaned = raw.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "");
|
|
60
|
+
const parsed = JSON.parse(cleaned);
|
|
61
|
+
const result = {};
|
|
62
|
+
if (typeof parsed.description === "string" && parsed.description) {
|
|
63
|
+
result.description = parsed.description;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(parsed.intents)) {
|
|
66
|
+
result.intents = parsed.intents.filter((s) => typeof s === "string" && s.trim().length > 0).slice(0, 8);
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(parsed.tags)) {
|
|
69
|
+
result.tags = parsed.tags.filter((s) => typeof s === "string" && s.trim().length > 0).slice(0, 10);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// LLM returned unparseable output, return empty
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if the LLM endpoint is reachable.
|
|
80
|
+
*/
|
|
81
|
+
export async function isLlmAvailable(config) {
|
|
82
|
+
try {
|
|
83
|
+
const result = await chatCompletion(config, [
|
|
84
|
+
{ role: "user", content: "Respond with just the word: ok" },
|
|
85
|
+
]);
|
|
86
|
+
return result.length > 0;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface TocHeading {
|
|
2
|
+
level: number;
|
|
3
|
+
text: string;
|
|
4
|
+
line: number;
|
|
5
|
+
}
|
|
6
|
+
export interface KnowledgeToc {
|
|
7
|
+
headings: TocHeading[];
|
|
8
|
+
totalLines: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseMarkdownToc(content: string): KnowledgeToc;
|
|
11
|
+
export declare function extractSection(content: string, heading: string): {
|
|
12
|
+
content: string;
|
|
13
|
+
startLine: number;
|
|
14
|
+
endLine: number;
|
|
15
|
+
} | null;
|
|
16
|
+
export declare function extractLineRange(content: string, start: number, end: number): string;
|
|
17
|
+
export declare function extractFrontmatterOnly(content: string): string | null;
|
|
18
|
+
export declare function formatToc(toc: KnowledgeToc): string;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parseFrontmatter } from "./frontmatter";
|
|
2
|
+
// ── Parsing ─────────────────────────────────────────────────────────────────
|
|
3
|
+
export function parseMarkdownToc(content) {
|
|
4
|
+
const lines = content.split(/\r?\n/);
|
|
5
|
+
const headings = [];
|
|
6
|
+
const parsed = parseFrontmatter(content);
|
|
7
|
+
const start = parsed.frontmatter ? parsed.bodyStartLine - 1 : 0;
|
|
8
|
+
for (let i = start; i < lines.length; i++) {
|
|
9
|
+
const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
|
|
10
|
+
if (match) {
|
|
11
|
+
headings.push({
|
|
12
|
+
level: match[1].length,
|
|
13
|
+
text: match[2].replace(/\s+#+\s*$/, "").trim(),
|
|
14
|
+
line: i + 1,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { headings, totalLines: lines.length };
|
|
19
|
+
}
|
|
20
|
+
// ── Extraction ──────────────────────────────────────────────────────────────
|
|
21
|
+
export function extractSection(content, heading) {
|
|
22
|
+
const lines = content.split(/\r?\n/);
|
|
23
|
+
const target = heading.toLowerCase();
|
|
24
|
+
let startIdx = -1;
|
|
25
|
+
let startLevel = 0;
|
|
26
|
+
for (let i = 0; i < lines.length; i++) {
|
|
27
|
+
const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
|
|
28
|
+
if (!match)
|
|
29
|
+
continue;
|
|
30
|
+
const text = match[2].replace(/\s+#+\s*$/, "").trim();
|
|
31
|
+
if (text.toLowerCase() === target && startIdx === -1) {
|
|
32
|
+
startIdx = i;
|
|
33
|
+
startLevel = match[1].length;
|
|
34
|
+
}
|
|
35
|
+
else if (startIdx !== -1 && match[1].length <= startLevel) {
|
|
36
|
+
return {
|
|
37
|
+
content: lines.slice(startIdx, i).join("\n"),
|
|
38
|
+
startLine: startIdx + 1,
|
|
39
|
+
endLine: i,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (startIdx === -1)
|
|
44
|
+
return null;
|
|
45
|
+
return {
|
|
46
|
+
content: lines.slice(startIdx).join("\n"),
|
|
47
|
+
startLine: startIdx + 1,
|
|
48
|
+
endLine: lines.length,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function extractLineRange(content, start, end) {
|
|
52
|
+
const lines = content.split(/\r?\n/);
|
|
53
|
+
if (end < start)
|
|
54
|
+
return "";
|
|
55
|
+
const s = Math.max(1, Math.min(start, lines.length));
|
|
56
|
+
const e = Math.min(end, lines.length);
|
|
57
|
+
return lines.slice(s - 1, e).join("\n");
|
|
58
|
+
}
|
|
59
|
+
export function extractFrontmatterOnly(content) {
|
|
60
|
+
const parsed = parseFrontmatter(content);
|
|
61
|
+
return parsed.frontmatter;
|
|
62
|
+
}
|
|
63
|
+
// ── Formatting ──────────────────────────────────────────────────────────────
|
|
64
|
+
export function formatToc(toc) {
|
|
65
|
+
if (toc.headings.length === 0) {
|
|
66
|
+
return `(no headings found — ${toc.totalLines} lines total)`;
|
|
67
|
+
}
|
|
68
|
+
const lineWidth = String(toc.totalLines).length;
|
|
69
|
+
const parts = toc.headings.map((h) => {
|
|
70
|
+
const lineNum = `L${String(h.line).padStart(lineWidth)}`;
|
|
71
|
+
const indent = " ".repeat(h.level - 1);
|
|
72
|
+
const prefix = "#".repeat(h.level);
|
|
73
|
+
return `${lineNum} ${indent}${prefix} ${h.text}`;
|
|
74
|
+
});
|
|
75
|
+
parts.push(`\n${toc.totalLines} lines total`);
|
|
76
|
+
return parts.join("\n");
|
|
77
|
+
}
|
package/dist/src/metadata.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AgentikitAssetType } from "./common";
|
|
2
|
+
import { type TocHeading } from "./markdown";
|
|
2
3
|
export interface StashIntent {
|
|
3
4
|
when?: string;
|
|
4
5
|
input?: string;
|
|
@@ -10,9 +11,16 @@ export interface StashEntry {
|
|
|
10
11
|
description?: string;
|
|
11
12
|
tags?: string[];
|
|
12
13
|
examples?: string[];
|
|
14
|
+
intents?: string[];
|
|
13
15
|
intent?: StashIntent;
|
|
14
16
|
entry?: string;
|
|
15
17
|
generated?: boolean;
|
|
18
|
+
quality?: "generated" | "curated";
|
|
19
|
+
confidence?: number;
|
|
20
|
+
source?: "package" | "frontmatter" | "comments" | "filename" | "manual" | "llm";
|
|
21
|
+
aliases?: string[];
|
|
22
|
+
toc?: TocHeading[];
|
|
23
|
+
usage?: string[];
|
|
16
24
|
}
|
|
17
25
|
export interface StashFile {
|
|
18
26
|
entries: StashEntry[];
|
|
@@ -21,7 +29,8 @@ export declare function stashFilePath(dirPath: string): string;
|
|
|
21
29
|
export declare function loadStashFile(dirPath: string): StashFile | null;
|
|
22
30
|
export declare function writeStashFile(dirPath: string, stash: StashFile): void;
|
|
23
31
|
export declare function validateStashEntry(entry: unknown): StashEntry | null;
|
|
24
|
-
export declare function generateMetadata(dirPath: string, assetType: AgentikitAssetType, files: string[]): StashFile;
|
|
32
|
+
export declare function generateMetadata(dirPath: string, assetType: AgentikitAssetType, files: string[], typeRoot?: string): StashFile;
|
|
33
|
+
export declare function generateIntents(description: string, tags: string[], name: string): string[];
|
|
25
34
|
export declare function extractDescriptionFromComments(filePath: string): string | null;
|
|
26
35
|
export declare function extractFrontmatterDescription(filePath: string): string | null;
|
|
27
36
|
export declare function extractPackageMetadata(dirPath: string): {
|
package/dist/src/metadata.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { isAssetType } from "./common";
|
|
4
|
+
import { SCRIPT_EXTENSIONS, isRelevantAssetFile, deriveCanonicalAssetName } from "./asset-spec";
|
|
5
|
+
import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
|
|
6
|
+
import { parseMarkdownToc } from "./markdown";
|
|
3
7
|
// ── Load / Write ────────────────────────────────────────────────────────────
|
|
4
8
|
const STASH_FILENAME = ".stash.json";
|
|
5
9
|
export function stashFilePath(dirPath) {
|
|
@@ -35,7 +39,7 @@ export function validateStashEntry(entry) {
|
|
|
35
39
|
const e = entry;
|
|
36
40
|
if (typeof e.name !== "string" || !e.name)
|
|
37
41
|
return null;
|
|
38
|
-
if (typeof e.type !== "string" || !
|
|
42
|
+
if (typeof e.type !== "string" || !isAssetType(e.type))
|
|
39
43
|
return null;
|
|
40
44
|
const result = {
|
|
41
45
|
name: e.name,
|
|
@@ -47,6 +51,11 @@ export function validateStashEntry(entry) {
|
|
|
47
51
|
result.tags = e.tags.filter((t) => typeof t === "string");
|
|
48
52
|
if (Array.isArray(e.examples))
|
|
49
53
|
result.examples = e.examples.filter((x) => typeof x === "string");
|
|
54
|
+
if (Array.isArray(e.intents)) {
|
|
55
|
+
const filtered = e.intents.filter((s) => typeof s === "string" && s.trim().length > 0);
|
|
56
|
+
if (filtered.length > 0)
|
|
57
|
+
result.intents = filtered;
|
|
58
|
+
}
|
|
50
59
|
if (typeof e.intent === "object" && e.intent !== null) {
|
|
51
60
|
const intent = e.intent;
|
|
52
61
|
result.intent = {};
|
|
@@ -61,62 +70,192 @@ export function validateStashEntry(entry) {
|
|
|
61
70
|
result.entry = e.entry;
|
|
62
71
|
if (e.generated === true)
|
|
63
72
|
result.generated = true;
|
|
73
|
+
if (e.quality === "generated" || e.quality === "curated")
|
|
74
|
+
result.quality = e.quality;
|
|
75
|
+
if (typeof e.confidence === "number" && Number.isFinite(e.confidence))
|
|
76
|
+
result.confidence = Math.max(0, Math.min(1, e.confidence));
|
|
77
|
+
if (typeof e.source === "string" && ["package", "frontmatter", "comments", "filename", "manual", "llm"].includes(e.source)) {
|
|
78
|
+
result.source = e.source;
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(e.aliases)) {
|
|
81
|
+
const filtered = e.aliases.filter((a) => typeof a === "string" && a.trim().length > 0);
|
|
82
|
+
if (filtered.length > 0)
|
|
83
|
+
result.aliases = normalizeTerms(filtered);
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(e.toc)) {
|
|
86
|
+
const validated = e.toc.filter((h) => {
|
|
87
|
+
if (typeof h !== "object" || h === null)
|
|
88
|
+
return false;
|
|
89
|
+
const rec = h;
|
|
90
|
+
return typeof rec.level === "number"
|
|
91
|
+
&& typeof rec.text === "string"
|
|
92
|
+
&& typeof rec.line === "number";
|
|
93
|
+
});
|
|
94
|
+
if (validated.length > 0)
|
|
95
|
+
result.toc = validated;
|
|
96
|
+
}
|
|
97
|
+
const usage = normalizeNonEmptyStringList(e.usage);
|
|
98
|
+
if (usage)
|
|
99
|
+
result.usage = usage;
|
|
64
100
|
return result;
|
|
65
101
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
102
|
+
function normalizeNonEmptyStringList(value) {
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
const trimmed = value.trim();
|
|
105
|
+
return trimmed ? [trimmed] : undefined;
|
|
106
|
+
}
|
|
107
|
+
if (!Array.isArray(value))
|
|
108
|
+
return undefined;
|
|
109
|
+
const filtered = value
|
|
110
|
+
.filter((item) => typeof item === "string")
|
|
111
|
+
.map((item) => item.trim())
|
|
112
|
+
.filter((item) => item.length > 0);
|
|
113
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
68
114
|
}
|
|
69
115
|
// ── Metadata Generation ─────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
export function generateMetadata(dirPath, assetType, files) {
|
|
116
|
+
export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath) {
|
|
72
117
|
const entries = [];
|
|
118
|
+
const pkgMeta = extractPackageMetadata(dirPath);
|
|
73
119
|
for (const file of files) {
|
|
74
120
|
const ext = path.extname(file).toLowerCase();
|
|
75
121
|
const baseName = path.basename(file, ext);
|
|
122
|
+
const fileName = path.basename(file);
|
|
76
123
|
// Skip non-relevant files
|
|
77
|
-
if (assetType
|
|
78
|
-
continue;
|
|
79
|
-
if ((assetType === "command" || assetType === "agent") && ext !== ".md")
|
|
80
|
-
continue;
|
|
81
|
-
if (assetType === "skill" && path.basename(file) !== "SKILL.md")
|
|
124
|
+
if (!isRelevantAssetFile(assetType, fileName))
|
|
82
125
|
continue;
|
|
126
|
+
const canonicalName = assetType === "skill"
|
|
127
|
+
? deriveCanonicalAssetName(assetType, typeRoot, file) ?? baseName
|
|
128
|
+
: baseName;
|
|
83
129
|
const entry = {
|
|
84
|
-
name:
|
|
130
|
+
name: canonicalName,
|
|
85
131
|
type: assetType,
|
|
86
132
|
generated: true,
|
|
133
|
+
quality: "generated",
|
|
134
|
+
confidence: 0.55,
|
|
135
|
+
source: "filename",
|
|
87
136
|
};
|
|
88
|
-
// Priority
|
|
89
|
-
const pkgMeta = extractPackageMetadata(dirPath);
|
|
137
|
+
// Priority 1: package.json metadata
|
|
90
138
|
if (pkgMeta) {
|
|
91
|
-
if (pkgMeta.description && !entry.description)
|
|
139
|
+
if (pkgMeta.description && !entry.description) {
|
|
92
140
|
entry.description = pkgMeta.description;
|
|
141
|
+
entry.source = "package";
|
|
142
|
+
entry.confidence = 0.8;
|
|
143
|
+
}
|
|
93
144
|
if (pkgMeta.keywords && pkgMeta.keywords.length > 0)
|
|
94
|
-
entry.tags = pkgMeta.keywords;
|
|
145
|
+
entry.tags = normalizeTerms(pkgMeta.keywords);
|
|
95
146
|
}
|
|
96
|
-
// Priority 2: Frontmatter (for .md files)
|
|
147
|
+
// Priority 2: Frontmatter (for .md files — overrides package.json description)
|
|
97
148
|
if (ext === ".md") {
|
|
98
149
|
const fm = extractFrontmatterDescription(file);
|
|
99
|
-
if (fm)
|
|
150
|
+
if (fm) {
|
|
100
151
|
entry.description = fm;
|
|
152
|
+
entry.source = "frontmatter";
|
|
153
|
+
entry.confidence = 0.9;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Knowledge entries: generate TOC from headings
|
|
157
|
+
if (assetType === "knowledge") {
|
|
158
|
+
try {
|
|
159
|
+
const mdContent = fs.readFileSync(file, "utf8");
|
|
160
|
+
const toc = parseMarkdownToc(mdContent);
|
|
161
|
+
if (toc.headings.length > 0)
|
|
162
|
+
entry.toc = toc.headings;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Non-fatal: skip TOC if file can't be read
|
|
166
|
+
}
|
|
101
167
|
}
|
|
102
|
-
// Priority
|
|
168
|
+
// Priority 3: Code comments (for script files)
|
|
103
169
|
if (SCRIPT_EXTENSIONS.has(ext) && ext !== ".md") {
|
|
104
170
|
const commentDesc = extractDescriptionFromComments(file);
|
|
105
|
-
if (commentDesc && !entry.description)
|
|
171
|
+
if (commentDesc && !entry.description) {
|
|
106
172
|
entry.description = commentDesc;
|
|
173
|
+
entry.source = "comments";
|
|
174
|
+
entry.confidence = 0.7;
|
|
175
|
+
}
|
|
107
176
|
}
|
|
108
|
-
// Priority
|
|
177
|
+
// Priority 4: Filename heuristics (fallback)
|
|
109
178
|
if (!entry.description) {
|
|
110
179
|
entry.description = fileNameToDescription(baseName);
|
|
180
|
+
entry.source = "filename";
|
|
181
|
+
entry.confidence = Math.min(entry.confidence ?? 0.55, 0.55);
|
|
111
182
|
}
|
|
112
183
|
if (!entry.tags || entry.tags.length === 0) {
|
|
113
184
|
entry.tags = extractTagsFromPath(file, dirPath);
|
|
114
185
|
}
|
|
186
|
+
entry.tags = normalizeTerms(entry.tags ?? []);
|
|
187
|
+
entry.aliases = buildAliases(canonicalName, entry.tags);
|
|
188
|
+
// Intents are only generated when LLM is configured (via enhanceStashWithLlm)
|
|
189
|
+
// Heuristic intents are too noisy to be useful for search quality
|
|
115
190
|
entry.entry = path.basename(file);
|
|
116
191
|
entries.push(entry);
|
|
117
192
|
}
|
|
118
193
|
return { entries };
|
|
119
194
|
}
|
|
195
|
+
function normalizeTerms(values) {
|
|
196
|
+
const normalized = new Set();
|
|
197
|
+
for (const value of values) {
|
|
198
|
+
const cleaned = value.toLowerCase().replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
|
|
199
|
+
if (!cleaned)
|
|
200
|
+
continue;
|
|
201
|
+
normalized.add(cleaned);
|
|
202
|
+
if (cleaned.endsWith("s") && cleaned.length > 3) {
|
|
203
|
+
normalized.add(cleaned.slice(0, -1));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return Array.from(normalized);
|
|
207
|
+
}
|
|
208
|
+
function buildAliases(name, tags) {
|
|
209
|
+
const aliases = new Set();
|
|
210
|
+
const spaced = name.replace(/[-_]+/g, " ").trim().toLowerCase();
|
|
211
|
+
if (spaced && spaced !== name.toLowerCase())
|
|
212
|
+
aliases.add(spaced);
|
|
213
|
+
if (tags.length > 1)
|
|
214
|
+
aliases.add(tags.join(" "));
|
|
215
|
+
return Array.from(aliases);
|
|
216
|
+
}
|
|
217
|
+
// ── Intent Generation ────────────────────────────────────────────────────────
|
|
218
|
+
export function generateIntents(description, tags, name) {
|
|
219
|
+
const intents = new Set();
|
|
220
|
+
// Split name on separators to extract tokens and potential verb
|
|
221
|
+
const nameTokens = name
|
|
222
|
+
.replace(/[-_]+/g, " ")
|
|
223
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
224
|
+
.toLowerCase()
|
|
225
|
+
.trim()
|
|
226
|
+
.split(/\s+/)
|
|
227
|
+
.filter((t) => t.length > 1);
|
|
228
|
+
// Intent from name as phrase (e.g. "summarize diff")
|
|
229
|
+
const namePhrase = nameTokens.join(" ");
|
|
230
|
+
if (namePhrase.length > 2)
|
|
231
|
+
intents.add(namePhrase);
|
|
232
|
+
// Intent from description (lowercased)
|
|
233
|
+
const desc = description.toLowerCase().trim();
|
|
234
|
+
if (desc.length > 2)
|
|
235
|
+
intents.add(desc);
|
|
236
|
+
// Combine first name token (potential verb) with tags
|
|
237
|
+
// e.g. name "summarize-diff", tags ["git"] → "summarize git diff"
|
|
238
|
+
if (nameTokens.length >= 1 && tags.length > 0) {
|
|
239
|
+
const verb = nameTokens[0];
|
|
240
|
+
const rest = nameTokens.slice(1).join(" ");
|
|
241
|
+
for (const tag of tags) {
|
|
242
|
+
const tagLower = tag.toLowerCase();
|
|
243
|
+
// verb + tag + rest (e.g. "summarize git diff")
|
|
244
|
+
const parts = [verb, tagLower, rest].filter((p) => p.length > 0);
|
|
245
|
+
const phrase = parts.join(" ");
|
|
246
|
+
if (phrase !== namePhrase && phrase.length > 2)
|
|
247
|
+
intents.add(phrase);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Join tag pairs (e.g. ["git", "diff"] → "git diff")
|
|
251
|
+
if (tags.length >= 2) {
|
|
252
|
+
const tagPhrase = tags.map((t) => t.toLowerCase()).join(" ");
|
|
253
|
+
if (tagPhrase.length > 2)
|
|
254
|
+
intents.add(tagPhrase);
|
|
255
|
+
}
|
|
256
|
+
// Cap at 8 intents
|
|
257
|
+
return Array.from(intents).slice(0, 8);
|
|
258
|
+
}
|
|
120
259
|
export function extractDescriptionFromComments(filePath) {
|
|
121
260
|
let content;
|
|
122
261
|
try {
|
|
@@ -170,15 +309,8 @@ export function extractFrontmatterDescription(filePath) {
|
|
|
170
309
|
catch {
|
|
171
310
|
return null;
|
|
172
311
|
}
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
return null;
|
|
176
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
177
|
-
const m = line.match(/^description:\s*"?(.+?)"?\s*$/);
|
|
178
|
-
if (m)
|
|
179
|
-
return m[1];
|
|
180
|
-
}
|
|
181
|
-
return null;
|
|
312
|
+
const parsed = parseFrontmatter(content);
|
|
313
|
+
return toStringOrUndefined(parsed.data.description) ?? null;
|
|
182
314
|
}
|
|
183
315
|
export function extractPackageMetadata(dirPath) {
|
|
184
316
|
const pkgPath = path.join(dirPath, "package.json");
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AgentikitConfig } from "./config";
|
|
2
|
+
import type { RegistryInstallResult, RegistryInstalledEntry } from "./registry-types";
|
|
3
|
+
export interface InstallRegistryRefOptions {
|
|
4
|
+
cacheRootDir?: string;
|
|
5
|
+
now?: Date;
|
|
6
|
+
}
|
|
7
|
+
export declare function installRegistryRef(ref: string, options?: InstallRegistryRefOptions): Promise<RegistryInstallResult>;
|
|
8
|
+
export declare function upsertInstalledRegistryEntry(entry: RegistryInstalledEntry, stashDir?: string): AgentikitConfig;
|
|
9
|
+
export declare function removeInstalledRegistryEntry(id: string, stashDir?: string): AgentikitConfig;
|
|
10
|
+
export declare function getRegistryCacheRootDir(): string;
|
|
11
|
+
export declare function detectStashRoot(extractedDir: string): string;
|