@side-quest/kit 0.1.0 → 0.2.0
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/CHANGELOG.md +20 -0
- package/README.md +54 -352
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +156 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -2509
- package/dist/index.js.map +1 -0
- package/dist/lib/ast/index.d.ts +11 -0
- package/dist/lib/ast/index.d.ts.map +1 -0
- package/dist/lib/ast/index.js +15 -0
- package/dist/lib/ast/index.js.map +1 -0
- package/dist/lib/ast/languages.d.ts +55 -0
- package/dist/lib/ast/languages.d.ts.map +1 -0
- package/dist/lib/ast/languages.js +146 -0
- package/dist/lib/ast/languages.js.map +1 -0
- package/dist/lib/ast/pattern.d.ts +84 -0
- package/dist/lib/ast/pattern.d.ts.map +1 -0
- package/dist/lib/ast/pattern.js +268 -0
- package/dist/lib/ast/pattern.js.map +1 -0
- package/dist/lib/ast/searcher.d.ts +89 -0
- package/dist/lib/ast/searcher.d.ts.map +1 -0
- package/dist/lib/ast/searcher.js +316 -0
- package/dist/lib/ast/searcher.js.map +1 -0
- package/dist/lib/ast/types.d.ts +93 -0
- package/dist/lib/ast/types.d.ts.map +1 -0
- package/dist/lib/ast/types.js +23 -0
- package/dist/lib/ast/types.js.map +1 -0
- package/dist/lib/commands/callers.d.ts +20 -0
- package/dist/lib/commands/callers.d.ts.map +1 -0
- package/dist/lib/commands/callers.js +162 -0
- package/dist/lib/commands/callers.js.map +1 -0
- package/dist/lib/commands/find.d.ts +15 -0
- package/dist/lib/commands/find.d.ts.map +1 -0
- package/dist/lib/commands/find.js +113 -0
- package/dist/lib/commands/find.js.map +1 -0
- package/dist/lib/commands/overview.d.ts +6 -0
- package/dist/lib/commands/overview.d.ts.map +1 -0
- package/dist/lib/commands/overview.js +52 -0
- package/dist/lib/commands/overview.js.map +1 -0
- package/dist/lib/commands/prime.d.ts +16 -0
- package/dist/lib/commands/prime.d.ts.map +1 -0
- package/dist/lib/commands/prime.js +168 -0
- package/dist/lib/commands/prime.js.map +1 -0
- package/dist/lib/commands/search.d.ts +20 -0
- package/dist/lib/commands/search.d.ts.map +1 -0
- package/dist/lib/commands/search.js +111 -0
- package/dist/lib/commands/search.js.map +1 -0
- package/dist/lib/errors.d.ts +80 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +189 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/formatters/output.d.ts +5 -0
- package/dist/lib/formatters/output.d.ts.map +1 -0
- package/dist/lib/formatters/output.js +5 -0
- package/dist/lib/formatters/output.js.map +1 -0
- package/dist/lib/formatters.d.ts +29 -0
- package/dist/lib/formatters.d.ts.map +1 -0
- package/dist/lib/formatters.js +141 -0
- package/dist/lib/formatters.js.map +1 -0
- package/dist/lib/index-tools.d.ts +108 -0
- package/dist/lib/index-tools.d.ts.map +1 -0
- package/dist/lib/index-tools.js +311 -0
- package/dist/lib/index-tools.js.map +1 -0
- package/dist/lib/index.d.ts +21 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +42 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/kit-wrapper.d.ts +70 -0
- package/dist/lib/kit-wrapper.d.ts.map +1 -0
- package/dist/lib/kit-wrapper.js +462 -0
- package/dist/lib/kit-wrapper.js.map +1 -0
- package/dist/lib/logger.d.ts +28 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +39 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/types.d.ts +179 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +48 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils/args.d.ts +40 -0
- package/dist/lib/utils/args.d.ts.map +1 -0
- package/dist/lib/utils/args.js +58 -0
- package/dist/lib/utils/args.js.map +1 -0
- package/dist/lib/utils/git.d.ts +23 -0
- package/dist/lib/utils/git.d.ts.map +1 -0
- package/dist/lib/utils/git.js +50 -0
- package/dist/lib/utils/git.js.map +1 -0
- package/dist/lib/utils/index-parser.d.ts +155 -0
- package/dist/lib/utils/index-parser.d.ts.map +1 -0
- package/dist/lib/utils/index-parser.js +252 -0
- package/dist/lib/utils/index-parser.js.map +1 -0
- package/dist/lib/validators.d.ts +138 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +302 -0
- package/dist/lib/validators.js.map +1 -0
- package/dist/mcp/index.d.ts +19 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +769 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +5 -2
- package/src/cli.ts +170 -0
- package/src/lib/ast/index.ts +32 -0
- package/src/lib/ast/languages.ts +172 -0
- package/src/lib/ast/pattern.ts +299 -0
- package/src/lib/ast/searcher.ts +381 -0
- package/src/lib/ast/types.ts +99 -0
- package/src/lib/commands/callers.ts +226 -0
- package/src/lib/commands/find.ts +159 -0
- package/src/lib/commands/overview.ts +73 -0
- package/src/lib/commands/prime.ts +271 -0
- package/src/lib/commands/search.ts +146 -0
- package/src/lib/errors.ts +221 -0
- package/src/lib/formatters/output.ts +9 -0
- package/src/lib/formatters.ts +189 -0
- package/src/lib/index-tools.ts +471 -0
- package/src/lib/index.ts +122 -0
- package/src/lib/kit-wrapper.ts +675 -0
- package/src/lib/logger.ts +57 -0
- package/src/lib/types.ts +228 -0
- package/src/lib/utils/args.ts +72 -0
- package/src/lib/utils/git.ts +65 -0
- package/src/lib/utils/index-parser.ts +350 -0
- package/src/lib/validators.ts +437 -0
- package/src/mcp/index.ts +144 -79
package/dist/index.js
CHANGED
|
@@ -1,2509 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
createPluginLogger
|
|
10
|
-
} from "@side-quest/core/logging";
|
|
11
|
-
var {
|
|
12
|
-
initLogger,
|
|
13
|
-
rootLogger: logger,
|
|
14
|
-
getSubsystemLogger,
|
|
15
|
-
logDir,
|
|
16
|
-
logFile
|
|
17
|
-
} = createPluginLogger({
|
|
18
|
-
name: "kit",
|
|
19
|
-
subsystems: [
|
|
20
|
-
"grep",
|
|
21
|
-
"semantic",
|
|
22
|
-
"symbols",
|
|
23
|
-
"fileTree",
|
|
24
|
-
"fileContent",
|
|
25
|
-
"usages",
|
|
26
|
-
"ast",
|
|
27
|
-
"commit",
|
|
28
|
-
"summarize"
|
|
29
|
-
]
|
|
30
|
-
});
|
|
31
|
-
var grepLogger = getSubsystemLogger("grep");
|
|
32
|
-
var semanticLogger = getSubsystemLogger("semantic");
|
|
33
|
-
var symbolsLogger = getSubsystemLogger("symbols");
|
|
34
|
-
var fileTreeLogger = getSubsystemLogger("fileTree");
|
|
35
|
-
var fileContentLogger = getSubsystemLogger("fileContent");
|
|
36
|
-
var usagesLogger = getSubsystemLogger("usages");
|
|
37
|
-
var astLogger = getSubsystemLogger("ast");
|
|
38
|
-
var commitLogger = getSubsystemLogger("commit");
|
|
39
|
-
var summarizeLogger = getSubsystemLogger("summarize");
|
|
40
|
-
function getKitLogger() {
|
|
41
|
-
return logger;
|
|
42
|
-
}
|
|
43
|
-
function getGrepLogger() {
|
|
44
|
-
return grepLogger;
|
|
45
|
-
}
|
|
46
|
-
function getSemanticLogger() {
|
|
47
|
-
return semanticLogger;
|
|
48
|
-
}
|
|
49
|
-
function getSymbolsLogger() {
|
|
50
|
-
return symbolsLogger;
|
|
51
|
-
}
|
|
52
|
-
function getFileTreeLogger() {
|
|
53
|
-
return fileTreeLogger;
|
|
54
|
-
}
|
|
55
|
-
function getFileContentLogger() {
|
|
56
|
-
return fileContentLogger;
|
|
57
|
-
}
|
|
58
|
-
function getUsagesLogger() {
|
|
59
|
-
return usagesLogger;
|
|
60
|
-
}
|
|
61
|
-
function getAstLogger() {
|
|
62
|
-
return astLogger;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// src/lib/ast/languages.ts
|
|
66
|
-
var require2 = createRequire(import.meta.url);
|
|
67
|
-
var LANGUAGES = {
|
|
68
|
-
".ts": "typescript",
|
|
69
|
-
".tsx": "tsx",
|
|
70
|
-
".mts": "typescript",
|
|
71
|
-
".cts": "typescript",
|
|
72
|
-
".js": "javascript",
|
|
73
|
-
".jsx": "javascript",
|
|
74
|
-
".mjs": "javascript",
|
|
75
|
-
".cjs": "javascript",
|
|
76
|
-
".py": "python"
|
|
77
|
-
};
|
|
78
|
-
var SUPPORTED_LANGUAGES = [
|
|
79
|
-
"typescript",
|
|
80
|
-
"tsx",
|
|
81
|
-
"javascript",
|
|
82
|
-
"python"
|
|
83
|
-
];
|
|
84
|
-
var languagePool = new ResourcePool;
|
|
85
|
-
var parserPool = new ResourcePool;
|
|
86
|
-
var initialized = false;
|
|
87
|
-
async function initParser() {
|
|
88
|
-
if (initialized)
|
|
89
|
-
return;
|
|
90
|
-
const logger2 = getAstLogger();
|
|
91
|
-
logger2.debug("Initializing tree-sitter parser");
|
|
92
|
-
try {
|
|
93
|
-
await Parser.init();
|
|
94
|
-
initialized = true;
|
|
95
|
-
logger2.debug("Tree-sitter parser initialized successfully");
|
|
96
|
-
} catch (error) {
|
|
97
|
-
logger2.error("Failed to initialize tree-sitter parser", {
|
|
98
|
-
error: error instanceof Error ? error.message : String(error)
|
|
99
|
-
});
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async function getParser(language) {
|
|
104
|
-
await initParser();
|
|
105
|
-
const logger2 = getAstLogger();
|
|
106
|
-
return parserPool.getOrCreate(language, async (lang) => {
|
|
107
|
-
logger2.debug("Creating new parser for language", { language: lang });
|
|
108
|
-
const parser = new Parser;
|
|
109
|
-
const grammar = await loadLanguage(lang);
|
|
110
|
-
parser.setLanguage(grammar);
|
|
111
|
-
return parser;
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
async function loadLanguage(language) {
|
|
115
|
-
const logger2 = getAstLogger();
|
|
116
|
-
return languagePool.getOrCreate(language, async (lang) => {
|
|
117
|
-
logger2.debug("Loading language grammar from WASM", { language: lang });
|
|
118
|
-
try {
|
|
119
|
-
const wasmPath = require2.resolve(`tree-sitter-wasms/out/tree-sitter-${lang}.wasm`);
|
|
120
|
-
const grammar = await Language.load(wasmPath);
|
|
121
|
-
logger2.debug("Language grammar loaded successfully", {
|
|
122
|
-
language: lang,
|
|
123
|
-
wasmPath
|
|
124
|
-
});
|
|
125
|
-
return grammar;
|
|
126
|
-
} catch (error) {
|
|
127
|
-
logger2.error("Failed to load language grammar", {
|
|
128
|
-
language: lang,
|
|
129
|
-
error: error instanceof Error ? error.message : String(error)
|
|
130
|
-
});
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
function detectLanguage(filePath) {
|
|
136
|
-
const lastDot = filePath.lastIndexOf(".");
|
|
137
|
-
if (lastDot === -1)
|
|
138
|
-
return null;
|
|
139
|
-
const ext = filePath.substring(lastDot).toLowerCase();
|
|
140
|
-
const language = LANGUAGES[ext];
|
|
141
|
-
if (language && SUPPORTED_LANGUAGES.includes(language)) {
|
|
142
|
-
return language;
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
function isSupported(filePath) {
|
|
147
|
-
return detectLanguage(filePath) !== null;
|
|
148
|
-
}
|
|
149
|
-
function getSupportedGlob() {
|
|
150
|
-
const extensions = Object.keys(LANGUAGES).map((ext) => ext.slice(1));
|
|
151
|
-
return `**/*.{${extensions.join(",")}}`;
|
|
152
|
-
}
|
|
153
|
-
// src/lib/ast/types.ts
|
|
154
|
-
var SearchMode;
|
|
155
|
-
((SearchMode2) => {
|
|
156
|
-
SearchMode2["SIMPLE"] = "simple";
|
|
157
|
-
SearchMode2["PATTERN"] = "pattern";
|
|
158
|
-
})(SearchMode ||= {});
|
|
159
|
-
var AST_SEARCH_TIMEOUT = 60000;
|
|
160
|
-
|
|
161
|
-
// src/lib/ast/pattern.ts
|
|
162
|
-
var FUNCTION_NODE_TYPES = [
|
|
163
|
-
"function_declaration",
|
|
164
|
-
"function_expression",
|
|
165
|
-
"arrow_function",
|
|
166
|
-
"method_definition",
|
|
167
|
-
"generator_function_declaration",
|
|
168
|
-
"function_definition"
|
|
169
|
-
];
|
|
170
|
-
var CLASS_NODE_TYPES = [
|
|
171
|
-
"class_declaration",
|
|
172
|
-
"class_expression",
|
|
173
|
-
"class_definition"
|
|
174
|
-
];
|
|
175
|
-
var TRY_NODE_TYPES = [
|
|
176
|
-
"try_statement"
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
class ASTPattern {
|
|
180
|
-
pattern;
|
|
181
|
-
mode;
|
|
182
|
-
isAsync = false;
|
|
183
|
-
isDef = false;
|
|
184
|
-
isClass = false;
|
|
185
|
-
isTry = false;
|
|
186
|
-
isImport = false;
|
|
187
|
-
isExport = false;
|
|
188
|
-
criteria = {};
|
|
189
|
-
constructor(pattern, mode = "simple" /* SIMPLE */) {
|
|
190
|
-
this.pattern = pattern;
|
|
191
|
-
this.mode = mode;
|
|
192
|
-
this.compile();
|
|
193
|
-
}
|
|
194
|
-
compile() {
|
|
195
|
-
if (this.mode === "simple" /* SIMPLE */) {
|
|
196
|
-
this.compileSimple();
|
|
197
|
-
} else if (this.mode === "pattern" /* PATTERN */) {
|
|
198
|
-
this.compilePattern();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
compileSimple() {
|
|
202
|
-
const lower = this.pattern.toLowerCase();
|
|
203
|
-
this.isAsync = lower.includes("async");
|
|
204
|
-
this.isDef = lower.includes("function") || lower.includes("def") || lower.includes("method");
|
|
205
|
-
this.isClass = lower.includes("class");
|
|
206
|
-
this.isTry = lower.includes("try") || lower.includes("catch");
|
|
207
|
-
this.isImport = lower.includes("import");
|
|
208
|
-
this.isExport = lower.includes("export");
|
|
209
|
-
if (!this.isAsync && !this.isDef && !this.isClass && !this.isTry && !this.isImport && !this.isExport) {
|
|
210
|
-
this.criteria = { textMatch: this.pattern };
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
compilePattern() {
|
|
214
|
-
try {
|
|
215
|
-
this.criteria = JSON.parse(this.pattern);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
const logger2 = getAstLogger();
|
|
218
|
-
logger2.debug("Pattern JSON parse failed, using text match fallback", {
|
|
219
|
-
pattern: this.pattern,
|
|
220
|
-
error: error instanceof Error ? error.message : String(error)
|
|
221
|
-
});
|
|
222
|
-
this.criteria = { textMatch: this.pattern };
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
matches(node, source) {
|
|
226
|
-
if (this.mode === "simple" /* SIMPLE */) {
|
|
227
|
-
return this.matchesSimple(node, source);
|
|
228
|
-
} else if (this.mode === "pattern" /* PATTERN */) {
|
|
229
|
-
return this.matchesPattern(node, source);
|
|
230
|
-
}
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
matchesSimple(node, source) {
|
|
234
|
-
const nodeType = node.type;
|
|
235
|
-
if (this.isDef || this.isClass || this.isTry || this.isImport || this.isExport) {
|
|
236
|
-
if (this.isDef && !this.isFunctionNode(nodeType))
|
|
237
|
-
return false;
|
|
238
|
-
if (this.isClass && !this.isClassNode(nodeType))
|
|
239
|
-
return false;
|
|
240
|
-
if (this.isTry && !TRY_NODE_TYPES.includes(nodeType))
|
|
241
|
-
return false;
|
|
242
|
-
if (this.isImport && !nodeType.includes("import"))
|
|
243
|
-
return false;
|
|
244
|
-
if (this.isExport && !nodeType.includes("export"))
|
|
245
|
-
return false;
|
|
246
|
-
if (this.isAsync && this.isDef) {
|
|
247
|
-
if (!this.hasAsyncModifier(node))
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
if (this.criteria.textMatch) {
|
|
253
|
-
const text = this.getNodeText(node, source);
|
|
254
|
-
return text.includes(this.criteria.textMatch);
|
|
255
|
-
}
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
matchesPattern(node, source) {
|
|
259
|
-
if (this.criteria.type && node.type !== this.criteria.type) {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
if (this.criteria.async !== undefined) {
|
|
263
|
-
if (this.hasAsyncModifier(node) !== this.criteria.async) {
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (this.criteria.name) {
|
|
268
|
-
const name = this.getNodeName(node, source);
|
|
269
|
-
if (name !== this.criteria.name) {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (this.criteria.textMatch) {
|
|
274
|
-
const text = this.getNodeText(node, source);
|
|
275
|
-
if (!text.includes(this.criteria.textMatch)) {
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (!this.criteria.type && !this.criteria.async && !this.criteria.name && !this.criteria.textMatch) {
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
isFunctionNode(type) {
|
|
285
|
-
return FUNCTION_NODE_TYPES.includes(type);
|
|
286
|
-
}
|
|
287
|
-
isClassNode(type) {
|
|
288
|
-
return CLASS_NODE_TYPES.includes(type);
|
|
289
|
-
}
|
|
290
|
-
hasAsyncModifier(node) {
|
|
291
|
-
for (const child of node.children) {
|
|
292
|
-
if (child && child.type === "async")
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
const firstPart = node.text?.slice(0, 10) ?? "";
|
|
296
|
-
return firstPart.includes("async");
|
|
297
|
-
}
|
|
298
|
-
getNodeName(node, source) {
|
|
299
|
-
for (const child of node.children) {
|
|
300
|
-
if (!child)
|
|
301
|
-
continue;
|
|
302
|
-
if (child.type === "identifier" || child.type === "type_identifier" || child.type === "property_identifier") {
|
|
303
|
-
return source.substring(child.startIndex, child.endIndex);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
const nameChild = node.childForFieldName("name");
|
|
307
|
-
if (nameChild) {
|
|
308
|
-
return source.substring(nameChild.startIndex, nameChild.endIndex);
|
|
309
|
-
}
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
getNodeText(node, source) {
|
|
313
|
-
return source.substring(node.startIndex, node.endIndex);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
// src/lib/ast/searcher.ts
|
|
317
|
-
import { readdir, readFile, stat } from "node:fs/promises";
|
|
318
|
-
import { join, relative } from "node:path";
|
|
319
|
-
import { processInParallelChunks } from "@side-quest/core/concurrency";
|
|
320
|
-
|
|
321
|
-
// src/lib/types.ts
|
|
322
|
-
var ResponseFormat;
|
|
323
|
-
((ResponseFormat2) => {
|
|
324
|
-
ResponseFormat2["MARKDOWN"] = "markdown";
|
|
325
|
-
ResponseFormat2["JSON"] = "json";
|
|
326
|
-
})(ResponseFormat ||= {});
|
|
327
|
-
function isError(result) {
|
|
328
|
-
return typeof result === "object" && result !== null && "error" in result;
|
|
329
|
-
}
|
|
330
|
-
var KIT_DEFAULT_PATH_ENV = "KIT_DEFAULT_PATH";
|
|
331
|
-
function getDefaultKitPath() {
|
|
332
|
-
return process.env[KIT_DEFAULT_PATH_ENV] || process.cwd();
|
|
333
|
-
}
|
|
334
|
-
var GREP_TIMEOUT = 30000;
|
|
335
|
-
var SEMANTIC_TIMEOUT = 60000;
|
|
336
|
-
var SYMBOLS_TIMEOUT = 45000;
|
|
337
|
-
var FILE_TREE_TIMEOUT = 30000;
|
|
338
|
-
var FILE_CONTENT_TIMEOUT = 15000;
|
|
339
|
-
var USAGES_TIMEOUT = 45000;
|
|
340
|
-
var COMMIT_TIMEOUT = 60000;
|
|
341
|
-
var SUMMARIZE_TIMEOUT = 60000;
|
|
342
|
-
var DEFAULT_MAX_RESULTS = 100;
|
|
343
|
-
var DEFAULT_TOP_K = 5;
|
|
344
|
-
|
|
345
|
-
// src/lib/ast/searcher.ts
|
|
346
|
-
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
347
|
-
var MAX_TEXT_LENGTH = 500;
|
|
348
|
-
var PARALLEL_CHUNK_SIZE = 10;
|
|
349
|
-
var SKIP_DIRS = new Set([
|
|
350
|
-
"node_modules",
|
|
351
|
-
".git",
|
|
352
|
-
".svn",
|
|
353
|
-
".hg",
|
|
354
|
-
"__pycache__",
|
|
355
|
-
".pytest_cache",
|
|
356
|
-
".mypy_cache",
|
|
357
|
-
"dist",
|
|
358
|
-
"build",
|
|
359
|
-
".next",
|
|
360
|
-
".nuxt",
|
|
361
|
-
"coverage",
|
|
362
|
-
".coverage",
|
|
363
|
-
"venv",
|
|
364
|
-
".venv",
|
|
365
|
-
"env",
|
|
366
|
-
".env"
|
|
367
|
-
]);
|
|
368
|
-
|
|
369
|
-
class ASTSearcher {
|
|
370
|
-
repoPath;
|
|
371
|
-
constructor(repoPath) {
|
|
372
|
-
this.repoPath = repoPath ?? getDefaultKitPath();
|
|
373
|
-
}
|
|
374
|
-
async searchPattern(options) {
|
|
375
|
-
const {
|
|
376
|
-
pattern,
|
|
377
|
-
mode = "simple" /* SIMPLE */,
|
|
378
|
-
filePattern,
|
|
379
|
-
maxResults = 100
|
|
380
|
-
} = options;
|
|
381
|
-
const astPattern = new ASTPattern(pattern, mode);
|
|
382
|
-
const files = await this.getMatchingFiles(filePattern);
|
|
383
|
-
const logger2 = getAstLogger();
|
|
384
|
-
logger2.debug("File search results", {
|
|
385
|
-
fileCount: files.length,
|
|
386
|
-
repoPath: this.repoPath,
|
|
387
|
-
filePattern,
|
|
388
|
-
firstFiles: files.slice(0, 3)
|
|
389
|
-
});
|
|
390
|
-
const matches = await processInParallelChunks({
|
|
391
|
-
items: files,
|
|
392
|
-
chunkSize: PARALLEL_CHUNK_SIZE,
|
|
393
|
-
maxResults,
|
|
394
|
-
processor: (filePath) => this.searchFile(filePath, astPattern),
|
|
395
|
-
onError: (filePath, error) => {
|
|
396
|
-
logger2.error("Error parsing file", {
|
|
397
|
-
filePath,
|
|
398
|
-
error: error.message
|
|
399
|
-
});
|
|
400
|
-
return [];
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
return {
|
|
404
|
-
count: matches.length,
|
|
405
|
-
matches,
|
|
406
|
-
pattern,
|
|
407
|
-
mode,
|
|
408
|
-
path: this.repoPath
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
async getMatchingFiles(filePattern) {
|
|
412
|
-
const files = [];
|
|
413
|
-
await this.walkDirectory(this.repoPath, files, filePattern);
|
|
414
|
-
return files;
|
|
415
|
-
}
|
|
416
|
-
async walkDirectory(dir, files, filePattern) {
|
|
417
|
-
const logger2 = getAstLogger();
|
|
418
|
-
let entries;
|
|
419
|
-
try {
|
|
420
|
-
entries = await readdir(dir);
|
|
421
|
-
} catch (error) {
|
|
422
|
-
logger2.warn("Failed to read directory", {
|
|
423
|
-
dir,
|
|
424
|
-
error: error instanceof Error ? error.message : String(error)
|
|
425
|
-
});
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
await Promise.all(entries.map(async (entry) => {
|
|
429
|
-
if (SKIP_DIRS.has(entry) || entry.startsWith("."))
|
|
430
|
-
return;
|
|
431
|
-
const fullPath = join(dir, entry);
|
|
432
|
-
let stats;
|
|
433
|
-
try {
|
|
434
|
-
stats = await stat(fullPath);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
logger2.debug("Failed to stat entry", {
|
|
437
|
-
path: fullPath,
|
|
438
|
-
error: error instanceof Error ? error.message : String(error)
|
|
439
|
-
});
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
if (stats.isDirectory()) {
|
|
443
|
-
await this.walkDirectory(fullPath, files, filePattern);
|
|
444
|
-
} else if (stats.isFile()) {
|
|
445
|
-
if (stats.size > MAX_FILE_SIZE) {
|
|
446
|
-
logger2.warn("Skipping large file", {
|
|
447
|
-
path: fullPath,
|
|
448
|
-
size: stats.size,
|
|
449
|
-
maxSize: MAX_FILE_SIZE
|
|
450
|
-
});
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (!isSupported(fullPath))
|
|
454
|
-
return;
|
|
455
|
-
if (filePattern && !this.matchesFilePattern(fullPath, filePattern)) {
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
files.push(fullPath);
|
|
459
|
-
}
|
|
460
|
-
}));
|
|
461
|
-
}
|
|
462
|
-
matchesFilePattern(filePath, pattern) {
|
|
463
|
-
const relativePath = relative(this.repoPath, filePath);
|
|
464
|
-
const glob = new Bun.Glob(pattern);
|
|
465
|
-
return glob.match(relativePath);
|
|
466
|
-
}
|
|
467
|
-
async searchFile(filePath, pattern) {
|
|
468
|
-
const language = detectLanguage(filePath);
|
|
469
|
-
if (!language)
|
|
470
|
-
return [];
|
|
471
|
-
const parser = await getParser(language);
|
|
472
|
-
const source = await readFile(filePath, "utf8");
|
|
473
|
-
const tree = parser.parse(source);
|
|
474
|
-
if (!tree)
|
|
475
|
-
return [];
|
|
476
|
-
const matches = [];
|
|
477
|
-
this.searchNode(tree.rootNode, source, pattern, filePath, matches);
|
|
478
|
-
return matches;
|
|
479
|
-
}
|
|
480
|
-
searchNode(node, source, pattern, filePath, matches) {
|
|
481
|
-
if (pattern.matches(node, source)) {
|
|
482
|
-
const match = this.createMatch(node, source, filePath);
|
|
483
|
-
matches.push(match);
|
|
484
|
-
}
|
|
485
|
-
for (let i = 0;i < node.childCount; i++) {
|
|
486
|
-
const child = node.child(i);
|
|
487
|
-
if (child) {
|
|
488
|
-
this.searchNode(child, source, pattern, filePath, matches);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
createMatch(node, source, filePath) {
|
|
493
|
-
return {
|
|
494
|
-
file: relative(this.repoPath, filePath),
|
|
495
|
-
line: node.startPosition.row + 1,
|
|
496
|
-
column: node.startPosition.column,
|
|
497
|
-
nodeType: node.type,
|
|
498
|
-
text: this.truncateText(source.substring(node.startIndex, node.endIndex)),
|
|
499
|
-
context: this.getContext(node, source)
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
getContext(node, source) {
|
|
503
|
-
const context = { nodeType: node.type };
|
|
504
|
-
let parent = node.parent;
|
|
505
|
-
while (parent) {
|
|
506
|
-
if (this.isFunctionNode(parent.type)) {
|
|
507
|
-
context.parentFunction = this.getNodeName(parent, source);
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
if (this.isClassNode(parent.type)) {
|
|
511
|
-
context.parentClass = this.getNodeName(parent, source);
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
514
|
-
parent = parent.parent;
|
|
515
|
-
}
|
|
516
|
-
return context;
|
|
517
|
-
}
|
|
518
|
-
getNodeName(node, source) {
|
|
519
|
-
const nameChild = node.childForFieldName("name");
|
|
520
|
-
if (nameChild) {
|
|
521
|
-
return source.substring(nameChild.startIndex, nameChild.endIndex);
|
|
522
|
-
}
|
|
523
|
-
for (let i = 0;i < node.childCount; i++) {
|
|
524
|
-
const child = node.child(i);
|
|
525
|
-
if (child && (child.type === "identifier" || child.type === "type_identifier")) {
|
|
526
|
-
return source.substring(child.startIndex, child.endIndex);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
isFunctionNode(type) {
|
|
532
|
-
return [
|
|
533
|
-
"function_declaration",
|
|
534
|
-
"function_definition",
|
|
535
|
-
"function_expression",
|
|
536
|
-
"arrow_function",
|
|
537
|
-
"method_definition",
|
|
538
|
-
"generator_function_declaration"
|
|
539
|
-
].includes(type);
|
|
540
|
-
}
|
|
541
|
-
isClassNode(type) {
|
|
542
|
-
return [
|
|
543
|
-
"class_declaration",
|
|
544
|
-
"class_definition",
|
|
545
|
-
"class_expression"
|
|
546
|
-
].includes(type);
|
|
547
|
-
}
|
|
548
|
-
truncateText(text) {
|
|
549
|
-
if (text.length <= MAX_TEXT_LENGTH)
|
|
550
|
-
return text;
|
|
551
|
-
return `${text.slice(0, MAX_TEXT_LENGTH - 3)}...`;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
// src/lib/errors.ts
|
|
555
|
-
import {
|
|
556
|
-
detectErrorFromOutput as coreDetectErrorFromOutput,
|
|
557
|
-
isTimeoutOutput,
|
|
558
|
-
PluginError
|
|
559
|
-
} from "@side-quest/core/instrumentation";
|
|
560
|
-
var KitErrorType;
|
|
561
|
-
((KitErrorType2) => {
|
|
562
|
-
KitErrorType2["KitNotInstalled"] = "KitNotInstalled";
|
|
563
|
-
KitErrorType2["InvalidPath"] = "InvalidPath";
|
|
564
|
-
KitErrorType2["InvalidInput"] = "InvalidInput";
|
|
565
|
-
KitErrorType2["SemanticNotAvailable"] = "SemanticNotAvailable";
|
|
566
|
-
KitErrorType2["SemanticIndexNotBuilt"] = "SemanticIndexNotBuilt";
|
|
567
|
-
KitErrorType2["TooManyResults"] = "TooManyResults";
|
|
568
|
-
KitErrorType2["KitCommandFailed"] = "KitCommandFailed";
|
|
569
|
-
KitErrorType2["OutputParseError"] = "OutputParseError";
|
|
570
|
-
KitErrorType2["Timeout"] = "Timeout";
|
|
571
|
-
})(KitErrorType ||= {});
|
|
572
|
-
var ERROR_MESSAGES = {
|
|
573
|
-
["KitNotInstalled" /* KitNotInstalled */]: {
|
|
574
|
-
message: "Kit CLI is not installed or not found in PATH.",
|
|
575
|
-
hint: "Install Kit with: uv tool install cased-kit"
|
|
576
|
-
},
|
|
577
|
-
["InvalidPath" /* InvalidPath */]: {
|
|
578
|
-
message: "The specified path does not exist or is not accessible.",
|
|
579
|
-
hint: "Check the path exists and you have read permissions."
|
|
580
|
-
},
|
|
581
|
-
["InvalidInput" /* InvalidInput */]: {
|
|
582
|
-
message: "Invalid input provided.",
|
|
583
|
-
hint: "Check your search pattern or options for syntax errors."
|
|
584
|
-
},
|
|
585
|
-
["SemanticNotAvailable" /* SemanticNotAvailable */]: {
|
|
586
|
-
message: "Semantic search is not available.",
|
|
587
|
-
hint: `Install ML dependencies: pip install 'cased-kit[ml]' or uv tool install 'cased-kit[ml]'`
|
|
588
|
-
},
|
|
589
|
-
["SemanticIndexNotBuilt" /* SemanticIndexNotBuilt */]: {
|
|
590
|
-
message: "Vector index has not been built for this repository yet.",
|
|
591
|
-
hint: "Build it by running the CLI command provided in the error details. After building (one-time), semantic search will be fast and cached."
|
|
592
|
-
},
|
|
593
|
-
["TooManyResults" /* TooManyResults */]: {
|
|
594
|
-
message: "Query returned too many results.",
|
|
595
|
-
hint: "Try a more specific query or use --max-results to limit output."
|
|
596
|
-
},
|
|
597
|
-
["KitCommandFailed" /* KitCommandFailed */]: {
|
|
598
|
-
message: "Kit command failed to execute.",
|
|
599
|
-
hint: "Check the error details below for more information."
|
|
600
|
-
},
|
|
601
|
-
["OutputParseError" /* OutputParseError */]: {
|
|
602
|
-
message: "Failed to parse Kit output.",
|
|
603
|
-
hint: "This may be a bug. Check logs for details."
|
|
604
|
-
},
|
|
605
|
-
["Timeout" /* Timeout */]: {
|
|
606
|
-
message: "Operation timed out (this may take longer on large repositories).",
|
|
607
|
-
hint: "Try again—the vector index will be cached. If it times out again, clear .kit/vector_db and rebuild with build_index: true."
|
|
608
|
-
}
|
|
609
|
-
};
|
|
610
|
-
|
|
611
|
-
class KitError extends PluginError {
|
|
612
|
-
constructor(type, details, stderr) {
|
|
613
|
-
const errorInfo = ERROR_MESSAGES[type];
|
|
614
|
-
super("KitError", type, errorInfo.message, errorInfo.hint, details, stderr);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
var SEMANTIC_INSTALL_HINT = `
|
|
618
|
-
Semantic search is not available. Using text search instead.
|
|
619
|
-
|
|
620
|
-
To enable semantic search, install the ML dependencies:
|
|
621
|
-
pip install 'cased-kit[ml]'
|
|
622
|
-
|
|
623
|
-
Or if using uv:
|
|
624
|
-
uv tool install 'cased-kit[ml]'
|
|
625
|
-
`.trim();
|
|
626
|
-
function isSemanticUnavailableError(stderr) {
|
|
627
|
-
const patterns = [
|
|
628
|
-
"sentence-transformers",
|
|
629
|
-
"chromadb",
|
|
630
|
-
"semantic search",
|
|
631
|
-
"vector index",
|
|
632
|
-
"embedding"
|
|
633
|
-
];
|
|
634
|
-
const lowerStderr = stderr.toLowerCase();
|
|
635
|
-
return patterns.some((p) => lowerStderr.includes(p));
|
|
636
|
-
}
|
|
637
|
-
var isTimeoutError = isTimeoutOutput;
|
|
638
|
-
var KIT_ERROR_PATTERNS = [
|
|
639
|
-
{
|
|
640
|
-
type: "SemanticNotAvailable" /* SemanticNotAvailable */,
|
|
641
|
-
patterns: [
|
|
642
|
-
"sentence-transformers",
|
|
643
|
-
"chromadb",
|
|
644
|
-
"semantic search",
|
|
645
|
-
"vector index",
|
|
646
|
-
"embedding"
|
|
647
|
-
]
|
|
648
|
-
},
|
|
649
|
-
{
|
|
650
|
-
type: "InvalidPath" /* InvalidPath */,
|
|
651
|
-
patterns: ["no such file", "not found", "does not exist"]
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
type: "Timeout" /* Timeout */,
|
|
655
|
-
patterns: ["timeout", "timed out", "etimedout"]
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
type: "TooManyResults" /* TooManyResults */,
|
|
659
|
-
patterns: ["too many", "limit"]
|
|
660
|
-
}
|
|
661
|
-
];
|
|
662
|
-
function detectErrorType(stderr, _exitCode) {
|
|
663
|
-
return coreDetectErrorFromOutput(stderr, KIT_ERROR_PATTERNS, "KitCommandFailed" /* KitCommandFailed */);
|
|
664
|
-
}
|
|
665
|
-
function createErrorFromOutput(stderr, exitCode) {
|
|
666
|
-
const errorType = detectErrorType(stderr, exitCode);
|
|
667
|
-
return new KitError(errorType, undefined, stderr.trim());
|
|
668
|
-
}
|
|
669
|
-
// src/lib/formatters.ts
|
|
670
|
-
import { formatBytes } from "@side-quest/core/formatters";
|
|
671
|
-
import { capitalize, truncate } from "@side-quest/core/utils";
|
|
672
|
-
function formatGrepResults(result, format = "markdown" /* MARKDOWN */) {
|
|
673
|
-
if (isError(result)) {
|
|
674
|
-
return formatError(result, format);
|
|
675
|
-
}
|
|
676
|
-
if (format === "json" /* JSON */) {
|
|
677
|
-
return JSON.stringify(result, null, 2);
|
|
678
|
-
}
|
|
679
|
-
const lines = [];
|
|
680
|
-
lines.push(`## Grep Results`);
|
|
681
|
-
lines.push("");
|
|
682
|
-
lines.push(`Found **${result.count}** matches for \`${result.pattern}\``);
|
|
683
|
-
lines.push("");
|
|
684
|
-
if (result.matches.length === 0) {
|
|
685
|
-
lines.push("_No matches found._");
|
|
686
|
-
return lines.join(`
|
|
687
|
-
`);
|
|
688
|
-
}
|
|
689
|
-
const byFile = new Map;
|
|
690
|
-
for (const match of result.matches) {
|
|
691
|
-
const existing = byFile.get(match.file) ?? [];
|
|
692
|
-
existing.push(match);
|
|
693
|
-
byFile.set(match.file, existing);
|
|
694
|
-
}
|
|
695
|
-
for (const [file, matches] of byFile) {
|
|
696
|
-
lines.push(`### ${file}`);
|
|
697
|
-
lines.push("");
|
|
698
|
-
for (const match of matches) {
|
|
699
|
-
const lineNum = match.line ? `:${match.line}` : "";
|
|
700
|
-
lines.push(`- **${file}${lineNum}**: ${truncate(match.content, 100)}`);
|
|
701
|
-
}
|
|
702
|
-
lines.push("");
|
|
703
|
-
}
|
|
704
|
-
return lines.join(`
|
|
705
|
-
`);
|
|
706
|
-
}
|
|
707
|
-
function formatSemanticResults(result, format = "markdown" /* MARKDOWN */) {
|
|
708
|
-
if (isError(result)) {
|
|
709
|
-
return formatError(result, format);
|
|
710
|
-
}
|
|
711
|
-
if (format === "json" /* JSON */) {
|
|
712
|
-
return JSON.stringify(result, null, 2);
|
|
713
|
-
}
|
|
714
|
-
const lines = [];
|
|
715
|
-
lines.push(`## Semantic Search Results`);
|
|
716
|
-
lines.push("");
|
|
717
|
-
if (result.fallback && result.installHint) {
|
|
718
|
-
lines.push(`> **Note:** ${result.installHint.split(`
|
|
719
|
-
`)[0]}`);
|
|
720
|
-
lines.push(">");
|
|
721
|
-
lines.push("> Using text search fallback. Results may be less relevant than semantic search.");
|
|
722
|
-
lines.push("");
|
|
723
|
-
}
|
|
724
|
-
lines.push(`Found **${result.count}** matches for query: _"${result.query}"_`);
|
|
725
|
-
lines.push("");
|
|
726
|
-
if (result.matches.length === 0) {
|
|
727
|
-
lines.push("_No matches found._");
|
|
728
|
-
return lines.join(`
|
|
729
|
-
`);
|
|
730
|
-
}
|
|
731
|
-
result.matches.forEach((match, i) => {
|
|
732
|
-
const score = (match.score * 100).toFixed(1);
|
|
733
|
-
const lineInfo = match.startLine && match.endLine ? `:${match.startLine}-${match.endLine}` : match.startLine ? `:${match.startLine}` : "";
|
|
734
|
-
lines.push(`### ${i + 1}. ${match.file}${lineInfo} (${score}% relevance)`);
|
|
735
|
-
lines.push("");
|
|
736
|
-
lines.push("```");
|
|
737
|
-
lines.push(truncate(match.chunk, 500));
|
|
738
|
-
lines.push("```");
|
|
739
|
-
lines.push("");
|
|
740
|
-
});
|
|
741
|
-
return lines.join(`
|
|
742
|
-
`);
|
|
743
|
-
}
|
|
744
|
-
function formatSymbolsResults(result, format = "markdown" /* MARKDOWN */) {
|
|
745
|
-
if (isError(result)) {
|
|
746
|
-
return formatError(result, format);
|
|
747
|
-
}
|
|
748
|
-
if (format === "json" /* JSON */) {
|
|
749
|
-
return JSON.stringify(result, null, 2);
|
|
750
|
-
}
|
|
751
|
-
const lines = [];
|
|
752
|
-
lines.push(`## Code Symbols`);
|
|
753
|
-
lines.push("");
|
|
754
|
-
lines.push(`Found **${result.count}** symbols in \`${result.path}\``);
|
|
755
|
-
lines.push("");
|
|
756
|
-
if (result.symbols.length === 0) {
|
|
757
|
-
lines.push("_No symbols found._");
|
|
758
|
-
return lines.join(`
|
|
759
|
-
`);
|
|
760
|
-
}
|
|
761
|
-
const byType = new Map;
|
|
762
|
-
for (const symbol of result.symbols) {
|
|
763
|
-
const existing = byType.get(symbol.type) ?? [];
|
|
764
|
-
existing.push(symbol);
|
|
765
|
-
byType.set(symbol.type, existing);
|
|
766
|
-
}
|
|
767
|
-
const sortedTypes = [...byType.keys()].sort();
|
|
768
|
-
for (const type of sortedTypes) {
|
|
769
|
-
const symbols = byType.get(type);
|
|
770
|
-
const icon = getSymbolIcon(type);
|
|
771
|
-
lines.push(`### ${icon} ${capitalize(type)}s (${symbols.length})`);
|
|
772
|
-
lines.push("");
|
|
773
|
-
for (const symbol of symbols) {
|
|
774
|
-
const loc = symbol.endLine ? `${symbol.startLine}-${symbol.endLine}` : String(symbol.startLine);
|
|
775
|
-
lines.push(`- **${symbol.name}** - \`${symbol.file}:${loc}\``);
|
|
776
|
-
}
|
|
777
|
-
lines.push("");
|
|
778
|
-
}
|
|
779
|
-
return lines.join(`
|
|
780
|
-
`);
|
|
781
|
-
}
|
|
782
|
-
function formatError(error, format = "markdown" /* MARKDOWN */) {
|
|
783
|
-
if (format === "json" /* JSON */) {
|
|
784
|
-
return JSON.stringify(error, null, 2);
|
|
785
|
-
}
|
|
786
|
-
const lines = [];
|
|
787
|
-
lines.push(`## Error`);
|
|
788
|
-
lines.push("");
|
|
789
|
-
lines.push(`**${error.error}**`);
|
|
790
|
-
if (error.hint) {
|
|
791
|
-
lines.push("");
|
|
792
|
-
lines.push(`\uD83D\uDCA1 **Hint:** ${error.hint}`);
|
|
793
|
-
}
|
|
794
|
-
return lines.join(`
|
|
795
|
-
`);
|
|
796
|
-
}
|
|
797
|
-
function getSymbolIcon(type) {
|
|
798
|
-
const icons = {
|
|
799
|
-
function: "\uD83D\uDCE6",
|
|
800
|
-
class: "\uD83D\uDCDA",
|
|
801
|
-
method: "\uD83D\uDD27",
|
|
802
|
-
property: "\uD83C\uDFF7️",
|
|
803
|
-
variable: "\uD83D\uDCCC",
|
|
804
|
-
constant: "\uD83D\uDD12",
|
|
805
|
-
type: "\uD83D\uDCDD",
|
|
806
|
-
interface: "\uD83D\uDCCB",
|
|
807
|
-
enum: "\uD83D\uDCCA",
|
|
808
|
-
module: "\uD83D\uDCC1"
|
|
809
|
-
};
|
|
810
|
-
return icons[type.toLowerCase()] ?? "•";
|
|
811
|
-
}
|
|
812
|
-
function formatFileTreeResults(result, format = "markdown" /* MARKDOWN */) {
|
|
813
|
-
if (isError(result)) {
|
|
814
|
-
return formatError(result, format);
|
|
815
|
-
}
|
|
816
|
-
if (format === "json" /* JSON */) {
|
|
817
|
-
return JSON.stringify(result, null, 2);
|
|
818
|
-
}
|
|
819
|
-
const lines = [];
|
|
820
|
-
lines.push(`## File Tree`);
|
|
821
|
-
lines.push("");
|
|
822
|
-
if (result.subpath) {
|
|
823
|
-
lines.push(`Showing \`${result.subpath}\` in \`${result.path}\``);
|
|
824
|
-
} else {
|
|
825
|
-
lines.push(`Repository: \`${result.path}\``);
|
|
826
|
-
}
|
|
827
|
-
lines.push("");
|
|
828
|
-
lines.push(`**${result.count}** entries`);
|
|
829
|
-
lines.push("");
|
|
830
|
-
if (result.entries.length === 0) {
|
|
831
|
-
lines.push("_No files found._");
|
|
832
|
-
return lines.join(`
|
|
833
|
-
`);
|
|
834
|
-
}
|
|
835
|
-
for (const entry of result.entries) {
|
|
836
|
-
const icon = entry.isDir ? "\uD83D\uDCC1" : "\uD83D\uDCC4";
|
|
837
|
-
const size = entry.isDir ? "" : ` (${formatBytes(entry.size)})`;
|
|
838
|
-
lines.push(`- ${icon} \`${entry.path}\`${size}`);
|
|
839
|
-
}
|
|
840
|
-
return lines.join(`
|
|
841
|
-
`);
|
|
842
|
-
}
|
|
843
|
-
function formatFileContentResults(result, format = "markdown" /* MARKDOWN */) {
|
|
844
|
-
if (isError(result)) {
|
|
845
|
-
return formatError(result, format);
|
|
846
|
-
}
|
|
847
|
-
if (format === "json" /* JSON */) {
|
|
848
|
-
return JSON.stringify(result, null, 2);
|
|
849
|
-
}
|
|
850
|
-
const lines = [];
|
|
851
|
-
lines.push(`## File Contents`);
|
|
852
|
-
lines.push("");
|
|
853
|
-
const found = result.files.filter((f) => f.found).length;
|
|
854
|
-
const notFound = result.files.length - found;
|
|
855
|
-
lines.push(`**${found}** of **${result.count}** files retrieved`);
|
|
856
|
-
if (notFound > 0) {
|
|
857
|
-
lines.push(` (${notFound} not found)`);
|
|
858
|
-
}
|
|
859
|
-
lines.push("");
|
|
860
|
-
for (const file of result.files) {
|
|
861
|
-
lines.push(`### ${file.file}`);
|
|
862
|
-
lines.push("");
|
|
863
|
-
if (!file.found) {
|
|
864
|
-
lines.push(`> **Error:** ${file.error || "File not found"}`);
|
|
865
|
-
} else {
|
|
866
|
-
const ext = file.file.split(".").pop() || "";
|
|
867
|
-
const lang = getLanguageForExtension(ext);
|
|
868
|
-
lines.push(`\`\`\`${lang}`);
|
|
869
|
-
lines.push(truncate(file.content, 5000));
|
|
870
|
-
lines.push("```");
|
|
871
|
-
}
|
|
872
|
-
lines.push("");
|
|
873
|
-
}
|
|
874
|
-
return lines.join(`
|
|
875
|
-
`);
|
|
876
|
-
}
|
|
877
|
-
function getLanguageForExtension(ext) {
|
|
878
|
-
const langMap = {
|
|
879
|
-
ts: "typescript",
|
|
880
|
-
tsx: "tsx",
|
|
881
|
-
js: "javascript",
|
|
882
|
-
jsx: "jsx",
|
|
883
|
-
py: "python",
|
|
884
|
-
rb: "ruby",
|
|
885
|
-
go: "go",
|
|
886
|
-
rs: "rust",
|
|
887
|
-
java: "java",
|
|
888
|
-
kt: "kotlin",
|
|
889
|
-
swift: "swift",
|
|
890
|
-
c: "c",
|
|
891
|
-
cpp: "cpp",
|
|
892
|
-
h: "c",
|
|
893
|
-
hpp: "cpp",
|
|
894
|
-
cs: "csharp",
|
|
895
|
-
php: "php",
|
|
896
|
-
sh: "bash",
|
|
897
|
-
bash: "bash",
|
|
898
|
-
zsh: "bash",
|
|
899
|
-
json: "json",
|
|
900
|
-
yaml: "yaml",
|
|
901
|
-
yml: "yaml",
|
|
902
|
-
toml: "toml",
|
|
903
|
-
xml: "xml",
|
|
904
|
-
html: "html",
|
|
905
|
-
css: "css",
|
|
906
|
-
scss: "scss",
|
|
907
|
-
md: "markdown",
|
|
908
|
-
sql: "sql"
|
|
909
|
-
};
|
|
910
|
-
return langMap[ext.toLowerCase()] || "";
|
|
911
|
-
}
|
|
912
|
-
function formatUsagesResults(result, format = "markdown" /* MARKDOWN */) {
|
|
913
|
-
if (isError(result)) {
|
|
914
|
-
return formatError(result, format);
|
|
915
|
-
}
|
|
916
|
-
if (format === "json" /* JSON */) {
|
|
917
|
-
return JSON.stringify(result, null, 2);
|
|
918
|
-
}
|
|
919
|
-
const lines = [];
|
|
920
|
-
lines.push(`## Symbol Definitions`);
|
|
921
|
-
lines.push("");
|
|
922
|
-
lines.push(`Found **${result.count}** definition(s) for \`${result.symbolName}\``);
|
|
923
|
-
lines.push("");
|
|
924
|
-
if (result.usages.length === 0) {
|
|
925
|
-
lines.push("_No definitions found._");
|
|
926
|
-
return lines.join(`
|
|
927
|
-
`);
|
|
928
|
-
}
|
|
929
|
-
for (const usage of result.usages) {
|
|
930
|
-
const icon = getSymbolTypeIcon(usage.type);
|
|
931
|
-
const lineInfo = usage.line ? `:${usage.line}` : "";
|
|
932
|
-
lines.push(`### ${icon} ${usage.name}`);
|
|
933
|
-
lines.push("");
|
|
934
|
-
lines.push(`- **Type:** ${usage.type}`);
|
|
935
|
-
lines.push(`- **File:** \`${usage.file}${lineInfo}\``);
|
|
936
|
-
if (usage.context) {
|
|
937
|
-
lines.push("");
|
|
938
|
-
lines.push("```");
|
|
939
|
-
lines.push(usage.context);
|
|
940
|
-
lines.push("```");
|
|
941
|
-
}
|
|
942
|
-
lines.push("");
|
|
943
|
-
}
|
|
944
|
-
return lines.join(`
|
|
945
|
-
`);
|
|
946
|
-
}
|
|
947
|
-
function getSymbolTypeIcon(type) {
|
|
948
|
-
const icons = {
|
|
949
|
-
function: "\uD83D\uDCE6",
|
|
950
|
-
class: "\uD83D\uDCDA",
|
|
951
|
-
method: "\uD83D\uDD27",
|
|
952
|
-
property: "\uD83C\uDFF7️",
|
|
953
|
-
variable: "\uD83D\uDCCC",
|
|
954
|
-
constant: "\uD83D\uDD12",
|
|
955
|
-
type: "\uD83D\uDCDD",
|
|
956
|
-
interface: "\uD83D\uDCCB",
|
|
957
|
-
enum: "\uD83D\uDCCA",
|
|
958
|
-
module: "\uD83D\uDCC1"
|
|
959
|
-
};
|
|
960
|
-
return icons[type.toLowerCase()] ?? "•";
|
|
961
|
-
}
|
|
962
|
-
function formatAstSearchResults(result, format = "markdown" /* MARKDOWN */) {
|
|
963
|
-
if (isError(result)) {
|
|
964
|
-
return formatError(result, format);
|
|
965
|
-
}
|
|
966
|
-
if (format === "json" /* JSON */) {
|
|
967
|
-
return JSON.stringify(result, null, 2);
|
|
968
|
-
}
|
|
969
|
-
const lines = [];
|
|
970
|
-
lines.push("## AST Search Results");
|
|
971
|
-
lines.push("");
|
|
972
|
-
lines.push(`Found **${result.count}** matches for pattern \`${result.pattern}\` (${result.mode} mode)`);
|
|
973
|
-
lines.push("");
|
|
974
|
-
if (result.matches.length === 0) {
|
|
975
|
-
lines.push("_No matches found._");
|
|
976
|
-
return lines.join(`
|
|
977
|
-
`);
|
|
978
|
-
}
|
|
979
|
-
const byFile = new Map;
|
|
980
|
-
for (const match of result.matches) {
|
|
981
|
-
const existing = byFile.get(match.file) ?? [];
|
|
982
|
-
existing.push(match);
|
|
983
|
-
byFile.set(match.file, existing);
|
|
984
|
-
}
|
|
985
|
-
for (const [file, matches] of byFile) {
|
|
986
|
-
lines.push(`### ${file}`);
|
|
987
|
-
lines.push("");
|
|
988
|
-
for (const m of matches) {
|
|
989
|
-
const ctx = m.context.parentFunction ? ` in \`${m.context.parentFunction}\`` : m.context.parentClass ? ` in class \`${m.context.parentClass}\`` : "";
|
|
990
|
-
lines.push(`- **L${m.line}** \`${m.nodeType}\`${ctx}`);
|
|
991
|
-
lines.push("```");
|
|
992
|
-
lines.push(truncate(m.text, 200));
|
|
993
|
-
lines.push("```");
|
|
994
|
-
}
|
|
995
|
-
lines.push("");
|
|
996
|
-
}
|
|
997
|
-
return lines.join(`
|
|
998
|
-
`);
|
|
999
|
-
}
|
|
1000
|
-
// src/lib/utils/index-parser.ts
|
|
1001
|
-
import { dirname, join as join2, resolve } from "node:path";
|
|
1002
|
-
import { findUpSync, pathExistsSync } from "@side-quest/core/fs";
|
|
1003
|
-
async function findProjectIndex(startDir = process.cwd()) {
|
|
1004
|
-
const indexPath = findUpSync("PROJECT_INDEX.json", startDir);
|
|
1005
|
-
if (indexPath) {
|
|
1006
|
-
return indexPath;
|
|
1007
|
-
}
|
|
1008
|
-
throw new Error(`PROJECT_INDEX.json not found in current directory or any parent directory.
|
|
1009
|
-
` + "Run: bun run src/cli.ts prime");
|
|
1010
|
-
}
|
|
1011
|
-
function resolveExplicitIndexPath(explicitPath) {
|
|
1012
|
-
const resolved = resolve(explicitPath);
|
|
1013
|
-
if (resolved.endsWith("PROJECT_INDEX.json")) {
|
|
1014
|
-
if (pathExistsSync(resolved)) {
|
|
1015
|
-
return resolved;
|
|
1016
|
-
}
|
|
1017
|
-
throw new Error(`PROJECT_INDEX.json not found at: ${resolved}`);
|
|
1018
|
-
}
|
|
1019
|
-
const indexInDir = join2(resolved, "PROJECT_INDEX.json");
|
|
1020
|
-
if (pathExistsSync(indexInDir)) {
|
|
1021
|
-
return indexInDir;
|
|
1022
|
-
}
|
|
1023
|
-
throw new Error(`PROJECT_INDEX.json not found at: ${indexInDir}
|
|
1024
|
-
` + `Specify either:
|
|
1025
|
-
` + ` - Path to PROJECT_INDEX.json file
|
|
1026
|
-
` + " - Directory containing PROJECT_INDEX.json");
|
|
1027
|
-
}
|
|
1028
|
-
async function loadProjectIndex(explicitPath) {
|
|
1029
|
-
const indexPath = explicitPath ? resolveExplicitIndexPath(explicitPath) : await findProjectIndex();
|
|
1030
|
-
const file = Bun.file(indexPath);
|
|
1031
|
-
return await file.json();
|
|
1032
|
-
}
|
|
1033
|
-
function findSymbol(index, symbolName) {
|
|
1034
|
-
const results = [];
|
|
1035
|
-
for (const [file, symbols] of Object.entries(index.symbols)) {
|
|
1036
|
-
for (const symbol of symbols) {
|
|
1037
|
-
if (symbol.name === symbolName) {
|
|
1038
|
-
results.push({ file, symbol });
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
return results;
|
|
1043
|
-
}
|
|
1044
|
-
function findSymbolFuzzy(index, query) {
|
|
1045
|
-
const results = [];
|
|
1046
|
-
const lowerQuery = query.toLowerCase();
|
|
1047
|
-
for (const [file, symbols] of Object.entries(index.symbols)) {
|
|
1048
|
-
for (const symbol of symbols) {
|
|
1049
|
-
const lowerName = symbol.name.toLowerCase();
|
|
1050
|
-
if (lowerName === lowerQuery) {
|
|
1051
|
-
results.push({ file, symbol, score: 100 });
|
|
1052
|
-
} else if (lowerName.startsWith(lowerQuery)) {
|
|
1053
|
-
results.push({ file, symbol, score: 75 });
|
|
1054
|
-
} else if (lowerName.includes(lowerQuery)) {
|
|
1055
|
-
results.push({ file, symbol, score: 50 });
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
return results.sort((a, b) => {
|
|
1060
|
-
if (b.score !== a.score)
|
|
1061
|
-
return b.score - a.score;
|
|
1062
|
-
return a.symbol.name.localeCompare(b.symbol.name);
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
function getFileSymbols(index, filePath) {
|
|
1066
|
-
if (index.symbols[filePath]) {
|
|
1067
|
-
return index.symbols[filePath];
|
|
1068
|
-
}
|
|
1069
|
-
for (const [path, symbols] of Object.entries(index.symbols)) {
|
|
1070
|
-
if (path.endsWith(filePath)) {
|
|
1071
|
-
return symbols;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
return [];
|
|
1075
|
-
}
|
|
1076
|
-
function getSymbolTypeDistribution(index) {
|
|
1077
|
-
const distribution = {
|
|
1078
|
-
function: 0,
|
|
1079
|
-
class: 0,
|
|
1080
|
-
interface: 0,
|
|
1081
|
-
type: 0,
|
|
1082
|
-
constant: 0,
|
|
1083
|
-
variable: 0
|
|
1084
|
-
};
|
|
1085
|
-
for (const symbols of Object.values(index.symbols)) {
|
|
1086
|
-
for (const symbol of symbols) {
|
|
1087
|
-
distribution[symbol.type] = (distribution[symbol.type] || 0) + 1;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
return distribution;
|
|
1091
|
-
}
|
|
1092
|
-
function groupSymbolsByDirectory(index) {
|
|
1093
|
-
const dirCounts = {};
|
|
1094
|
-
for (const [file, symbols] of Object.entries(index.symbols)) {
|
|
1095
|
-
const dir = dirname(file);
|
|
1096
|
-
dirCounts[dir] = (dirCounts[dir] || 0) + symbols.length;
|
|
1097
|
-
}
|
|
1098
|
-
return dirCounts;
|
|
1099
|
-
}
|
|
1100
|
-
function getComplexityHotspots(index, topN = 10) {
|
|
1101
|
-
const dirCounts = groupSymbolsByDirectory(index);
|
|
1102
|
-
const sorted = Object.entries(dirCounts).map(([directory, symbolCount]) => ({ directory, symbolCount })).sort((a, b) => b.symbolCount - a.symbolCount);
|
|
1103
|
-
return sorted.slice(0, topN);
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
// src/lib/index-tools.ts
|
|
1107
|
-
import { join as join3, resolve as resolve2 } from "node:path";
|
|
1108
|
-
import { getFileAgeHours, getFileSizeMB } from "@side-quest/core/fs";
|
|
1109
|
-
import {
|
|
1110
|
-
ensureCommandAvailable,
|
|
1111
|
-
spawnWithTimeout
|
|
1112
|
-
} from "@side-quest/core/spawn";
|
|
1113
|
-
async function executeIndexFind(symbolName, indexPath) {
|
|
1114
|
-
try {
|
|
1115
|
-
const index = await loadProjectIndex(indexPath);
|
|
1116
|
-
let results = findSymbol(index, symbolName);
|
|
1117
|
-
let matchType = "exact";
|
|
1118
|
-
if (results.length === 0) {
|
|
1119
|
-
const fuzzyResults = findSymbolFuzzy(index, symbolName);
|
|
1120
|
-
matchType = "fuzzy";
|
|
1121
|
-
results = fuzzyResults.map(({ file, symbol }) => ({ file, symbol }));
|
|
1122
|
-
return {
|
|
1123
|
-
query: symbolName,
|
|
1124
|
-
matchType,
|
|
1125
|
-
count: fuzzyResults.length,
|
|
1126
|
-
results: fuzzyResults.map(({ file, symbol, score }) => ({
|
|
1127
|
-
file,
|
|
1128
|
-
name: symbol.name,
|
|
1129
|
-
type: symbol.type,
|
|
1130
|
-
line: symbol.start_line,
|
|
1131
|
-
code: symbol.code,
|
|
1132
|
-
score
|
|
1133
|
-
}))
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
return {
|
|
1137
|
-
query: symbolName,
|
|
1138
|
-
matchType,
|
|
1139
|
-
count: results.length,
|
|
1140
|
-
results: results.map(({ file, symbol }) => ({
|
|
1141
|
-
file,
|
|
1142
|
-
name: symbol.name,
|
|
1143
|
-
type: symbol.type,
|
|
1144
|
-
line: symbol.start_line,
|
|
1145
|
-
code: symbol.code
|
|
1146
|
-
}))
|
|
1147
|
-
};
|
|
1148
|
-
} catch (error) {
|
|
1149
|
-
return {
|
|
1150
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1151
|
-
isError: true
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
async function executeIndexStats(indexPath, topN = 5) {
|
|
1156
|
-
try {
|
|
1157
|
-
const index = await loadProjectIndex(indexPath);
|
|
1158
|
-
const distribution = getSymbolTypeDistribution(index);
|
|
1159
|
-
const hotspots = getComplexityHotspots(index, topN);
|
|
1160
|
-
const totalSymbols = Object.values(distribution).reduce((sum, count) => sum + count, 0);
|
|
1161
|
-
return {
|
|
1162
|
-
files: Object.keys(index.symbols).length,
|
|
1163
|
-
totalSymbols,
|
|
1164
|
-
distribution,
|
|
1165
|
-
hotspots
|
|
1166
|
-
};
|
|
1167
|
-
} catch (error) {
|
|
1168
|
-
return {
|
|
1169
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1170
|
-
isError: true
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
async function executeIndexOverview(filePath, indexPath) {
|
|
1175
|
-
try {
|
|
1176
|
-
const index = await loadProjectIndex(indexPath);
|
|
1177
|
-
const symbols = getFileSymbols(index, filePath);
|
|
1178
|
-
return {
|
|
1179
|
-
file: filePath,
|
|
1180
|
-
symbolCount: symbols.length,
|
|
1181
|
-
symbols: symbols.map((s) => ({
|
|
1182
|
-
name: s.name,
|
|
1183
|
-
type: s.type,
|
|
1184
|
-
line: s.start_line
|
|
1185
|
-
}))
|
|
1186
|
-
};
|
|
1187
|
-
} catch (error) {
|
|
1188
|
-
return {
|
|
1189
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1190
|
-
isError: true
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
var INDEX_FILE = "PROJECT_INDEX.json";
|
|
1195
|
-
var MAX_AGE_HOURS = 24;
|
|
1196
|
-
async function findGitRoot() {
|
|
1197
|
-
const gitCmd = ensureCommandAvailable("git");
|
|
1198
|
-
const result = await spawnWithTimeout([gitCmd, "rev-parse", "--show-toplevel"], 1e4);
|
|
1199
|
-
if (result.timedOut || result.exitCode !== 0) {
|
|
1200
|
-
return null;
|
|
1201
|
-
}
|
|
1202
|
-
if (result.stdout) {
|
|
1203
|
-
return result.stdout.trim();
|
|
1204
|
-
}
|
|
1205
|
-
return null;
|
|
1206
|
-
}
|
|
1207
|
-
async function getTargetDir(customPath) {
|
|
1208
|
-
if (customPath) {
|
|
1209
|
-
return resolve2(customPath);
|
|
1210
|
-
}
|
|
1211
|
-
const gitRoot = await findGitRoot();
|
|
1212
|
-
if (gitRoot) {
|
|
1213
|
-
return gitRoot;
|
|
1214
|
-
}
|
|
1215
|
-
return process.cwd();
|
|
1216
|
-
}
|
|
1217
|
-
async function executeIndexPrime(force = false, customPath) {
|
|
1218
|
-
try {
|
|
1219
|
-
const targetDir = await getTargetDir(customPath);
|
|
1220
|
-
const kitCmd = ensureCommandAvailable("kit");
|
|
1221
|
-
const indexPath = join3(targetDir, INDEX_FILE);
|
|
1222
|
-
const ageHours = getFileAgeHours(indexPath);
|
|
1223
|
-
if (ageHours !== null && ageHours <= MAX_AGE_HOURS && !force) {
|
|
1224
|
-
const index2 = await loadProjectIndex(indexPath);
|
|
1225
|
-
const symbolCount2 = Object.values(index2.symbols).reduce((sum, symbols) => sum + symbols.length, 0);
|
|
1226
|
-
const sizeMB2 = getFileSizeMB(indexPath);
|
|
1227
|
-
return {
|
|
1228
|
-
status: "exists",
|
|
1229
|
-
location: targetDir,
|
|
1230
|
-
ageHours: Number.parseFloat(ageHours.toFixed(1)),
|
|
1231
|
-
files: Object.keys(index2.symbols).length,
|
|
1232
|
-
symbols: symbolCount2,
|
|
1233
|
-
size: `${sizeMB2} MB`,
|
|
1234
|
-
message: "Index is less than 24 hours old. Use force=true to regenerate."
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
const startTime = Date.now();
|
|
1238
|
-
const result = await spawnWithTimeout([kitCmd, "index", targetDir, "-o", indexPath], 60000);
|
|
1239
|
-
if (result.timedOut) {
|
|
1240
|
-
return {
|
|
1241
|
-
error: "kit index timed out",
|
|
1242
|
-
isError: true
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
if (result.exitCode !== 0) {
|
|
1246
|
-
return {
|
|
1247
|
-
error: `kit index failed: ${result.stderr}`,
|
|
1248
|
-
isError: true
|
|
1249
|
-
};
|
|
1250
|
-
}
|
|
1251
|
-
const durationSec = (Date.now() - startTime) / 1000;
|
|
1252
|
-
const index = await loadProjectIndex(indexPath);
|
|
1253
|
-
const symbolCount = Object.values(index.symbols).reduce((sum, symbols) => sum + symbols.length, 0);
|
|
1254
|
-
const sizeMB = getFileSizeMB(indexPath);
|
|
1255
|
-
return {
|
|
1256
|
-
success: true,
|
|
1257
|
-
location: targetDir,
|
|
1258
|
-
files: Object.keys(index.symbols).length,
|
|
1259
|
-
symbols: symbolCount,
|
|
1260
|
-
size: `${sizeMB} MB`,
|
|
1261
|
-
durationSec: Number.parseFloat(durationSec.toFixed(1))
|
|
1262
|
-
};
|
|
1263
|
-
} catch (error) {
|
|
1264
|
-
return {
|
|
1265
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1266
|
-
isError: true
|
|
1267
|
-
};
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
function formatIndexFindResults(result, format) {
|
|
1271
|
-
if ("isError" in result) {
|
|
1272
|
-
return format === "json" ? JSON.stringify(result, null, 2) : `**Error:** ${result.error}`;
|
|
1273
|
-
}
|
|
1274
|
-
if (format === "json") {
|
|
1275
|
-
return JSON.stringify(result, null, 2);
|
|
1276
|
-
}
|
|
1277
|
-
if (result.count === 0) {
|
|
1278
|
-
return `No symbols found matching: ${result.query}`;
|
|
1279
|
-
}
|
|
1280
|
-
let output = `Found ${result.count} ${result.matchType} match(es) for "${result.query}":
|
|
1281
|
-
|
|
1282
|
-
`;
|
|
1283
|
-
for (const r of result.results) {
|
|
1284
|
-
output += `- **${r.name}** (${r.type}) in \`${r.file}:${r.line}\``;
|
|
1285
|
-
if (r.score !== undefined) {
|
|
1286
|
-
output += ` [score: ${r.score}]`;
|
|
1287
|
-
}
|
|
1288
|
-
output += `
|
|
1289
|
-
`;
|
|
1290
|
-
}
|
|
1291
|
-
return output;
|
|
1292
|
-
}
|
|
1293
|
-
function formatIndexStatsResults(result, format) {
|
|
1294
|
-
if ("isError" in result) {
|
|
1295
|
-
return format === "json" ? JSON.stringify(result, null, 2) : `**Error:** ${result.error}`;
|
|
1296
|
-
}
|
|
1297
|
-
if (format === "json") {
|
|
1298
|
-
return JSON.stringify(result, null, 2);
|
|
1299
|
-
}
|
|
1300
|
-
let output = `## Codebase Statistics
|
|
1301
|
-
|
|
1302
|
-
`;
|
|
1303
|
-
output += `- **Files:** ${result.files}
|
|
1304
|
-
`;
|
|
1305
|
-
output += `- **Total Symbols:** ${result.totalSymbols}
|
|
1306
|
-
|
|
1307
|
-
`;
|
|
1308
|
-
output += `### Symbol Distribution
|
|
1309
|
-
`;
|
|
1310
|
-
for (const [type, count] of Object.entries(result.distribution)) {
|
|
1311
|
-
if (count > 0) {
|
|
1312
|
-
output += `- ${type}: ${count}
|
|
1313
|
-
`;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
output += `
|
|
1317
|
-
### Complexity Hotspots
|
|
1318
|
-
`;
|
|
1319
|
-
for (const { directory, symbolCount } of result.hotspots) {
|
|
1320
|
-
output += `- \`${directory}\`: ${symbolCount} symbols
|
|
1321
|
-
`;
|
|
1322
|
-
}
|
|
1323
|
-
return output;
|
|
1324
|
-
}
|
|
1325
|
-
function formatIndexOverviewResults(result, format) {
|
|
1326
|
-
if ("isError" in result) {
|
|
1327
|
-
return format === "json" ? JSON.stringify(result, null, 2) : `**Error:** ${result.error}`;
|
|
1328
|
-
}
|
|
1329
|
-
if (format === "json") {
|
|
1330
|
-
return JSON.stringify(result, null, 2);
|
|
1331
|
-
}
|
|
1332
|
-
if (result.symbolCount === 0) {
|
|
1333
|
-
return `No symbols found in: ${result.file}`;
|
|
1334
|
-
}
|
|
1335
|
-
let output = `## ${result.file}
|
|
1336
|
-
|
|
1337
|
-
`;
|
|
1338
|
-
output += `**${result.symbolCount} symbol(s)**
|
|
1339
|
-
|
|
1340
|
-
`;
|
|
1341
|
-
const byType = {};
|
|
1342
|
-
for (const sym of result.symbols) {
|
|
1343
|
-
if (!byType[sym.type]) {
|
|
1344
|
-
byType[sym.type] = [];
|
|
1345
|
-
}
|
|
1346
|
-
byType[sym.type].push(sym);
|
|
1347
|
-
}
|
|
1348
|
-
for (const [type, symbols] of Object.entries(byType)) {
|
|
1349
|
-
output += `### ${type}s
|
|
1350
|
-
`;
|
|
1351
|
-
for (const sym of symbols) {
|
|
1352
|
-
output += `- \`${sym.name}\` (line ${sym.line})
|
|
1353
|
-
`;
|
|
1354
|
-
}
|
|
1355
|
-
output += `
|
|
1356
|
-
`;
|
|
1357
|
-
}
|
|
1358
|
-
return output;
|
|
1359
|
-
}
|
|
1360
|
-
function formatIndexPrimeResults(result, format) {
|
|
1361
|
-
if ("isError" in result) {
|
|
1362
|
-
return format === "json" ? JSON.stringify(result, null, 2) : `**Error:** ${result.error}`;
|
|
1363
|
-
}
|
|
1364
|
-
if (format === "json") {
|
|
1365
|
-
return JSON.stringify(result, null, 2);
|
|
1366
|
-
}
|
|
1367
|
-
if ("status" in result && result.status === "exists") {
|
|
1368
|
-
return `## Index Already Exists
|
|
1369
|
-
|
|
1370
|
-
` + `- **Location:** ${result.location}
|
|
1371
|
-
` + `- **Age:** ${result.ageHours} hours
|
|
1372
|
-
` + `- **Files:** ${result.files}
|
|
1373
|
-
` + `- **Symbols:** ${result.symbols}
|
|
1374
|
-
` + `- **Size:** ${result.size}
|
|
1375
|
-
|
|
1376
|
-
` + `> ${result.message}`;
|
|
1377
|
-
}
|
|
1378
|
-
const primeResult = result;
|
|
1379
|
-
return `## Index Generated Successfully
|
|
1380
|
-
|
|
1381
|
-
` + `- **Location:** ${primeResult.location}
|
|
1382
|
-
` + `- **Files:** ${primeResult.files}
|
|
1383
|
-
` + `- **Symbols:** ${primeResult.symbols}
|
|
1384
|
-
` + `- **Size:** ${primeResult.size}
|
|
1385
|
-
` + `- **Duration:** ${primeResult.durationSec}s`;
|
|
1386
|
-
}
|
|
1387
|
-
// src/lib/kit-wrapper.ts
|
|
1388
|
-
import { join as join4 } from "node:path";
|
|
1389
|
-
import { TimeoutError, withTimeout } from "@side-quest/core/concurrency";
|
|
1390
|
-
import {
|
|
1391
|
-
ensureCacheDir,
|
|
1392
|
-
isCachePopulated,
|
|
1393
|
-
withTempJsonFileSync
|
|
1394
|
-
} from "@side-quest/core/fs";
|
|
1395
|
-
import {
|
|
1396
|
-
buildEnhancedPath,
|
|
1397
|
-
ensureCommandAvailable as ensureCommandAvailable2,
|
|
1398
|
-
spawnSyncCollect
|
|
1399
|
-
} from "@side-quest/core/spawn";
|
|
1400
|
-
function isKitInstalled() {
|
|
1401
|
-
try {
|
|
1402
|
-
ensureCommandAvailable2("kit");
|
|
1403
|
-
return true;
|
|
1404
|
-
} catch {
|
|
1405
|
-
return false;
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
function getKitVersion() {
|
|
1409
|
-
try {
|
|
1410
|
-
const result = spawnSyncCollect(["kit", "--version"], {
|
|
1411
|
-
env: {
|
|
1412
|
-
...process.env,
|
|
1413
|
-
PATH: buildEnhancedPath()
|
|
1414
|
-
}
|
|
1415
|
-
});
|
|
1416
|
-
if (result.exitCode === 0 && result.stdout) {
|
|
1417
|
-
return result.stdout.trim();
|
|
1418
|
-
}
|
|
1419
|
-
return null;
|
|
1420
|
-
} catch {
|
|
1421
|
-
return null;
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
function executeKit(args, options = {}) {
|
|
1425
|
-
const { cwd } = options;
|
|
1426
|
-
const result = spawnSyncCollect(["kit", ...args], {
|
|
1427
|
-
env: {
|
|
1428
|
-
...process.env,
|
|
1429
|
-
PATH: buildEnhancedPath()
|
|
1430
|
-
},
|
|
1431
|
-
...cwd && { cwd }
|
|
1432
|
-
});
|
|
1433
|
-
return {
|
|
1434
|
-
stdout: result.stdout || "",
|
|
1435
|
-
stderr: result.stderr || "",
|
|
1436
|
-
exitCode: result.exitCode ?? 1
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
function executeKitGrep(options) {
|
|
1440
|
-
const cid = createCorrelationId();
|
|
1441
|
-
const startTime = Date.now();
|
|
1442
|
-
if (!isKitInstalled()) {
|
|
1443
|
-
grepLogger.error("Kit not installed", { cid });
|
|
1444
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1445
|
-
}
|
|
1446
|
-
const {
|
|
1447
|
-
pattern,
|
|
1448
|
-
path = getDefaultKitPath(),
|
|
1449
|
-
caseSensitive = true,
|
|
1450
|
-
include,
|
|
1451
|
-
exclude,
|
|
1452
|
-
maxResults = 100,
|
|
1453
|
-
directory
|
|
1454
|
-
} = options;
|
|
1455
|
-
const args = ["grep", path, pattern];
|
|
1456
|
-
if (!caseSensitive) {
|
|
1457
|
-
args.push("--ignore-case");
|
|
1458
|
-
}
|
|
1459
|
-
if (include) {
|
|
1460
|
-
args.push("--include", include);
|
|
1461
|
-
}
|
|
1462
|
-
if (exclude) {
|
|
1463
|
-
args.push("--exclude", exclude);
|
|
1464
|
-
}
|
|
1465
|
-
args.push("--max-results", String(maxResults));
|
|
1466
|
-
if (directory) {
|
|
1467
|
-
args.push("--directory", directory);
|
|
1468
|
-
}
|
|
1469
|
-
grepLogger.info("Executing kit grep", {
|
|
1470
|
-
cid,
|
|
1471
|
-
pattern,
|
|
1472
|
-
path,
|
|
1473
|
-
args
|
|
1474
|
-
});
|
|
1475
|
-
try {
|
|
1476
|
-
const rawMatches = withTempJsonFileSync(`kit-grep-${cid}`, (tempFile) => {
|
|
1477
|
-
args.push("--output", tempFile);
|
|
1478
|
-
const result = executeKit(args, { timeout: GREP_TIMEOUT });
|
|
1479
|
-
return {
|
|
1480
|
-
exitCode: result.exitCode,
|
|
1481
|
-
stderr: result.stderr
|
|
1482
|
-
};
|
|
1483
|
-
});
|
|
1484
|
-
const matches = rawMatches.map((m) => ({
|
|
1485
|
-
file: m.file,
|
|
1486
|
-
line: m.line_number,
|
|
1487
|
-
content: m.line_content
|
|
1488
|
-
}));
|
|
1489
|
-
grepLogger.info("Grep completed", {
|
|
1490
|
-
cid,
|
|
1491
|
-
pattern,
|
|
1492
|
-
matchCount: matches.length,
|
|
1493
|
-
durationMs: Date.now() - startTime
|
|
1494
|
-
});
|
|
1495
|
-
return {
|
|
1496
|
-
count: matches.length,
|
|
1497
|
-
matches,
|
|
1498
|
-
pattern,
|
|
1499
|
-
path
|
|
1500
|
-
};
|
|
1501
|
-
} catch (error) {
|
|
1502
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1503
|
-
grepLogger.error("Grep threw exception", { cid, error: message });
|
|
1504
|
-
if (message.includes("Operation failed with exit code")) {
|
|
1505
|
-
const exitCode = Number.parseInt(message.match(/exit code (\d+)/)?.[1] || "1", 10);
|
|
1506
|
-
const stderr = message.split(": ").slice(2).join(": ") || message;
|
|
1507
|
-
return createErrorFromOutput(stderr, exitCode).toJSON();
|
|
1508
|
-
}
|
|
1509
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
function executeKitSymbols(options) {
|
|
1513
|
-
const cid = createCorrelationId();
|
|
1514
|
-
const startTime = Date.now();
|
|
1515
|
-
if (!isKitInstalled()) {
|
|
1516
|
-
symbolsLogger.error("Kit not installed", { cid });
|
|
1517
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1518
|
-
}
|
|
1519
|
-
const { path = getDefaultKitPath(), pattern, symbolType, file } = options;
|
|
1520
|
-
const args = ["symbols", path, "--format", "json"];
|
|
1521
|
-
if (file) {
|
|
1522
|
-
args.push("--file", file);
|
|
1523
|
-
}
|
|
1524
|
-
if (pattern) {}
|
|
1525
|
-
symbolsLogger.info("Executing kit symbols", {
|
|
1526
|
-
cid,
|
|
1527
|
-
path,
|
|
1528
|
-
pattern,
|
|
1529
|
-
symbolType
|
|
1530
|
-
});
|
|
1531
|
-
try {
|
|
1532
|
-
const result = executeKit(args, { timeout: SYMBOLS_TIMEOUT });
|
|
1533
|
-
if (result.exitCode !== 0) {
|
|
1534
|
-
symbolsLogger.error("Symbols failed", {
|
|
1535
|
-
cid,
|
|
1536
|
-
exitCode: result.exitCode,
|
|
1537
|
-
stderr: result.stderr,
|
|
1538
|
-
durationMs: Date.now() - startTime
|
|
1539
|
-
});
|
|
1540
|
-
return createErrorFromOutput(result.stderr, result.exitCode).toJSON();
|
|
1541
|
-
}
|
|
1542
|
-
let rawSymbols;
|
|
1543
|
-
try {
|
|
1544
|
-
rawSymbols = JSON.parse(result.stdout);
|
|
1545
|
-
} catch {
|
|
1546
|
-
symbolsLogger.error("Failed to parse symbols output", {
|
|
1547
|
-
cid,
|
|
1548
|
-
stdout: result.stdout
|
|
1549
|
-
});
|
|
1550
|
-
return new KitError("OutputParseError" /* OutputParseError */, "Failed to parse symbols JSON output").toJSON();
|
|
1551
|
-
}
|
|
1552
|
-
let symbols = rawSymbols.map((s) => ({
|
|
1553
|
-
name: s.name,
|
|
1554
|
-
type: s.type,
|
|
1555
|
-
file: s.file,
|
|
1556
|
-
startLine: s.start_line,
|
|
1557
|
-
endLine: s.end_line,
|
|
1558
|
-
code: s.code
|
|
1559
|
-
}));
|
|
1560
|
-
if (symbolType) {
|
|
1561
|
-
symbols = symbols.filter((s) => s.type.toLowerCase() === symbolType.toLowerCase());
|
|
1562
|
-
}
|
|
1563
|
-
if (pattern) {
|
|
1564
|
-
const regex = globToRegex(pattern);
|
|
1565
|
-
symbols = symbols.filter((s) => regex.test(s.file));
|
|
1566
|
-
}
|
|
1567
|
-
symbolsLogger.info("Symbols completed", {
|
|
1568
|
-
cid,
|
|
1569
|
-
symbolCount: symbols.length,
|
|
1570
|
-
durationMs: Date.now() - startTime
|
|
1571
|
-
});
|
|
1572
|
-
return {
|
|
1573
|
-
count: symbols.length,
|
|
1574
|
-
symbols,
|
|
1575
|
-
path
|
|
1576
|
-
};
|
|
1577
|
-
} catch (error) {
|
|
1578
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1579
|
-
symbolsLogger.error("Symbols threw exception", { cid, error: message });
|
|
1580
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
function executeKitSemantic(options) {
|
|
1584
|
-
const cid = createCorrelationId();
|
|
1585
|
-
const startTime = Date.now();
|
|
1586
|
-
if (!isKitInstalled()) {
|
|
1587
|
-
semanticLogger.error("Kit not installed", { cid });
|
|
1588
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1589
|
-
}
|
|
1590
|
-
const {
|
|
1591
|
-
query,
|
|
1592
|
-
path = getDefaultKitPath(),
|
|
1593
|
-
topK = 5,
|
|
1594
|
-
chunkBy = "symbols",
|
|
1595
|
-
buildIndex = false
|
|
1596
|
-
} = options;
|
|
1597
|
-
if (!buildIndex && !isSemanticIndexBuilt(path)) {
|
|
1598
|
-
semanticLogger.info("Semantic index not built, instructing user to build", {
|
|
1599
|
-
cid,
|
|
1600
|
-
path
|
|
1601
|
-
});
|
|
1602
|
-
const buildCommand = `kit search-semantic "${path}" "${query}" --build-index`;
|
|
1603
|
-
const error = new KitError("SemanticIndexNotBuilt" /* SemanticIndexNotBuilt */, `To use semantic search, build the vector index with:
|
|
1604
|
-
|
|
1605
|
-
${buildCommand}
|
|
1606
|
-
|
|
1607
|
-
After building (one-time), semantic search will be fast and cached.`);
|
|
1608
|
-
return error.toJSON();
|
|
1609
|
-
}
|
|
1610
|
-
const persistDir = getSemanticCacheDir(path);
|
|
1611
|
-
const args = [
|
|
1612
|
-
"search-semantic",
|
|
1613
|
-
path,
|
|
1614
|
-
query,
|
|
1615
|
-
"--top-k",
|
|
1616
|
-
String(topK),
|
|
1617
|
-
"--format",
|
|
1618
|
-
"json",
|
|
1619
|
-
"--chunk-by",
|
|
1620
|
-
chunkBy,
|
|
1621
|
-
"--persist-dir",
|
|
1622
|
-
persistDir
|
|
1623
|
-
];
|
|
1624
|
-
if (buildIndex) {
|
|
1625
|
-
args.push("--build-index");
|
|
1626
|
-
}
|
|
1627
|
-
semanticLogger.info("Executing kit semantic search", {
|
|
1628
|
-
cid,
|
|
1629
|
-
query,
|
|
1630
|
-
path,
|
|
1631
|
-
topK,
|
|
1632
|
-
chunkBy,
|
|
1633
|
-
persistDir
|
|
1634
|
-
});
|
|
1635
|
-
try {
|
|
1636
|
-
const result = executeKit(args, { timeout: SEMANTIC_TIMEOUT });
|
|
1637
|
-
const combinedOutput = `${result.stdout}
|
|
1638
|
-
${result.stderr}`;
|
|
1639
|
-
if (result.exitCode !== 0 && isSemanticUnavailableError(combinedOutput)) {
|
|
1640
|
-
semanticLogger.warn("Semantic search unavailable, falling back to grep", {
|
|
1641
|
-
cid,
|
|
1642
|
-
output: combinedOutput.slice(0, 200)
|
|
1643
|
-
});
|
|
1644
|
-
return fallbackToGrep(query, path, topK, cid);
|
|
1645
|
-
}
|
|
1646
|
-
if (result.exitCode !== 0 && isTimeoutError(combinedOutput)) {
|
|
1647
|
-
semanticLogger.warn("Semantic search timed out on large repository", {
|
|
1648
|
-
cid,
|
|
1649
|
-
query,
|
|
1650
|
-
durationMs: Date.now() - startTime
|
|
1651
|
-
});
|
|
1652
|
-
return new KitError("Timeout" /* Timeout */, `Semantic search timed out after ${SEMANTIC_TIMEOUT}ms. On first run, building the vector index may take longer. Try again to use the cached index.`).toJSON();
|
|
1653
|
-
}
|
|
1654
|
-
if (result.exitCode !== 0) {
|
|
1655
|
-
semanticLogger.error("Semantic search failed", {
|
|
1656
|
-
cid,
|
|
1657
|
-
exitCode: result.exitCode,
|
|
1658
|
-
output: combinedOutput.slice(0, 500),
|
|
1659
|
-
durationMs: Date.now() - startTime
|
|
1660
|
-
});
|
|
1661
|
-
return createErrorFromOutput(combinedOutput, result.exitCode).toJSON();
|
|
1662
|
-
}
|
|
1663
|
-
let rawMatches;
|
|
1664
|
-
try {
|
|
1665
|
-
rawMatches = JSON.parse(result.stdout);
|
|
1666
|
-
} catch {
|
|
1667
|
-
semanticLogger.error("Failed to parse semantic output", {
|
|
1668
|
-
cid,
|
|
1669
|
-
stdout: result.stdout
|
|
1670
|
-
});
|
|
1671
|
-
return new KitError("OutputParseError" /* OutputParseError */, "Failed to parse semantic search JSON output").toJSON();
|
|
1672
|
-
}
|
|
1673
|
-
const matches = rawMatches.map((m) => ({
|
|
1674
|
-
file: m.file,
|
|
1675
|
-
chunk: m.code,
|
|
1676
|
-
score: m.score,
|
|
1677
|
-
startLine: m.start_line,
|
|
1678
|
-
endLine: m.end_line
|
|
1679
|
-
}));
|
|
1680
|
-
semanticLogger.info("Semantic search completed", {
|
|
1681
|
-
cid,
|
|
1682
|
-
query,
|
|
1683
|
-
matchCount: matches.length,
|
|
1684
|
-
durationMs: Date.now() - startTime
|
|
1685
|
-
});
|
|
1686
|
-
return {
|
|
1687
|
-
count: matches.length,
|
|
1688
|
-
matches,
|
|
1689
|
-
query,
|
|
1690
|
-
path
|
|
1691
|
-
};
|
|
1692
|
-
} catch (error) {
|
|
1693
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1694
|
-
semanticLogger.error("Semantic search threw exception", {
|
|
1695
|
-
cid,
|
|
1696
|
-
error: message
|
|
1697
|
-
});
|
|
1698
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
function fallbackToGrep(query, path, limit, cid) {
|
|
1702
|
-
const keywords = query.split(/\s+/).filter((w) => w.length > 2).slice(0, 3);
|
|
1703
|
-
const pattern = keywords.join("|");
|
|
1704
|
-
semanticLogger.info("Fallback grep search", { cid, pattern, path });
|
|
1705
|
-
const grepResult = executeKitGrep({
|
|
1706
|
-
pattern,
|
|
1707
|
-
path,
|
|
1708
|
-
maxResults: limit,
|
|
1709
|
-
caseSensitive: false
|
|
1710
|
-
});
|
|
1711
|
-
if ("error" in grepResult) {
|
|
1712
|
-
return grepResult;
|
|
1713
|
-
}
|
|
1714
|
-
const matches = grepResult.matches.map((m, idx) => ({
|
|
1715
|
-
file: m.file,
|
|
1716
|
-
chunk: m.content,
|
|
1717
|
-
score: Math.max(0.1, 1 - idx * 0.05),
|
|
1718
|
-
startLine: m.line,
|
|
1719
|
-
endLine: m.line
|
|
1720
|
-
}));
|
|
1721
|
-
return {
|
|
1722
|
-
count: matches.length,
|
|
1723
|
-
matches,
|
|
1724
|
-
query,
|
|
1725
|
-
path,
|
|
1726
|
-
fallback: true,
|
|
1727
|
-
installHint: SEMANTIC_INSTALL_HINT
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
function executeKitFileTree(options) {
|
|
1731
|
-
const cid = createCorrelationId();
|
|
1732
|
-
const startTime = Date.now();
|
|
1733
|
-
if (!isKitInstalled()) {
|
|
1734
|
-
fileTreeLogger.error("Kit not installed", { cid });
|
|
1735
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1736
|
-
}
|
|
1737
|
-
const { path = getDefaultKitPath(), subpath } = options;
|
|
1738
|
-
const args = ["file-tree", path];
|
|
1739
|
-
if (subpath) {
|
|
1740
|
-
args.push("--path", subpath);
|
|
1741
|
-
}
|
|
1742
|
-
fileTreeLogger.info("Executing kit file-tree", {
|
|
1743
|
-
cid,
|
|
1744
|
-
path,
|
|
1745
|
-
subpath,
|
|
1746
|
-
args
|
|
1747
|
-
});
|
|
1748
|
-
try {
|
|
1749
|
-
const rawEntries = withTempJsonFileSync(`kit-file-tree-${cid}`, (tempFile) => {
|
|
1750
|
-
const argsWithOutput = [...args, "--output", tempFile];
|
|
1751
|
-
const result = executeKit(argsWithOutput, {
|
|
1752
|
-
timeout: FILE_TREE_TIMEOUT
|
|
1753
|
-
});
|
|
1754
|
-
return {
|
|
1755
|
-
exitCode: result.exitCode,
|
|
1756
|
-
stderr: result.stderr
|
|
1757
|
-
};
|
|
1758
|
-
});
|
|
1759
|
-
const entries = rawEntries.map((e) => ({
|
|
1760
|
-
path: e.path,
|
|
1761
|
-
name: e.name,
|
|
1762
|
-
isDir: e.is_dir,
|
|
1763
|
-
size: e.size
|
|
1764
|
-
}));
|
|
1765
|
-
fileTreeLogger.info("File tree completed", {
|
|
1766
|
-
cid,
|
|
1767
|
-
entryCount: entries.length,
|
|
1768
|
-
durationMs: Date.now() - startTime
|
|
1769
|
-
});
|
|
1770
|
-
return {
|
|
1771
|
-
count: entries.length,
|
|
1772
|
-
entries,
|
|
1773
|
-
path,
|
|
1774
|
-
subpath
|
|
1775
|
-
};
|
|
1776
|
-
} catch (error) {
|
|
1777
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1778
|
-
fileTreeLogger.error("File tree threw exception", { cid, error: message });
|
|
1779
|
-
if (message.includes("Operation failed with exit code")) {
|
|
1780
|
-
const exitCode = Number.parseInt(message.match(/exit code (\d+)/)?.[1] || "1", 10);
|
|
1781
|
-
const stderr = message.split(": ").slice(2).join(": ") || message;
|
|
1782
|
-
return createErrorFromOutput(stderr, exitCode).toJSON();
|
|
1783
|
-
}
|
|
1784
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
function executeKitFileContent(options) {
|
|
1788
|
-
const cid = createCorrelationId();
|
|
1789
|
-
const startTime = Date.now();
|
|
1790
|
-
if (!isKitInstalled()) {
|
|
1791
|
-
fileContentLogger.error("Kit not installed", { cid });
|
|
1792
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1793
|
-
}
|
|
1794
|
-
const { path = getDefaultKitPath(), filePaths } = options;
|
|
1795
|
-
if (!filePaths || filePaths.length === 0) {
|
|
1796
|
-
return new KitError("InvalidInput" /* InvalidInput */, "At least one file path is required").toJSON();
|
|
1797
|
-
}
|
|
1798
|
-
fileContentLogger.info("Executing kit file-content", {
|
|
1799
|
-
cid,
|
|
1800
|
-
path,
|
|
1801
|
-
fileCount: filePaths.length
|
|
1802
|
-
});
|
|
1803
|
-
const files = [];
|
|
1804
|
-
for (const filePath of filePaths) {
|
|
1805
|
-
const args = ["file-content", path, filePath];
|
|
1806
|
-
try {
|
|
1807
|
-
const result = executeKit(args, { timeout: FILE_CONTENT_TIMEOUT });
|
|
1808
|
-
if (result.exitCode !== 0) {
|
|
1809
|
-
files.push({
|
|
1810
|
-
file: filePath,
|
|
1811
|
-
content: "",
|
|
1812
|
-
found: false,
|
|
1813
|
-
error: result.stderr.trim() || "File not found"
|
|
1814
|
-
});
|
|
1815
|
-
} else {
|
|
1816
|
-
files.push({
|
|
1817
|
-
file: filePath,
|
|
1818
|
-
content: result.stdout,
|
|
1819
|
-
found: true
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
} catch (error) {
|
|
1823
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1824
|
-
fileContentLogger.warn("Failed to fetch file content", {
|
|
1825
|
-
cid,
|
|
1826
|
-
filePath,
|
|
1827
|
-
error: message
|
|
1828
|
-
});
|
|
1829
|
-
files.push({
|
|
1830
|
-
file: filePath,
|
|
1831
|
-
content: "",
|
|
1832
|
-
found: false,
|
|
1833
|
-
error: message
|
|
1834
|
-
});
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
fileContentLogger.info("File content completed", {
|
|
1838
|
-
cid,
|
|
1839
|
-
fileCount: files.length,
|
|
1840
|
-
foundCount: files.filter((f) => f.found).length,
|
|
1841
|
-
durationMs: Date.now() - startTime
|
|
1842
|
-
});
|
|
1843
|
-
return {
|
|
1844
|
-
count: files.length,
|
|
1845
|
-
files,
|
|
1846
|
-
path
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
|
-
function executeKitUsages(options) {
|
|
1850
|
-
const cid = createCorrelationId();
|
|
1851
|
-
const startTime = Date.now();
|
|
1852
|
-
if (!isKitInstalled()) {
|
|
1853
|
-
usagesLogger.error("Kit not installed", { cid });
|
|
1854
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1855
|
-
}
|
|
1856
|
-
const { path = getDefaultKitPath(), symbolName, symbolType } = options;
|
|
1857
|
-
if (!symbolName || symbolName.trim() === "") {
|
|
1858
|
-
return new KitError("InvalidInput" /* InvalidInput */, "Symbol name is required").toJSON();
|
|
1859
|
-
}
|
|
1860
|
-
const args = ["usages", path, symbolName.trim()];
|
|
1861
|
-
if (symbolType) {
|
|
1862
|
-
args.push("--type", symbolType);
|
|
1863
|
-
}
|
|
1864
|
-
usagesLogger.info("Executing kit usages", {
|
|
1865
|
-
cid,
|
|
1866
|
-
path,
|
|
1867
|
-
symbolName,
|
|
1868
|
-
symbolType,
|
|
1869
|
-
args
|
|
1870
|
-
});
|
|
1871
|
-
try {
|
|
1872
|
-
const rawUsages = withTempJsonFileSync(`kit-usages-${cid}`, (tempFile) => {
|
|
1873
|
-
const argsWithOutput = [...args, "--output", tempFile];
|
|
1874
|
-
const result = executeKit(argsWithOutput, { timeout: USAGES_TIMEOUT });
|
|
1875
|
-
return {
|
|
1876
|
-
exitCode: result.exitCode,
|
|
1877
|
-
stderr: result.stderr
|
|
1878
|
-
};
|
|
1879
|
-
});
|
|
1880
|
-
const usages = rawUsages.map((u) => ({
|
|
1881
|
-
file: u.file,
|
|
1882
|
-
type: u.type,
|
|
1883
|
-
name: u.name,
|
|
1884
|
-
line: u.line,
|
|
1885
|
-
context: u.context
|
|
1886
|
-
}));
|
|
1887
|
-
usagesLogger.info("Usages completed", {
|
|
1888
|
-
cid,
|
|
1889
|
-
symbolName,
|
|
1890
|
-
usageCount: usages.length,
|
|
1891
|
-
durationMs: Date.now() - startTime
|
|
1892
|
-
});
|
|
1893
|
-
return {
|
|
1894
|
-
count: usages.length,
|
|
1895
|
-
usages,
|
|
1896
|
-
symbolName: symbolName.trim(),
|
|
1897
|
-
path
|
|
1898
|
-
};
|
|
1899
|
-
} catch (error) {
|
|
1900
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1901
|
-
usagesLogger.error("Usages threw exception", { cid, error: message });
|
|
1902
|
-
if (message.includes("Operation failed with exit code")) {
|
|
1903
|
-
const exitCode = Number.parseInt(message.match(/exit code (\d+)/)?.[1] || "1", 10);
|
|
1904
|
-
const stderr = message.split(": ").slice(2).join(": ") || message;
|
|
1905
|
-
return createErrorFromOutput(stderr, exitCode).toJSON();
|
|
1906
|
-
}
|
|
1907
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
function executeKitCommit(options) {
|
|
1911
|
-
const cid = createCorrelationId();
|
|
1912
|
-
const startTime = Date.now();
|
|
1913
|
-
if (!isKitInstalled()) {
|
|
1914
|
-
commitLogger.error("Kit not installed", { cid });
|
|
1915
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1916
|
-
}
|
|
1917
|
-
const { dryRun = true, model, cwd } = options;
|
|
1918
|
-
const args = ["commit"];
|
|
1919
|
-
if (dryRun) {
|
|
1920
|
-
args.push("--dry-run");
|
|
1921
|
-
}
|
|
1922
|
-
if (model) {
|
|
1923
|
-
args.push("--model", model);
|
|
1924
|
-
}
|
|
1925
|
-
commitLogger.info("Executing kit commit", {
|
|
1926
|
-
cid,
|
|
1927
|
-
dryRun,
|
|
1928
|
-
model,
|
|
1929
|
-
cwd,
|
|
1930
|
-
args
|
|
1931
|
-
});
|
|
1932
|
-
try {
|
|
1933
|
-
const result = executeKit(args, { timeout: COMMIT_TIMEOUT, cwd });
|
|
1934
|
-
if (result.exitCode !== 0) {
|
|
1935
|
-
commitLogger.error("Commit failed", {
|
|
1936
|
-
cid,
|
|
1937
|
-
exitCode: result.exitCode,
|
|
1938
|
-
stderr: result.stderr,
|
|
1939
|
-
durationMs: Date.now() - startTime
|
|
1940
|
-
});
|
|
1941
|
-
return createErrorFromOutput(result.stderr, result.exitCode).toJSON();
|
|
1942
|
-
}
|
|
1943
|
-
const output = result.stdout.trim();
|
|
1944
|
-
commitLogger.info("Commit completed", {
|
|
1945
|
-
cid,
|
|
1946
|
-
dryRun,
|
|
1947
|
-
committed: !dryRun,
|
|
1948
|
-
durationMs: Date.now() - startTime
|
|
1949
|
-
});
|
|
1950
|
-
return {
|
|
1951
|
-
message: output,
|
|
1952
|
-
committed: !dryRun,
|
|
1953
|
-
model
|
|
1954
|
-
};
|
|
1955
|
-
} catch (error) {
|
|
1956
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1957
|
-
commitLogger.error("Commit threw exception", { cid, error: message });
|
|
1958
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
async function executeAstSearch(options) {
|
|
1962
|
-
const cid = createCorrelationId();
|
|
1963
|
-
const startTime = Date.now();
|
|
1964
|
-
const { pattern, mode, filePattern, path, maxResults } = options;
|
|
1965
|
-
astLogger.info("Executing AST search", {
|
|
1966
|
-
cid,
|
|
1967
|
-
pattern,
|
|
1968
|
-
mode,
|
|
1969
|
-
filePattern,
|
|
1970
|
-
path,
|
|
1971
|
-
maxResults
|
|
1972
|
-
});
|
|
1973
|
-
try {
|
|
1974
|
-
const searcher = new ASTSearcher(path);
|
|
1975
|
-
const result = await withTimeout(searcher.searchPattern(options), AST_SEARCH_TIMEOUT, "AST search timed out");
|
|
1976
|
-
astLogger.info("AST search completed", {
|
|
1977
|
-
cid,
|
|
1978
|
-
pattern,
|
|
1979
|
-
matchCount: result.count,
|
|
1980
|
-
durationMs: Date.now() - startTime
|
|
1981
|
-
});
|
|
1982
|
-
return result;
|
|
1983
|
-
} catch (error) {
|
|
1984
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1985
|
-
const timeoutInfo = error instanceof TimeoutError ? ` after ${error.timeoutMs}ms` : "";
|
|
1986
|
-
astLogger.error("AST search failed", {
|
|
1987
|
-
cid,
|
|
1988
|
-
error: message + timeoutInfo
|
|
1989
|
-
});
|
|
1990
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
function executeKitSummarize(options) {
|
|
1994
|
-
const cid = createCorrelationId();
|
|
1995
|
-
const startTime = Date.now();
|
|
1996
|
-
if (!isKitInstalled()) {
|
|
1997
|
-
summarizeLogger.error("Kit not installed", { cid });
|
|
1998
|
-
return new KitError("KitNotInstalled" /* KitNotInstalled */).toJSON();
|
|
1999
|
-
}
|
|
2000
|
-
const {
|
|
2001
|
-
prUrl,
|
|
2002
|
-
updatePrBody = false,
|
|
2003
|
-
model,
|
|
2004
|
-
repoPath
|
|
2005
|
-
} = options;
|
|
2006
|
-
const args = ["summarize", prUrl];
|
|
2007
|
-
if (updatePrBody) {
|
|
2008
|
-
args.push("--update-pr-body");
|
|
2009
|
-
}
|
|
2010
|
-
if (model) {
|
|
2011
|
-
args.push("--model", model);
|
|
2012
|
-
}
|
|
2013
|
-
if (repoPath) {
|
|
2014
|
-
args.push("--repo-path", repoPath);
|
|
2015
|
-
}
|
|
2016
|
-
args.push("--plain");
|
|
2017
|
-
summarizeLogger.info("Executing kit summarize", {
|
|
2018
|
-
cid,
|
|
2019
|
-
prUrl,
|
|
2020
|
-
updatePrBody,
|
|
2021
|
-
model,
|
|
2022
|
-
repoPath
|
|
2023
|
-
});
|
|
2024
|
-
try {
|
|
2025
|
-
const result = executeKit(args, { timeout: SUMMARIZE_TIMEOUT });
|
|
2026
|
-
if (result.exitCode !== 0) {
|
|
2027
|
-
summarizeLogger.error("Summarize failed", {
|
|
2028
|
-
cid,
|
|
2029
|
-
exitCode: result.exitCode,
|
|
2030
|
-
stderr: result.stderr,
|
|
2031
|
-
durationMs: Date.now() - startTime
|
|
2032
|
-
});
|
|
2033
|
-
return createErrorFromOutput(result.stderr, result.exitCode).toJSON();
|
|
2034
|
-
}
|
|
2035
|
-
const summary = result.stdout.trim();
|
|
2036
|
-
if (!summary) {
|
|
2037
|
-
summarizeLogger.error("Empty summary returned", { cid });
|
|
2038
|
-
return new KitError("OutputParseError" /* OutputParseError */, "Summarize completed but returned empty output").toJSON();
|
|
2039
|
-
}
|
|
2040
|
-
summarizeLogger.info("Summarize completed", {
|
|
2041
|
-
cid,
|
|
2042
|
-
prUrl,
|
|
2043
|
-
updated: updatePrBody,
|
|
2044
|
-
summaryLength: summary.length,
|
|
2045
|
-
durationMs: Date.now() - startTime
|
|
2046
|
-
});
|
|
2047
|
-
return {
|
|
2048
|
-
prUrl,
|
|
2049
|
-
summary,
|
|
2050
|
-
updated: updatePrBody,
|
|
2051
|
-
...model && { model }
|
|
2052
|
-
};
|
|
2053
|
-
} catch (error) {
|
|
2054
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2055
|
-
summarizeLogger.error("Summarize threw exception", {
|
|
2056
|
-
cid,
|
|
2057
|
-
error: message
|
|
2058
|
-
});
|
|
2059
|
-
return new KitError("KitCommandFailed" /* KitCommandFailed */, message).toJSON();
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
function globToRegex(pattern) {
|
|
2063
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*");
|
|
2064
|
-
return new RegExp(escaped);
|
|
2065
|
-
}
|
|
2066
|
-
function getSemanticCacheDir(repoPath) {
|
|
2067
|
-
return ensureCacheDir(repoPath, "vector_db");
|
|
2068
|
-
}
|
|
2069
|
-
function isSemanticIndexBuilt(repoPath) {
|
|
2070
|
-
const cacheDir = join4(repoPath, ".kit", "vector_db");
|
|
2071
|
-
return isCachePopulated(cacheDir);
|
|
2072
|
-
}
|
|
2073
|
-
// src/lib/validators.ts
|
|
2074
|
-
import { normalizePath, pathExistsSync as pathExistsSync2, statSync } from "@side-quest/core/fs";
|
|
2075
|
-
import {
|
|
2076
|
-
validateGlob,
|
|
2077
|
-
validateInteger,
|
|
2078
|
-
validateRegex
|
|
2079
|
-
} from "@side-quest/core/validation";
|
|
2080
|
-
function validatePath(inputPath, options = {}) {
|
|
2081
|
-
const { basePath, mustBeDirectory = true, mustExist = true } = options;
|
|
2082
|
-
if (!inputPath || inputPath.trim() === "") {
|
|
2083
|
-
return { valid: false, error: "Path cannot be empty" };
|
|
2084
|
-
}
|
|
2085
|
-
const normalizedPath = normalizePath(inputPath, basePath);
|
|
2086
|
-
if (basePath) {
|
|
2087
|
-
const normalizedBase = normalizePath(basePath);
|
|
2088
|
-
if (!normalizedPath.startsWith(normalizedBase)) {
|
|
2089
|
-
return {
|
|
2090
|
-
valid: false,
|
|
2091
|
-
error: "Path traversal detected: path escapes base directory"
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
if (mustExist && !pathExistsSync2(normalizedPath)) {
|
|
2096
|
-
return { valid: false, error: `Path does not exist: ${normalizedPath}` };
|
|
2097
|
-
}
|
|
2098
|
-
if (mustExist && mustBeDirectory) {
|
|
2099
|
-
try {
|
|
2100
|
-
const stats = statSync(normalizedPath);
|
|
2101
|
-
if (!stats.isDirectory()) {
|
|
2102
|
-
return {
|
|
2103
|
-
valid: false,
|
|
2104
|
-
error: `Path is not a directory: ${normalizedPath}`
|
|
2105
|
-
};
|
|
2106
|
-
}
|
|
2107
|
-
} catch {
|
|
2108
|
-
return { valid: false, error: `Cannot access path: ${normalizedPath}` };
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
return { valid: true, path: normalizedPath };
|
|
2112
|
-
}
|
|
2113
|
-
function validatePositiveInt(value, options) {
|
|
2114
|
-
return validateInteger(value, {
|
|
2115
|
-
name: options.name,
|
|
2116
|
-
min: options.min ?? 1,
|
|
2117
|
-
max: options.max ?? 1e4,
|
|
2118
|
-
defaultValue: options.defaultValue
|
|
2119
|
-
});
|
|
2120
|
-
}
|
|
2121
|
-
function validateGrepInputs(inputs) {
|
|
2122
|
-
const errors = [];
|
|
2123
|
-
const patternResult = validateRegex(inputs.pattern);
|
|
2124
|
-
if (!patternResult.valid) {
|
|
2125
|
-
errors.push(patternResult.error);
|
|
2126
|
-
}
|
|
2127
|
-
const pathResult = validatePath(inputs.path || getDefaultKitPath());
|
|
2128
|
-
if (!pathResult.valid) {
|
|
2129
|
-
errors.push(pathResult.error);
|
|
2130
|
-
}
|
|
2131
|
-
let validatedInclude;
|
|
2132
|
-
if (inputs.include) {
|
|
2133
|
-
const includeResult = validateGlob(inputs.include);
|
|
2134
|
-
if (!includeResult.valid) {
|
|
2135
|
-
errors.push(`Include pattern: ${includeResult.error}`);
|
|
2136
|
-
} else {
|
|
2137
|
-
validatedInclude = includeResult.value;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
let validatedExclude;
|
|
2141
|
-
if (inputs.exclude) {
|
|
2142
|
-
const excludeResult = validateGlob(inputs.exclude);
|
|
2143
|
-
if (!excludeResult.valid) {
|
|
2144
|
-
errors.push(`Exclude pattern: ${excludeResult.error}`);
|
|
2145
|
-
} else {
|
|
2146
|
-
validatedExclude = excludeResult.value;
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
const maxResultsResult = validatePositiveInt(inputs.maxResults, {
|
|
2150
|
-
name: "maxResults",
|
|
2151
|
-
min: 1,
|
|
2152
|
-
max: 1000,
|
|
2153
|
-
defaultValue: 100
|
|
2154
|
-
});
|
|
2155
|
-
if (!maxResultsResult.valid) {
|
|
2156
|
-
errors.push(maxResultsResult.error);
|
|
2157
|
-
}
|
|
2158
|
-
if (errors.length > 0) {
|
|
2159
|
-
return { valid: false, errors };
|
|
2160
|
-
}
|
|
2161
|
-
return {
|
|
2162
|
-
valid: true,
|
|
2163
|
-
errors: [],
|
|
2164
|
-
validated: {
|
|
2165
|
-
pattern: patternResult.value.source,
|
|
2166
|
-
path: pathResult.path,
|
|
2167
|
-
include: validatedInclude,
|
|
2168
|
-
exclude: validatedExclude,
|
|
2169
|
-
maxResults: maxResultsResult.value
|
|
2170
|
-
}
|
|
2171
|
-
};
|
|
2172
|
-
}
|
|
2173
|
-
function validateSemanticInputs(inputs) {
|
|
2174
|
-
const errors = [];
|
|
2175
|
-
if (!inputs.query || inputs.query.trim() === "") {
|
|
2176
|
-
errors.push("Query cannot be empty");
|
|
2177
|
-
}
|
|
2178
|
-
const pathResult = validatePath(inputs.path || getDefaultKitPath());
|
|
2179
|
-
if (!pathResult.valid) {
|
|
2180
|
-
errors.push(pathResult.error);
|
|
2181
|
-
}
|
|
2182
|
-
const topKResult = validatePositiveInt(inputs.topK, {
|
|
2183
|
-
name: "topK",
|
|
2184
|
-
min: 1,
|
|
2185
|
-
max: 50,
|
|
2186
|
-
defaultValue: 5
|
|
2187
|
-
});
|
|
2188
|
-
if (!topKResult.valid) {
|
|
2189
|
-
errors.push(topKResult.error);
|
|
2190
|
-
}
|
|
2191
|
-
if (errors.length > 0) {
|
|
2192
|
-
return { valid: false, errors };
|
|
2193
|
-
}
|
|
2194
|
-
return {
|
|
2195
|
-
valid: true,
|
|
2196
|
-
errors: [],
|
|
2197
|
-
validated: {
|
|
2198
|
-
query: inputs.query.trim(),
|
|
2199
|
-
path: pathResult.path,
|
|
2200
|
-
topK: topKResult.value
|
|
2201
|
-
}
|
|
2202
|
-
};
|
|
2203
|
-
}
|
|
2204
|
-
function validateSymbolsInputs(inputs) {
|
|
2205
|
-
const errors = [];
|
|
2206
|
-
const pathResult = validatePath(inputs.path || getDefaultKitPath());
|
|
2207
|
-
if (!pathResult.valid) {
|
|
2208
|
-
errors.push(pathResult.error);
|
|
2209
|
-
}
|
|
2210
|
-
let validatedFile;
|
|
2211
|
-
if (inputs.file) {
|
|
2212
|
-
const trimmed = inputs.file.trim();
|
|
2213
|
-
if (trimmed.includes("..")) {
|
|
2214
|
-
errors.push("File path cannot contain path traversal sequences");
|
|
2215
|
-
} else {
|
|
2216
|
-
validatedFile = trimmed;
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
let validatedPattern;
|
|
2220
|
-
if (inputs.pattern) {
|
|
2221
|
-
const patternResult = validateGlob(inputs.pattern);
|
|
2222
|
-
if (!patternResult.valid) {
|
|
2223
|
-
errors.push(`File pattern: ${patternResult.error}`);
|
|
2224
|
-
} else {
|
|
2225
|
-
validatedPattern = patternResult.value;
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
let validatedSymbolType;
|
|
2229
|
-
if (inputs.symbolType) {
|
|
2230
|
-
const sanitized = inputs.symbolType.trim().toLowerCase();
|
|
2231
|
-
const validTypes = [
|
|
2232
|
-
"function",
|
|
2233
|
-
"class",
|
|
2234
|
-
"variable",
|
|
2235
|
-
"type",
|
|
2236
|
-
"interface",
|
|
2237
|
-
"method",
|
|
2238
|
-
"property",
|
|
2239
|
-
"constant"
|
|
2240
|
-
];
|
|
2241
|
-
if (sanitized && !validTypes.includes(sanitized)) {
|
|
2242
|
-
errors.push(`Invalid symbol type: ${inputs.symbolType}. Valid types: ${validTypes.join(", ")}`);
|
|
2243
|
-
} else {
|
|
2244
|
-
validatedSymbolType = sanitized || undefined;
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
if (errors.length > 0) {
|
|
2248
|
-
return { valid: false, errors };
|
|
2249
|
-
}
|
|
2250
|
-
return {
|
|
2251
|
-
valid: true,
|
|
2252
|
-
errors: [],
|
|
2253
|
-
validated: {
|
|
2254
|
-
path: pathResult.path,
|
|
2255
|
-
pattern: validatedPattern,
|
|
2256
|
-
symbolType: validatedSymbolType,
|
|
2257
|
-
file: validatedFile
|
|
2258
|
-
}
|
|
2259
|
-
};
|
|
2260
|
-
}
|
|
2261
|
-
function validateFileTreeInputs(inputs) {
|
|
2262
|
-
const errors = [];
|
|
2263
|
-
const pathResult = validatePath(inputs.path || getDefaultKitPath());
|
|
2264
|
-
if (!pathResult.valid) {
|
|
2265
|
-
errors.push(pathResult.error);
|
|
2266
|
-
}
|
|
2267
|
-
let validatedSubpath;
|
|
2268
|
-
if (inputs.subpath) {
|
|
2269
|
-
const trimmed = inputs.subpath.trim();
|
|
2270
|
-
if (trimmed.includes("..")) {
|
|
2271
|
-
errors.push("Subpath cannot contain path traversal sequences");
|
|
2272
|
-
} else {
|
|
2273
|
-
validatedSubpath = trimmed;
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
if (errors.length > 0) {
|
|
2277
|
-
return { valid: false, errors };
|
|
2278
|
-
}
|
|
2279
|
-
return {
|
|
2280
|
-
valid: true,
|
|
2281
|
-
errors: [],
|
|
2282
|
-
validated: {
|
|
2283
|
-
path: pathResult.path,
|
|
2284
|
-
subpath: validatedSubpath
|
|
2285
|
-
}
|
|
2286
|
-
};
|
|
2287
|
-
}
|
|
2288
|
-
function validateFileContentInputs(inputs) {
|
|
2289
|
-
const errors = [];
|
|
2290
|
-
const pathResult = validatePath(inputs.path || getDefaultKitPath());
|
|
2291
|
-
if (!pathResult.valid) {
|
|
2292
|
-
errors.push(pathResult.error);
|
|
2293
|
-
}
|
|
2294
|
-
if (!inputs.filePaths || inputs.filePaths.length === 0) {
|
|
2295
|
-
errors.push("At least one file path is required");
|
|
2296
|
-
} else {
|
|
2297
|
-
const validatedPaths = [];
|
|
2298
|
-
for (const filePath of inputs.filePaths) {
|
|
2299
|
-
const trimmed = filePath.trim();
|
|
2300
|
-
if (!trimmed) {
|
|
2301
|
-
errors.push("File paths cannot be empty");
|
|
2302
|
-
} else if (trimmed.includes("..")) {
|
|
2303
|
-
errors.push(`File path "${trimmed}" cannot contain path traversal sequences`);
|
|
2304
|
-
} else {
|
|
2305
|
-
validatedPaths.push(trimmed);
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
if (validatedPaths.length > 20) {
|
|
2309
|
-
errors.push("Cannot request more than 20 files at once");
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
if (errors.length > 0) {
|
|
2313
|
-
return { valid: false, errors };
|
|
2314
|
-
}
|
|
2315
|
-
return {
|
|
2316
|
-
valid: true,
|
|
2317
|
-
errors: [],
|
|
2318
|
-
validated: {
|
|
2319
|
-
path: pathResult.path,
|
|
2320
|
-
filePaths: inputs.filePaths.map((p) => p.trim())
|
|
2321
|
-
}
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2324
|
-
function validateUsagesInputs(inputs) {
|
|
2325
|
-
const errors = [];
|
|
2326
|
-
const pathResult = validatePath(inputs.path || getDefaultKitPath());
|
|
2327
|
-
if (!pathResult.valid) {
|
|
2328
|
-
errors.push(pathResult.error);
|
|
2329
|
-
}
|
|
2330
|
-
if (!inputs.symbolName || inputs.symbolName.trim() === "") {
|
|
2331
|
-
errors.push("Symbol name is required");
|
|
2332
|
-
}
|
|
2333
|
-
let validatedSymbolType;
|
|
2334
|
-
if (inputs.symbolType) {
|
|
2335
|
-
const sanitized = inputs.symbolType.trim().toLowerCase();
|
|
2336
|
-
const validTypes = [
|
|
2337
|
-
"function",
|
|
2338
|
-
"class",
|
|
2339
|
-
"variable",
|
|
2340
|
-
"type",
|
|
2341
|
-
"interface",
|
|
2342
|
-
"method",
|
|
2343
|
-
"property",
|
|
2344
|
-
"constant"
|
|
2345
|
-
];
|
|
2346
|
-
if (sanitized && !validTypes.includes(sanitized)) {
|
|
2347
|
-
errors.push(`Invalid symbol type: ${inputs.symbolType}. Valid types: ${validTypes.join(", ")}`);
|
|
2348
|
-
} else {
|
|
2349
|
-
validatedSymbolType = sanitized || undefined;
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
if (errors.length > 0) {
|
|
2353
|
-
return { valid: false, errors };
|
|
2354
|
-
}
|
|
2355
|
-
return {
|
|
2356
|
-
valid: true,
|
|
2357
|
-
errors: [],
|
|
2358
|
-
validated: {
|
|
2359
|
-
path: pathResult.path,
|
|
2360
|
-
symbolName: inputs.symbolName.trim(),
|
|
2361
|
-
symbolType: validatedSymbolType
|
|
2362
|
-
}
|
|
2363
|
-
};
|
|
2364
|
-
}
|
|
2365
|
-
function validateAstSearchInputs(inputs) {
|
|
2366
|
-
const errors = [];
|
|
2367
|
-
if (!inputs.pattern || inputs.pattern.trim() === "") {
|
|
2368
|
-
errors.push("Pattern cannot be empty");
|
|
2369
|
-
}
|
|
2370
|
-
const validModes = ["simple", "pattern"];
|
|
2371
|
-
const mode = (inputs.mode || "simple").toLowerCase();
|
|
2372
|
-
if (!validModes.includes(mode)) {
|
|
2373
|
-
errors.push(`Invalid mode: ${inputs.mode}. Valid modes: ${validModes.join(", ")}`);
|
|
2374
|
-
}
|
|
2375
|
-
if (mode === "pattern" && inputs.pattern) {
|
|
2376
|
-
try {
|
|
2377
|
-
JSON.parse(inputs.pattern);
|
|
2378
|
-
} catch {}
|
|
2379
|
-
}
|
|
2380
|
-
let validatedFilePattern;
|
|
2381
|
-
if (inputs.filePattern) {
|
|
2382
|
-
const patternResult = validateGlob(inputs.filePattern);
|
|
2383
|
-
if (!patternResult.valid) {
|
|
2384
|
-
errors.push(`File pattern: ${patternResult.error}`);
|
|
2385
|
-
} else {
|
|
2386
|
-
validatedFilePattern = patternResult.value;
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
const pathResult = validatePath(inputs.path || getDefaultKitPath());
|
|
2390
|
-
if (!pathResult.valid) {
|
|
2391
|
-
errors.push(pathResult.error);
|
|
2392
|
-
}
|
|
2393
|
-
const maxResultsResult = validatePositiveInt(inputs.maxResults, {
|
|
2394
|
-
name: "maxResults",
|
|
2395
|
-
min: 1,
|
|
2396
|
-
max: 500,
|
|
2397
|
-
defaultValue: 100
|
|
2398
|
-
});
|
|
2399
|
-
if (!maxResultsResult.valid) {
|
|
2400
|
-
errors.push(maxResultsResult.error);
|
|
2401
|
-
}
|
|
2402
|
-
if (errors.length > 0) {
|
|
2403
|
-
return { valid: false, errors };
|
|
2404
|
-
}
|
|
2405
|
-
return {
|
|
2406
|
-
valid: true,
|
|
2407
|
-
errors: [],
|
|
2408
|
-
validated: {
|
|
2409
|
-
pattern: inputs.pattern.trim(),
|
|
2410
|
-
mode,
|
|
2411
|
-
filePattern: validatedFilePattern,
|
|
2412
|
-
path: pathResult.path,
|
|
2413
|
-
maxResults: maxResultsResult.value
|
|
2414
|
-
}
|
|
2415
|
-
};
|
|
2416
|
-
}
|
|
2417
|
-
export {
|
|
2418
|
-
validateUsagesInputs,
|
|
2419
|
-
validateSymbolsInputs,
|
|
2420
|
-
validateSemanticInputs,
|
|
2421
|
-
validatePositiveInt,
|
|
2422
|
-
validatePath,
|
|
2423
|
-
validateGrepInputs,
|
|
2424
|
-
validateFileTreeInputs,
|
|
2425
|
-
validateFileContentInputs,
|
|
2426
|
-
validateAstSearchInputs,
|
|
2427
|
-
usagesLogger,
|
|
2428
|
-
symbolsLogger,
|
|
2429
|
-
semanticLogger,
|
|
2430
|
-
logger,
|
|
2431
|
-
logFile,
|
|
2432
|
-
logDir,
|
|
2433
|
-
isTimeoutError,
|
|
2434
|
-
isSupported,
|
|
2435
|
-
isSemanticUnavailableError,
|
|
2436
|
-
isSemanticIndexBuilt,
|
|
2437
|
-
isKitInstalled,
|
|
2438
|
-
isError,
|
|
2439
|
-
initParser,
|
|
2440
|
-
initLogger,
|
|
2441
|
-
grepLogger,
|
|
2442
|
-
getUsagesLogger,
|
|
2443
|
-
getSymbolsLogger,
|
|
2444
|
-
getSupportedGlob,
|
|
2445
|
-
getSemanticLogger,
|
|
2446
|
-
getSemanticCacheDir,
|
|
2447
|
-
getParser,
|
|
2448
|
-
getKitVersion,
|
|
2449
|
-
getKitLogger,
|
|
2450
|
-
getGrepLogger,
|
|
2451
|
-
getFileTreeLogger,
|
|
2452
|
-
getFileContentLogger,
|
|
2453
|
-
getDefaultKitPath,
|
|
2454
|
-
getAstLogger,
|
|
2455
|
-
formatUsagesResults,
|
|
2456
|
-
formatSymbolsResults,
|
|
2457
|
-
formatSemanticResults,
|
|
2458
|
-
formatIndexStatsResults,
|
|
2459
|
-
formatIndexPrimeResults,
|
|
2460
|
-
formatIndexOverviewResults,
|
|
2461
|
-
formatIndexFindResults,
|
|
2462
|
-
formatGrepResults,
|
|
2463
|
-
formatFileTreeResults,
|
|
2464
|
-
formatFileContentResults,
|
|
2465
|
-
formatError,
|
|
2466
|
-
formatAstSearchResults,
|
|
2467
|
-
fileTreeLogger,
|
|
2468
|
-
fileContentLogger,
|
|
2469
|
-
executeKitUsages,
|
|
2470
|
-
executeKitSymbols,
|
|
2471
|
-
executeKitSummarize,
|
|
2472
|
-
executeKitSemantic,
|
|
2473
|
-
executeKitGrep,
|
|
2474
|
-
executeKitFileTree,
|
|
2475
|
-
executeKitFileContent,
|
|
2476
|
-
executeKitCommit,
|
|
2477
|
-
executeIndexStats,
|
|
2478
|
-
executeIndexPrime,
|
|
2479
|
-
executeIndexOverview,
|
|
2480
|
-
executeIndexFind,
|
|
2481
|
-
executeAstSearch,
|
|
2482
|
-
detectLanguage,
|
|
2483
|
-
detectErrorType,
|
|
2484
|
-
createErrorFromOutput,
|
|
2485
|
-
createCorrelationId,
|
|
2486
|
-
astLogger,
|
|
2487
|
-
USAGES_TIMEOUT,
|
|
2488
|
-
SearchMode,
|
|
2489
|
-
SYMBOLS_TIMEOUT,
|
|
2490
|
-
SUPPORTED_LANGUAGES,
|
|
2491
|
-
SUMMARIZE_TIMEOUT,
|
|
2492
|
-
SEMANTIC_TIMEOUT,
|
|
2493
|
-
SEMANTIC_INSTALL_HINT,
|
|
2494
|
-
ResponseFormat,
|
|
2495
|
-
LANGUAGES,
|
|
2496
|
-
KitErrorType,
|
|
2497
|
-
KitError,
|
|
2498
|
-
KIT_DEFAULT_PATH_ENV,
|
|
2499
|
-
GREP_TIMEOUT,
|
|
2500
|
-
FILE_TREE_TIMEOUT,
|
|
2501
|
-
FILE_CONTENT_TIMEOUT,
|
|
2502
|
-
ERROR_MESSAGES,
|
|
2503
|
-
DEFAULT_TOP_K,
|
|
2504
|
-
DEFAULT_MAX_RESULTS,
|
|
2505
|
-
COMMIT_TIMEOUT,
|
|
2506
|
-
AST_SEARCH_TIMEOUT,
|
|
2507
|
-
ASTSearcher,
|
|
2508
|
-
ASTPattern
|
|
2509
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @side-quest/kit
|
|
3
|
+
*
|
|
4
|
+
* Kit CLI integration for intelligent code search - MCP server with 7 tools
|
|
5
|
+
* for token-efficient codebase navigation.
|
|
6
|
+
*/
|
|
7
|
+
export * from './lib/index.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|