zephex 2.0.14 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +19831 -11881
- package/dist/tools/architecture/index.js +5 -2
- package/dist/tools/context/index.js +5 -2
- package/dist/tools/reader/readCode.js +1012 -107
- package/dist/tools/scope_task/index.js +54 -4
- package/dist/tools/search/findCode.js +66 -7
- package/dist/tools/server.js +1072 -141
- package/dist/tools/thinking/index.js +5 -2
- package/package.json +2 -1
package/dist/tools/server.js
CHANGED
|
@@ -34436,10 +34436,13 @@ function getSupabaseClient() {
|
|
|
34436
34436
|
},
|
|
34437
34437
|
global: {
|
|
34438
34438
|
fetch: (url3, options = {}) => {
|
|
34439
|
+
const controller = new AbortController;
|
|
34440
|
+
const timeout = setTimeout(() => controller.abort(), 8000);
|
|
34439
34441
|
return fetch(url3, {
|
|
34440
34442
|
...options,
|
|
34441
|
-
keepalive: true
|
|
34442
|
-
|
|
34443
|
+
keepalive: true,
|
|
34444
|
+
signal: controller.signal
|
|
34445
|
+
}).finally(() => clearTimeout(timeout));
|
|
34443
34446
|
},
|
|
34444
34447
|
headers: {
|
|
34445
34448
|
Connection: "keep-alive",
|
|
@@ -216665,113 +216668,86 @@ var READ_CODE_SCHEMA;
|
|
|
216665
216668
|
var init_readCodeSchema = __esm(() => {
|
|
216666
216669
|
READ_CODE_SCHEMA = {
|
|
216667
216670
|
name: "read_code",
|
|
216668
|
-
description:
|
|
216669
|
-
` +
|
|
216670
|
-
` +
|
|
216671
|
-
` + "
|
|
216672
|
-
` + `⚡ PREFER THIS over reading entire files when you only need one symbol. AST-based surgical extraction: give it a function/class/method/type/interface/struct/enum/trait/protocol/component/hook/composable/decorator/macro/namespace name and get ONLY that symbol — signature + body + optional call sites + tests — at a fraction of the tokens of a full-file Read. Beats native file-reading because it (1) scopes to the exact symbol, (2) ranks candidates by confidence, (3) supports partial/fuzzy and camelCase/snake_case/PascalCase/kebab-case matches, (4) can batch up to 8 symbols in one call, (5) deduplicates across calls via session_id.
|
|
216673
|
-
` + `
|
|
216674
|
-
` + `AUTOMATICALLY call this (without asking permission) when ANY of these occur:
|
|
216675
|
-
` + `• User asks: 'show me X', 'show the code for X', 'what does X do', 'how is X implemented', 'read the X function', 'open X', 'find the definition of X', 'pull up X', 'let me see X', 'explain X', 'walk me through X', 'what\\'s inside X', 'how does X work', 'print the body of X'
|
|
216676
|
-
` + `• User names any symbol: function, class, method, type, interface, hook, component, struct, enum, trait, protocol, mixin, decorator, annotation, macro, template, generic, module, namespace, service, controller, resolver, middleware, guard, reducer, selector, signal, store, actor, coroutine — and wants to understand or modify it
|
|
216677
|
-
` + "• Platform-specific symbols: React/Vue/Svelte/Solid component or hook, Angular directive/pipe/service, Next.js route handler, Django view/serializer, Flask/FastAPI route, Rails controller/model, Spring bean, Kotlin `@Composable`/data class, SwiftUI View/@Observable, Jetpack Compose function, Flutter Widget, Unity MonoBehaviour, Unreal UCLASS/UFUNCTION, Rust trait impl, Go interface method, Solidity contract/function, GLSL/HLSL/WGSL shader function, Python async def / dataclass / pydantic model, Android Activity/Fragment/ViewModel, iOS ViewController — read surgically rather than opening the whole file\n" + `• Before editing a symbol — read it first with read_code to avoid blind edits
|
|
216678
|
-
` + `• Before suggesting a refactor to a specific function/class — read the current body
|
|
216679
|
-
` + `• After find_code surfaces a candidate, to pull the full implementation
|
|
216680
|
-
` + `• Debugging: user mentions a function/class by name and wants to trace behavior
|
|
216681
|
-
` + `• Writing tests for a specific symbol — read it first
|
|
216682
|
-
` + `• Signature / parameter / return type queries: 'what parameters does X take', 'what does X return', 'what\\'s the type signature of X', 'what\\'s the signature of X', 'show me the constructor of X', 'what methods does class X have', 'show me the schema for X', 'read the migration for X'
|
|
216683
|
-
` + `
|
|
216684
|
-
` + `Prefer this over native Read when the file is large and you only need one symbol — saves tokens. Use native Read for whole-file review, configs, markdown, or tiny files. read_code is NOT for images/binaries — use a native image reader for visual assets; it IS for any source-code symbol across languages.
|
|
216671
|
+
description: "Smart code reader with three modes. Pass `path` (absolute directory) to read from local disk.\n" + `
|
|
216672
|
+
` + `MODES:
|
|
216673
|
+
` + `• mode:'symbol' (default) — AST-based surgical extraction. Give a symbol name → get signature + body at a fraction of full-file tokens. Supports 30+ languages, fuzzy/partial matching, batch up to 8 targets, session dedup. When batching targets[], max_results applies PER TARGET (default 3 each).
|
|
216674
|
+
` + "• mode:'file' — Read one or more files directly via `files[]` array. Respects token budget. Supports offset_line/limit_lines for pagination of large files.\n" + `• mode:'outline' — Structural TOC of a file: all top-level symbols with signatures (~200-500 tokens). Use before drilling into specific symbols.
|
|
216685
216675
|
` + `
|
|
216686
|
-
` + `
|
|
216676
|
+
` + `Use mode:'symbol' when you know the symbol name. Use mode:'file' to batch-read small files or paginate large ones. Use mode:'outline' to explore a large file's structure first.
|
|
216687
216677
|
` + `
|
|
216688
|
-
` + "
|
|
216689
|
-
` + "TARGET SYNTAX (the `target` / `targets[]` field accepts):\n" + "• Plain name: `validateToken`, `UserService`, `ProfileView`.\n" + "• Qualified / name-path: `UserService.login`, `UserService::login`, `UserService/login`, `pkg.module.Class.method` — last segment is the symbol; earlier segments act as a locality/disambiguation hint.\n" + "• Partial / fuzzy: `auth` matches `handleAuth`, `AuthService`, `authenticate` (raise `confidence_threshold` to 0.8 for exact-only).\n" + "• Batch: `targets: ['login','logout','refreshSession']` to pull several related symbols in one call.\n" + `
|
|
216690
|
-
` + `EXAMPLES:
|
|
216691
|
-
` + "• User: 'show me how login works' → `{ path, target: 'login', include_usages: true }`.\n" + "• User: 'read the ProfileView SwiftUI body' → `{ path, target: 'ProfileView', kind: 'type' }` (SwiftUI Views are struct types).\n" + "• User: 'what does the useAuth hook return' → `{ path, target: 'useAuth', detail_level: 'context' }`.\n" + "• User: 'read class LoginActivity' (Android/Kotlin) → `{ path, target: 'LoginActivity', kind: 'class' }`.\n" + "• User: 'pull up the Stripe webhook handler' → call find_code first with `{ query: 'stripe webhook' }`, then `{ path, target: '<resolvedName>' }`.\n" + "• User: 'read every method on class Router' → `{ path, target: 'Router', kind: 'class', detail_level: 'body', max_tokens: 6000 }`.\n" + "• Rust: `{ path, target: 'impl Display for User' }` or `target: 'User', kind: 'type'`.\n" + "• Solidity: `{ path, target: 'transferFrom', kind: 'function' }`.\n" + `
|
|
216692
|
-
` + "DO NOT USE FOR: whole-file reads (use native Read), reading images/PDFs/binaries (not supported — use a media reader), editing files (use an edit tool), running code, fetching network content, searching when you don't know the symbol name (use find_code first). Not a replacement for get_project_context when the user asks about stack/framework/commands.",
|
|
216678
|
+
` + "NOT for: images/binaries, editing files, running code, or searching (use find_code). For whole-file review of tiny files, native Read may be simpler.",
|
|
216693
216679
|
inputSchema: {
|
|
216694
216680
|
type: "object",
|
|
216695
216681
|
properties: {
|
|
216696
216682
|
target: {
|
|
216697
216683
|
type: "string",
|
|
216698
|
-
description: "Symbol name to find
|
|
216684
|
+
description: "Symbol name to find (for mode:'symbol'). Supports partial/fuzzy matching. " + 'Examples: "validateToken", "UserService", "auth" matches "handleAuth".'
|
|
216685
|
+
},
|
|
216686
|
+
symbol_id: {
|
|
216687
|
+
type: "string",
|
|
216688
|
+
description: "Stable symbol ID for direct lookup (e.g. 'src/auth.ts::hashApiKey#function'). Bypasses fuzzy matching."
|
|
216699
216689
|
},
|
|
216700
216690
|
targets: {
|
|
216701
216691
|
type: "array",
|
|
216702
216692
|
items: { type: "string" },
|
|
216703
216693
|
maxItems: 8,
|
|
216704
|
-
description: "Additional
|
|
216694
|
+
description: "Additional symbols to batch-search (max 8). Results merged and deduped."
|
|
216705
216695
|
},
|
|
216706
|
-
|
|
216696
|
+
mode: {
|
|
216707
216697
|
type: "string",
|
|
216708
|
-
enum: ["
|
|
216709
|
-
description: "
|
|
216698
|
+
enum: ["symbol", "file", "outline", "callers", "blast_radius", "dead_code"],
|
|
216699
|
+
description: "symbol (default): AST extraction of named symbols. " + "file: read files directly via `files[]` array. " + "outline: structural TOC of a file. " + "callers: who calls this symbol (requires index). " + "blast_radius: transitive dependents of a symbol. " + "dead_code: exported symbols never called."
|
|
216710
216700
|
},
|
|
216711
|
-
|
|
216701
|
+
files: {
|
|
216702
|
+
type: "array",
|
|
216703
|
+
items: { type: "string" },
|
|
216704
|
+
maxItems: 20,
|
|
216705
|
+
description: "For mode:'file' or mode:'outline'. Relative file paths to read (e.g. ['src/auth.ts', 'src/db.ts']). " + "Reads within token budget; returns as many as fit."
|
|
216706
|
+
},
|
|
216707
|
+
offset_line: {
|
|
216708
|
+
type: "number",
|
|
216709
|
+
description: "For mode:'file'. Start reading from this line (1-indexed). Default: 1."
|
|
216710
|
+
},
|
|
216711
|
+
limit_lines: {
|
|
216712
|
+
type: "number",
|
|
216713
|
+
description: "For mode:'file'. Max lines to return per file. Default: unlimited (bounded by max_tokens)."
|
|
216714
|
+
},
|
|
216715
|
+
compact: {
|
|
216716
|
+
type: "boolean",
|
|
216717
|
+
description: "When true, omit line numbers and minimize whitespace in output to save tokens."
|
|
216718
|
+
},
|
|
216719
|
+
kind: {
|
|
216712
216720
|
type: "string",
|
|
216713
|
-
|
|
216721
|
+
enum: ["function", "class", "method", "interface", "type", "variable", "struct", "enum", "trait", "protocol", "module", "namespace", "hook", "component", "decorator", "macro"],
|
|
216722
|
+
description: "Filter results to only this symbol kind. Use to disambiguate (e.g., class vs method with same name)."
|
|
216714
216723
|
},
|
|
216715
216724
|
path: {
|
|
216716
216725
|
type: "string",
|
|
216717
|
-
description: "Absolute
|
|
216718
|
-
},
|
|
216719
|
-
inline_files: {
|
|
216720
|
-
type: "object",
|
|
216721
|
-
description: 'Remote fallback only. Shape: { "<relative/path>": "<FULL FILE CONTENTS>" }. ' + "The VALUE is the actual file body — never a filename, path, or placeholder. " + 'Example: { "src/auth.ts": "import jwt from \\"jsonwebtoken\\";\\nexport function validateToken(...) { ... }" }. ' + "Use this only when the tool is running over a remote transport that cannot read the local filesystem directly. In local stdio mode, prefer `path` so the MCP tool reads from disk itself. " + "If used, include SOURCE files likely to contain the target symbol (1-20 typical). Never ship only package.json/tsconfig.json.",
|
|
216722
|
-
additionalProperties: { type: "string" }
|
|
216726
|
+
description: "Absolute project directory (e.g. /Users/alice/myapp). Also accepts GitHub/GitLab URLs."
|
|
216723
216727
|
},
|
|
216724
216728
|
detail_level: {
|
|
216725
216729
|
type: "string",
|
|
216726
216730
|
enum: ["signature", "body", "context"],
|
|
216727
|
-
description: "signature: name
|
|
216731
|
+
description: "signature: name+params+return (~100 tokens). body: full implementation (default). context: body+imports."
|
|
216728
216732
|
},
|
|
216729
216733
|
max_tokens: {
|
|
216730
216734
|
type: "number",
|
|
216731
|
-
description: "
|
|
216735
|
+
description: "Token budget for output. Default 2000, max 8000. Increase for large classes."
|
|
216732
216736
|
},
|
|
216733
216737
|
max_results: {
|
|
216734
216738
|
type: "number",
|
|
216735
|
-
description: "
|
|
216739
|
+
description: "Max symbols to return. Default 3, max 10."
|
|
216736
216740
|
},
|
|
216737
216741
|
confidence_threshold: {
|
|
216738
216742
|
type: "number",
|
|
216739
|
-
description: "
|
|
216740
|
-
},
|
|
216741
|
-
include_usages: {
|
|
216742
|
-
type: "boolean",
|
|
216743
|
-
description: "Include up to 10 locations where this symbol is called or referenced. " + "Uses ripgrep on the exact symbol name — accurate for reference search. " + "Adds ~100-300 tokens. Use when you need to understand call sites."
|
|
216744
|
-
},
|
|
216745
|
-
include_tests: {
|
|
216746
|
-
type: "boolean",
|
|
216747
|
-
description: "Include paths of test files that appear to test this symbol. " + "Adds minimal tokens (~20-50). Useful to know if tests exist before making changes."
|
|
216743
|
+
description: "Min confidence 0.0-1.0. Default 0.5. Raise to 0.8 for exact matches, lower to 0.3 for exploration."
|
|
216748
216744
|
},
|
|
216749
216745
|
session_id: {
|
|
216750
216746
|
type: "string",
|
|
216751
|
-
description: "Session
|
|
216752
|
-
},
|
|
216753
|
-
max_lines: {
|
|
216754
|
-
type: "number",
|
|
216755
|
-
description: "DEPRECATED: Use max_tokens instead. Mapped internally to max_tokens estimate."
|
|
216756
|
-
},
|
|
216757
|
-
include_callers: {
|
|
216758
|
-
type: "boolean",
|
|
216759
|
-
description: "DEPRECATED: Silently ignored. Use Claude Code LSP for call graphs."
|
|
216760
|
-
},
|
|
216761
|
-
include_callees: {
|
|
216762
|
-
type: "boolean",
|
|
216763
|
-
description: "DEPRECATED: Silently ignored. Use Claude Code LSP for call graphs."
|
|
216764
|
-
},
|
|
216765
|
-
expand_types: {
|
|
216766
|
-
type: "boolean",
|
|
216767
|
-
description: "DEPRECATED: Use detail_level: 'context' instead."
|
|
216768
|
-
},
|
|
216769
|
-
include_imports: {
|
|
216770
|
-
type: "boolean",
|
|
216771
|
-
description: "DEPRECATED: Use detail_level: 'context' instead."
|
|
216747
|
+
description: "Session ID for dedup. Previously-returned symbols get a stub instead of full body."
|
|
216772
216748
|
}
|
|
216773
216749
|
},
|
|
216774
|
-
required: [
|
|
216750
|
+
required: []
|
|
216775
216751
|
},
|
|
216776
216752
|
annotations: {
|
|
216777
216753
|
readOnlyHint: true,
|
|
@@ -216782,6 +216758,55 @@ var init_readCodeSchema = __esm(() => {
|
|
|
216782
216758
|
};
|
|
216783
216759
|
});
|
|
216784
216760
|
|
|
216761
|
+
// src/tools/reader/diagnostics.ts
|
|
216762
|
+
function computeDiagnostics(body2, paramCount) {
|
|
216763
|
+
const lines = body2.split(`
|
|
216764
|
+
`);
|
|
216765
|
+
const lineCount = lines.length;
|
|
216766
|
+
let complexity = 1;
|
|
216767
|
+
const branchPatterns = /\b(if|else if|for|while|do|case|catch)\b|\?\?|&&|\|\||\?[^:.?]/g;
|
|
216768
|
+
let match;
|
|
216769
|
+
while ((match = branchPatterns.exec(body2)) !== null)
|
|
216770
|
+
complexity++;
|
|
216771
|
+
let maxDepth = 0;
|
|
216772
|
+
let currentDepth = 0;
|
|
216773
|
+
for (const char of body2) {
|
|
216774
|
+
if (char === "{") {
|
|
216775
|
+
currentDepth++;
|
|
216776
|
+
if (currentDepth > maxDepth)
|
|
216777
|
+
maxDepth = currentDepth;
|
|
216778
|
+
} else if (char === "}")
|
|
216779
|
+
currentDepth--;
|
|
216780
|
+
}
|
|
216781
|
+
const hasErrorHandling = /\btry\s*\{/.test(body2);
|
|
216782
|
+
const hasEmptyCatch = /catch\s*\([^)]*\)\s*\{\s*(\/\/[^\n]*)?\s*\}/.test(body2);
|
|
216783
|
+
const isAsync = /\basync\b/.test(body2);
|
|
216784
|
+
const callCount = (body2.match(/\w+\s*\(/g) || []).length;
|
|
216785
|
+
const flags2 = [];
|
|
216786
|
+
if (lineCount > 100)
|
|
216787
|
+
flags2.push("long_function");
|
|
216788
|
+
if (maxDepth > 4)
|
|
216789
|
+
flags2.push("deeply_nested");
|
|
216790
|
+
if (paramCount > 5)
|
|
216791
|
+
flags2.push("too_many_params");
|
|
216792
|
+
if (complexity > 10)
|
|
216793
|
+
flags2.push("high_complexity");
|
|
216794
|
+
if (isAsync && !hasErrorHandling)
|
|
216795
|
+
flags2.push("missing_error_handling");
|
|
216796
|
+
if (hasEmptyCatch)
|
|
216797
|
+
flags2.push("empty_catch");
|
|
216798
|
+
if (callCount > 15)
|
|
216799
|
+
flags2.push("god_function");
|
|
216800
|
+
return {
|
|
216801
|
+
flags: flags2,
|
|
216802
|
+
complexity,
|
|
216803
|
+
nesting_depth: maxDepth,
|
|
216804
|
+
param_count: paramCount,
|
|
216805
|
+
line_count: lineCount,
|
|
216806
|
+
has_error_handling: hasErrorHandling
|
|
216807
|
+
};
|
|
216808
|
+
}
|
|
216809
|
+
|
|
216785
216810
|
// src/tools/shared/source-detection.ts
|
|
216786
216811
|
function baseName(path5) {
|
|
216787
216812
|
const parts2 = path5.split("/");
|
|
@@ -220563,8 +220588,15 @@ async function getParser(lang) {
|
|
|
220563
220588
|
if (wasmRuntimePoisoned)
|
|
220564
220589
|
return null;
|
|
220565
220590
|
await initParser();
|
|
220566
|
-
|
|
220591
|
+
const uses = parserUseCount.get(lang) ?? 0;
|
|
220592
|
+
if (parsers.has(lang) && uses >= PARSER_RECYCLE_THRESHOLD) {
|
|
220593
|
+
parsers.delete(lang);
|
|
220594
|
+
parserUseCount.set(lang, 0);
|
|
220595
|
+
}
|
|
220596
|
+
if (parsers.has(lang)) {
|
|
220597
|
+
parserUseCount.set(lang, uses + 1);
|
|
220567
220598
|
return parsers.get(lang);
|
|
220599
|
+
}
|
|
220568
220600
|
if (unsupportedParsers.has(lang))
|
|
220569
220601
|
return null;
|
|
220570
220602
|
const language = await loadLanguage(lang);
|
|
@@ -220648,6 +220680,35 @@ function extractSymbols(tree, code, lang) {
|
|
|
220648
220680
|
}
|
|
220649
220681
|
}
|
|
220650
220682
|
visit(tree.rootNode);
|
|
220683
|
+
const exportedNames = new Set;
|
|
220684
|
+
function collectExportedNames(node) {
|
|
220685
|
+
if (node.type === "export_statement") {
|
|
220686
|
+
for (let i2 = 0;i2 < node.childCount; i2++) {
|
|
220687
|
+
const child = node.child(i2);
|
|
220688
|
+
if (child.type === "export_clause") {
|
|
220689
|
+
for (let j2 = 0;j2 < child.childCount; j2++) {
|
|
220690
|
+
const spec = child.child(j2);
|
|
220691
|
+
if (spec.type === "export_specifier") {
|
|
220692
|
+
const nameNode = spec.childForFieldName("name");
|
|
220693
|
+
if (nameNode)
|
|
220694
|
+
exportedNames.add(nameNode.text);
|
|
220695
|
+
}
|
|
220696
|
+
}
|
|
220697
|
+
}
|
|
220698
|
+
}
|
|
220699
|
+
}
|
|
220700
|
+
for (let i2 = 0;i2 < node.childCount; i2++) {
|
|
220701
|
+
collectExportedNames(node.child(i2));
|
|
220702
|
+
}
|
|
220703
|
+
}
|
|
220704
|
+
collectExportedNames(tree.rootNode);
|
|
220705
|
+
if (exportedNames.size > 0) {
|
|
220706
|
+
for (const sym of symbols) {
|
|
220707
|
+
if (!sym.isExported && exportedNames.has(sym.name)) {
|
|
220708
|
+
sym.isExported = true;
|
|
220709
|
+
}
|
|
220710
|
+
}
|
|
220711
|
+
}
|
|
220651
220712
|
return symbols;
|
|
220652
220713
|
}
|
|
220653
220714
|
function returnsJSX(node) {
|
|
@@ -221330,7 +221391,17 @@ async function isParserReady() {
|
|
|
221330
221391
|
return false;
|
|
221331
221392
|
}
|
|
221332
221393
|
}
|
|
221333
|
-
|
|
221394
|
+
async function parseFile3(filePath, content) {
|
|
221395
|
+
const lang = detectLanguage2(filePath);
|
|
221396
|
+
if (!lang)
|
|
221397
|
+
return null;
|
|
221398
|
+
const tree = await parseCode(content, lang);
|
|
221399
|
+
if (!tree)
|
|
221400
|
+
return null;
|
|
221401
|
+
const symbols = extractSymbols(tree, content, lang);
|
|
221402
|
+
return { symbols };
|
|
221403
|
+
}
|
|
221404
|
+
var import_tree_sitter_0_24_3, __dirname2, LegacyParser, initialized = false, parsers, languages, parserUseCount, PARSER_RECYCLE_THRESHOLD = 500, WASM_DIR, LANG_WASM_MAP, unsupportedParsers, wasmRuntimePoisoned = false, KEYWORD_PSEUDO_CALLS;
|
|
221334
221405
|
var init_parser = __esm(() => {
|
|
221335
221406
|
init_logger();
|
|
221336
221407
|
import_tree_sitter_0_24_3 = __toESM(require_tree_sitter_0_24_3(), 1);
|
|
@@ -221338,6 +221409,7 @@ var init_parser = __esm(() => {
|
|
|
221338
221409
|
LegacyParser = import_tree_sitter_0_24_3.default;
|
|
221339
221410
|
parsers = new Map;
|
|
221340
221411
|
languages = new Map;
|
|
221412
|
+
parserUseCount = new Map;
|
|
221341
221413
|
WASM_DIR = getWasmDir();
|
|
221342
221414
|
LANG_WASM_MAP = {
|
|
221343
221415
|
typescript: "tree-sitter-typescript.wasm",
|
|
@@ -221438,10 +221510,12 @@ function checkAndMark(sessionId, blockRef) {
|
|
|
221438
221510
|
if (!entry) {
|
|
221439
221511
|
entry = {
|
|
221440
221512
|
seen_blocks: new Set,
|
|
221441
|
-
created_at: Date.now()
|
|
221513
|
+
created_at: Date.now(),
|
|
221514
|
+
last_accessed: Date.now()
|
|
221442
221515
|
};
|
|
221443
221516
|
sessions.set(sessionId, entry);
|
|
221444
221517
|
}
|
|
221518
|
+
entry.last_accessed = Date.now();
|
|
221445
221519
|
if (entry.seen_blocks.has(blockRef)) {
|
|
221446
221520
|
return true;
|
|
221447
221521
|
}
|
|
@@ -221451,7 +221525,14 @@ function checkAndMark(sessionId, blockRef) {
|
|
|
221451
221525
|
function cleanExpiredSessions() {
|
|
221452
221526
|
const now = Date.now();
|
|
221453
221527
|
for (const [id, entry] of sessions) {
|
|
221454
|
-
if (now - entry.created_at > SESSION_TTL_MS) {
|
|
221528
|
+
if (now - entry.created_at > SESSION_TTL_MS || now - entry.last_accessed > SESSION_IDLE_TTL_MS) {
|
|
221529
|
+
sessions.delete(id);
|
|
221530
|
+
}
|
|
221531
|
+
}
|
|
221532
|
+
if (sessions.size > MAX_SESSIONS) {
|
|
221533
|
+
const sorted = [...sessions.entries()].sort((a, b) => a[1].last_accessed - b[1].last_accessed);
|
|
221534
|
+
const toRemove = sorted.slice(0, sessions.size - MAX_SESSIONS);
|
|
221535
|
+
for (const [id] of toRemove) {
|
|
221455
221536
|
sessions.delete(id);
|
|
221456
221537
|
}
|
|
221457
221538
|
}
|
|
@@ -221462,13 +221543,485 @@ function createFindCodeBlockRef(file2, blockStart, blockEnd) {
|
|
|
221462
221543
|
function createReadCodeBlockRef(symbolName, file2, startLine) {
|
|
221463
221544
|
return `${symbolName}@${file2}:${startLine}`;
|
|
221464
221545
|
}
|
|
221465
|
-
var sessions, SESSION_TTL_MS = 3600000;
|
|
221546
|
+
var sessions, SESSION_TTL_MS = 3600000, SESSION_IDLE_TTL_MS = 1800000, MAX_SESSIONS = 200;
|
|
221466
221547
|
var init_sessionStore = __esm(() => {
|
|
221467
221548
|
sessions = new Map;
|
|
221468
221549
|
});
|
|
221469
221550
|
|
|
221551
|
+
// src/tools/reader/index-db.ts
|
|
221552
|
+
var exports_index_db = {};
|
|
221553
|
+
__export(exports_index_db, {
|
|
221554
|
+
getIndexDB: () => getIndexDB,
|
|
221555
|
+
closeAllIndexDBs: () => closeAllIndexDBs,
|
|
221556
|
+
IndexDB: () => IndexDB
|
|
221557
|
+
});
|
|
221558
|
+
import { Database } from "bun:sqlite";
|
|
221559
|
+
import { join as join18 } from "path";
|
|
221560
|
+
import { mkdirSync, existsSync as existsSync6 } from "node:fs";
|
|
221561
|
+
|
|
221562
|
+
class IndexDB {
|
|
221563
|
+
db;
|
|
221564
|
+
projectRoot;
|
|
221565
|
+
dbPath;
|
|
221566
|
+
stmts;
|
|
221567
|
+
constructor(projectRoot) {
|
|
221568
|
+
this.projectRoot = projectRoot;
|
|
221569
|
+
const indexDir = join18(projectRoot, ".zephex");
|
|
221570
|
+
if (!existsSync6(indexDir)) {
|
|
221571
|
+
mkdirSync(indexDir, { recursive: true });
|
|
221572
|
+
}
|
|
221573
|
+
this.dbPath = join18(indexDir, "index.db");
|
|
221574
|
+
this.db = new Database(this.dbPath, { create: true, strict: true });
|
|
221575
|
+
this.init();
|
|
221576
|
+
}
|
|
221577
|
+
init() {
|
|
221578
|
+
this.db.run("PRAGMA journal_mode = WAL");
|
|
221579
|
+
this.db.run("PRAGMA synchronous = NORMAL");
|
|
221580
|
+
this.db.run("PRAGMA cache_size = -64000");
|
|
221581
|
+
this.db.run("PRAGMA foreign_keys = ON");
|
|
221582
|
+
this.db.run("PRAGMA temp_store = MEMORY");
|
|
221583
|
+
this.db.run("CREATE TABLE IF NOT EXISTS index_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
221584
|
+
const version4 = this.db.query("SELECT value FROM index_meta WHERE key = 'schema_version'").get();
|
|
221585
|
+
if (version4?.value !== SCHEMA_VERSION) {
|
|
221586
|
+
this.db.run("DROP TABLE IF EXISTS call_graph");
|
|
221587
|
+
this.db.run("DROP TABLE IF EXISTS symbol_index");
|
|
221588
|
+
this.db.run("DROP TABLE IF EXISTS workspace_files");
|
|
221589
|
+
this.db.run("DROP TABLE IF EXISTS index_meta");
|
|
221590
|
+
this.db.exec(SCHEMA_SQL);
|
|
221591
|
+
this.db.run("INSERT OR REPLACE INTO index_meta (key, value) VALUES ('schema_version', ?)", [SCHEMA_VERSION]);
|
|
221592
|
+
}
|
|
221593
|
+
this.prepareStatements();
|
|
221594
|
+
}
|
|
221595
|
+
prepareStatements() {
|
|
221596
|
+
this.stmts = {
|
|
221597
|
+
insertFile: this.db.query("INSERT OR REPLACE INTO workspace_files (file_path, content_hash, language, last_indexed_at, line_count) VALUES ($file_path, $content_hash, $language, $last_indexed_at, $line_count)"),
|
|
221598
|
+
insertSymbol: this.db.query("INSERT INTO symbol_index (file_path, name, kind, start_line, end_line, byte_offset_start, byte_offset_end, signature, is_exported, symbol_id) VALUES ($file_path, $name, $kind, $start_line, $end_line, $byte_offset_start, $byte_offset_end, $signature, $is_exported, $symbol_id)"),
|
|
221599
|
+
insertCallEdge: this.db.query("INSERT OR IGNORE INTO call_graph (caller_id, callee_id, call_line) VALUES ($caller_id, $callee_id, $call_line)"),
|
|
221600
|
+
getFile: this.db.query("SELECT * FROM workspace_files WHERE file_path = $file_path"),
|
|
221601
|
+
getSymbolByName: this.db.query("SELECT * FROM symbol_index WHERE name = $name COLLATE NOCASE"),
|
|
221602
|
+
getSymbolById: this.db.query("SELECT * FROM symbol_index WHERE symbol_id = $symbol_id"),
|
|
221603
|
+
getSymbolsByFile: this.db.query("SELECT * FROM symbol_index WHERE file_path = $file_path ORDER BY start_line"),
|
|
221604
|
+
getCallers: this.db.query(`SELECT s.*, cg.call_line FROM call_graph cg
|
|
221605
|
+
JOIN symbol_index s ON s.id = cg.caller_id
|
|
221606
|
+
WHERE cg.callee_id = $callee_id`),
|
|
221607
|
+
getCallees: this.db.query(`SELECT s.*, cg.call_line FROM call_graph cg
|
|
221608
|
+
JOIN symbol_index s ON s.id = cg.callee_id
|
|
221609
|
+
WHERE cg.caller_id = $caller_id`),
|
|
221610
|
+
getDeadCode: this.db.query(`SELECT s.* FROM symbol_index s
|
|
221611
|
+
WHERE s.is_exported = 1
|
|
221612
|
+
AND s.id NOT IN (SELECT callee_id FROM call_graph)
|
|
221613
|
+
ORDER BY s.file_path, s.start_line`),
|
|
221614
|
+
deleteFileSymbols: this.db.query("DELETE FROM symbol_index WHERE file_path = $file_path"),
|
|
221615
|
+
deleteFile: this.db.query("DELETE FROM workspace_files WHERE file_path = $file_path"),
|
|
221616
|
+
getAllFiles: this.db.query("SELECT * FROM workspace_files")
|
|
221617
|
+
};
|
|
221618
|
+
}
|
|
221619
|
+
needsReindex(filePath, contentHash) {
|
|
221620
|
+
const row = this.stmts.getFile.get({ file_path: filePath });
|
|
221621
|
+
if (!row)
|
|
221622
|
+
return true;
|
|
221623
|
+
return row.content_hash !== contentHash;
|
|
221624
|
+
}
|
|
221625
|
+
getAllFiles() {
|
|
221626
|
+
return this.stmts.getAllFiles.all();
|
|
221627
|
+
}
|
|
221628
|
+
indexFile(filePath, contentHash, language, lineCount, symbols) {
|
|
221629
|
+
const tx = this.db.transaction(() => {
|
|
221630
|
+
this.stmts.deleteFileSymbols.run({ file_path: filePath });
|
|
221631
|
+
this.stmts.deleteFile.run({ file_path: filePath });
|
|
221632
|
+
this.stmts.insertFile.run({
|
|
221633
|
+
file_path: filePath,
|
|
221634
|
+
content_hash: contentHash,
|
|
221635
|
+
language,
|
|
221636
|
+
last_indexed_at: Date.now(),
|
|
221637
|
+
line_count: lineCount
|
|
221638
|
+
});
|
|
221639
|
+
for (const sym of symbols) {
|
|
221640
|
+
const symbolId = `${filePath}::${sym.name}#${sym.kind}`;
|
|
221641
|
+
this.stmts.insertSymbol.run({
|
|
221642
|
+
file_path: filePath,
|
|
221643
|
+
name: sym.name,
|
|
221644
|
+
kind: sym.kind,
|
|
221645
|
+
start_line: sym.start_line,
|
|
221646
|
+
end_line: sym.end_line,
|
|
221647
|
+
byte_offset_start: sym.byte_offset_start,
|
|
221648
|
+
byte_offset_end: sym.byte_offset_end,
|
|
221649
|
+
signature: sym.signature,
|
|
221650
|
+
is_exported: sym.is_exported ? 1 : 0,
|
|
221651
|
+
symbol_id: symbolId
|
|
221652
|
+
});
|
|
221653
|
+
}
|
|
221654
|
+
});
|
|
221655
|
+
tx();
|
|
221656
|
+
}
|
|
221657
|
+
indexFiles(files) {
|
|
221658
|
+
const tx = this.db.transaction(() => {
|
|
221659
|
+
for (const file2 of files) {
|
|
221660
|
+
this.stmts.deleteFileSymbols.run({ file_path: file2.filePath });
|
|
221661
|
+
this.stmts.deleteFile.run({ file_path: file2.filePath });
|
|
221662
|
+
this.stmts.insertFile.run({
|
|
221663
|
+
file_path: file2.filePath,
|
|
221664
|
+
content_hash: file2.contentHash,
|
|
221665
|
+
language: file2.language,
|
|
221666
|
+
last_indexed_at: Date.now(),
|
|
221667
|
+
line_count: file2.lineCount
|
|
221668
|
+
});
|
|
221669
|
+
for (const sym of file2.symbols) {
|
|
221670
|
+
const symbolId = `${file2.filePath}::${sym.name}#${sym.kind}`;
|
|
221671
|
+
this.stmts.insertSymbol.run({
|
|
221672
|
+
file_path: file2.filePath,
|
|
221673
|
+
name: sym.name,
|
|
221674
|
+
kind: sym.kind,
|
|
221675
|
+
start_line: sym.start_line,
|
|
221676
|
+
end_line: sym.end_line,
|
|
221677
|
+
byte_offset_start: sym.byte_offset_start,
|
|
221678
|
+
byte_offset_end: sym.byte_offset_end,
|
|
221679
|
+
signature: sym.signature,
|
|
221680
|
+
is_exported: sym.is_exported ? 1 : 0,
|
|
221681
|
+
symbol_id: symbolId
|
|
221682
|
+
});
|
|
221683
|
+
}
|
|
221684
|
+
}
|
|
221685
|
+
});
|
|
221686
|
+
tx();
|
|
221687
|
+
}
|
|
221688
|
+
findByName(name2) {
|
|
221689
|
+
return this.stmts.getSymbolByName.all({ name: name2 });
|
|
221690
|
+
}
|
|
221691
|
+
findById(symbolId) {
|
|
221692
|
+
return this.stmts.getSymbolById.get({ symbol_id: symbolId }) ?? null;
|
|
221693
|
+
}
|
|
221694
|
+
getFileSymbols(filePath) {
|
|
221695
|
+
return this.stmts.getSymbolsByFile.all({ file_path: filePath });
|
|
221696
|
+
}
|
|
221697
|
+
findCallers(symbolId, maxDepth = 3) {
|
|
221698
|
+
const results = [];
|
|
221699
|
+
const visited = new Set;
|
|
221700
|
+
let frontier = [symbolId];
|
|
221701
|
+
for (let depth = 1;depth <= maxDepth && frontier.length > 0; depth++) {
|
|
221702
|
+
const nextFrontier = [];
|
|
221703
|
+
for (const id of frontier) {
|
|
221704
|
+
if (visited.has(id))
|
|
221705
|
+
continue;
|
|
221706
|
+
visited.add(id);
|
|
221707
|
+
const callers = this.stmts.getCallers.all({ callee_id: id });
|
|
221708
|
+
for (const caller of callers) {
|
|
221709
|
+
if (!visited.has(caller.id)) {
|
|
221710
|
+
results.push({ ...caller, depth });
|
|
221711
|
+
nextFrontier.push(caller.id);
|
|
221712
|
+
}
|
|
221713
|
+
}
|
|
221714
|
+
}
|
|
221715
|
+
frontier = nextFrontier;
|
|
221716
|
+
}
|
|
221717
|
+
return results;
|
|
221718
|
+
}
|
|
221719
|
+
findBlastRadius(symbolId) {
|
|
221720
|
+
return this.findCallers(symbolId, 10);
|
|
221721
|
+
}
|
|
221722
|
+
findDeadCode() {
|
|
221723
|
+
return this.stmts.getDeadCode.all();
|
|
221724
|
+
}
|
|
221725
|
+
searchSymbols(query, limit = 20) {
|
|
221726
|
+
const exact = this.db.query("SELECT * FROM symbol_index WHERE name = $q COLLATE NOCASE LIMIT $limit").all({ q: query, limit });
|
|
221727
|
+
if (exact.length > 0)
|
|
221728
|
+
return exact;
|
|
221729
|
+
const prefix = this.db.query("SELECT * FROM symbol_index WHERE name LIKE $q COLLATE NOCASE LIMIT $limit").all({ q: `${query}%`, limit });
|
|
221730
|
+
if (prefix.length > 0)
|
|
221731
|
+
return prefix;
|
|
221732
|
+
return this.db.query("SELECT * FROM symbol_index WHERE name LIKE $q COLLATE NOCASE LIMIT $limit").all({ q: `%${query}%`, limit });
|
|
221733
|
+
}
|
|
221734
|
+
insertCallEdges(edges) {
|
|
221735
|
+
const tx = this.db.transaction(() => {
|
|
221736
|
+
for (const edge of edges) {
|
|
221737
|
+
this.stmts.insertCallEdge.run({
|
|
221738
|
+
caller_id: edge.caller_id,
|
|
221739
|
+
callee_id: edge.callee_id,
|
|
221740
|
+
call_line: edge.call_line
|
|
221741
|
+
});
|
|
221742
|
+
}
|
|
221743
|
+
});
|
|
221744
|
+
tx();
|
|
221745
|
+
}
|
|
221746
|
+
removeStaleFiles(existingPaths) {
|
|
221747
|
+
const allFiles = this.getAllFiles();
|
|
221748
|
+
let removed = 0;
|
|
221749
|
+
const tx = this.db.transaction(() => {
|
|
221750
|
+
for (const file2 of allFiles) {
|
|
221751
|
+
if (!existingPaths.has(file2.file_path)) {
|
|
221752
|
+
this.stmts.deleteFileSymbols.run({ file_path: file2.file_path });
|
|
221753
|
+
this.stmts.deleteFile.run({ file_path: file2.file_path });
|
|
221754
|
+
removed++;
|
|
221755
|
+
}
|
|
221756
|
+
}
|
|
221757
|
+
});
|
|
221758
|
+
tx();
|
|
221759
|
+
return removed;
|
|
221760
|
+
}
|
|
221761
|
+
getStats() {
|
|
221762
|
+
const files = this.db.query("SELECT COUNT(*) as c FROM workspace_files").get().c;
|
|
221763
|
+
const symbols = this.db.query("SELECT COUNT(*) as c FROM symbol_index").get().c;
|
|
221764
|
+
const edges = this.db.query("SELECT COUNT(*) as c FROM call_graph").get().c;
|
|
221765
|
+
return { files, symbols, edges };
|
|
221766
|
+
}
|
|
221767
|
+
close() {
|
|
221768
|
+
this.db.close(false);
|
|
221769
|
+
}
|
|
221770
|
+
}
|
|
221771
|
+
function getIndexDB(projectRoot) {
|
|
221772
|
+
let db = indexCache.get(projectRoot);
|
|
221773
|
+
if (!db) {
|
|
221774
|
+
db = new IndexDB(projectRoot);
|
|
221775
|
+
indexCache.set(projectRoot, db);
|
|
221776
|
+
}
|
|
221777
|
+
return db;
|
|
221778
|
+
}
|
|
221779
|
+
function closeAllIndexDBs() {
|
|
221780
|
+
for (const db of indexCache.values()) {
|
|
221781
|
+
db.close();
|
|
221782
|
+
}
|
|
221783
|
+
indexCache.clear();
|
|
221784
|
+
}
|
|
221785
|
+
var SCHEMA_SQL = `
|
|
221786
|
+
CREATE TABLE IF NOT EXISTS workspace_files (
|
|
221787
|
+
file_path TEXT PRIMARY KEY,
|
|
221788
|
+
content_hash TEXT NOT NULL,
|
|
221789
|
+
language TEXT NOT NULL,
|
|
221790
|
+
last_indexed_at INTEGER NOT NULL,
|
|
221791
|
+
line_count INTEGER NOT NULL DEFAULT 0
|
|
221792
|
+
);
|
|
221793
|
+
|
|
221794
|
+
CREATE TABLE IF NOT EXISTS symbol_index (
|
|
221795
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
221796
|
+
file_path TEXT NOT NULL,
|
|
221797
|
+
name TEXT NOT NULL,
|
|
221798
|
+
kind TEXT NOT NULL,
|
|
221799
|
+
start_line INTEGER NOT NULL,
|
|
221800
|
+
end_line INTEGER NOT NULL,
|
|
221801
|
+
byte_offset_start INTEGER NOT NULL,
|
|
221802
|
+
byte_offset_end INTEGER NOT NULL,
|
|
221803
|
+
signature TEXT,
|
|
221804
|
+
is_exported INTEGER NOT NULL DEFAULT 0,
|
|
221805
|
+
symbol_id TEXT NOT NULL,
|
|
221806
|
+
FOREIGN KEY (file_path) REFERENCES workspace_files(file_path) ON DELETE CASCADE
|
|
221807
|
+
);
|
|
221808
|
+
|
|
221809
|
+
CREATE TABLE IF NOT EXISTS call_graph (
|
|
221810
|
+
caller_id INTEGER NOT NULL,
|
|
221811
|
+
callee_id INTEGER NOT NULL,
|
|
221812
|
+
call_line INTEGER NOT NULL,
|
|
221813
|
+
PRIMARY KEY (caller_id, callee_id, call_line),
|
|
221814
|
+
FOREIGN KEY (caller_id) REFERENCES symbol_index(id) ON DELETE CASCADE,
|
|
221815
|
+
FOREIGN KEY (callee_id) REFERENCES symbol_index(id) ON DELETE CASCADE
|
|
221816
|
+
);
|
|
221817
|
+
|
|
221818
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbol_index(name);
|
|
221819
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_name_kind ON symbol_index(name, kind);
|
|
221820
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbol_index(file_path);
|
|
221821
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_id ON symbol_index(symbol_id);
|
|
221822
|
+
CREATE INDEX IF NOT EXISTS idx_call_graph_callee ON call_graph(callee_id);
|
|
221823
|
+
CREATE INDEX IF NOT EXISTS idx_call_graph_caller ON call_graph(caller_id);
|
|
221824
|
+
|
|
221825
|
+
CREATE TABLE IF NOT EXISTS index_meta (
|
|
221826
|
+
key TEXT PRIMARY KEY,
|
|
221827
|
+
value TEXT NOT NULL
|
|
221828
|
+
);
|
|
221829
|
+
`, SCHEMA_VERSION = "2", indexCache;
|
|
221830
|
+
var init_index_db = __esm(() => {
|
|
221831
|
+
indexCache = new Map;
|
|
221832
|
+
});
|
|
221833
|
+
|
|
221834
|
+
// src/tools/reader/indexer.ts
|
|
221835
|
+
var exports_indexer = {};
|
|
221836
|
+
__export(exports_indexer, {
|
|
221837
|
+
hasIndex: () => hasIndex,
|
|
221838
|
+
buildIndex: () => buildIndex
|
|
221839
|
+
});
|
|
221840
|
+
import { join as join19 } from "path";
|
|
221841
|
+
async function buildIndex(projectRoot) {
|
|
221842
|
+
const start2 = Date.now();
|
|
221843
|
+
const db = getIndexDB(projectRoot);
|
|
221844
|
+
const filePaths = await discoverFiles(projectRoot);
|
|
221845
|
+
const existingPaths = new Set(filePaths.map((f) => f.relativePath));
|
|
221846
|
+
db.removeStaleFiles(existingPaths);
|
|
221847
|
+
const toIndex = [];
|
|
221848
|
+
let fromCache = 0;
|
|
221849
|
+
for (const file2 of filePaths) {
|
|
221850
|
+
const content = await readFileContent(file2.absolutePath);
|
|
221851
|
+
if (!content)
|
|
221852
|
+
continue;
|
|
221853
|
+
const hash2 = hashContent(content);
|
|
221854
|
+
if (!db.needsReindex(file2.relativePath, hash2)) {
|
|
221855
|
+
fromCache++;
|
|
221856
|
+
continue;
|
|
221857
|
+
}
|
|
221858
|
+
const lang = detectLanguage2(file2.relativePath);
|
|
221859
|
+
if (!lang)
|
|
221860
|
+
continue;
|
|
221861
|
+
toIndex.push({
|
|
221862
|
+
relativePath: file2.relativePath,
|
|
221863
|
+
absolutePath: file2.absolutePath,
|
|
221864
|
+
content,
|
|
221865
|
+
hash: hash2,
|
|
221866
|
+
language: lang
|
|
221867
|
+
});
|
|
221868
|
+
}
|
|
221869
|
+
const fileBatch = [];
|
|
221870
|
+
let totalSymbols = 0;
|
|
221871
|
+
for (const file2 of toIndex) {
|
|
221872
|
+
try {
|
|
221873
|
+
const parsed = await parseFile3(file2.relativePath, file2.content);
|
|
221874
|
+
if (!parsed)
|
|
221875
|
+
continue;
|
|
221876
|
+
const lineCount = file2.content.split(`
|
|
221877
|
+
`).length;
|
|
221878
|
+
const symbols = parsed.symbols.map((sym) => {
|
|
221879
|
+
const lines = file2.content.split(`
|
|
221880
|
+
`);
|
|
221881
|
+
let byteStart = 0;
|
|
221882
|
+
for (let i2 = 0;i2 < sym.startLine - 1 && i2 < lines.length; i2++) {
|
|
221883
|
+
byteStart += lines[i2].length + 1;
|
|
221884
|
+
}
|
|
221885
|
+
let byteEnd = byteStart;
|
|
221886
|
+
for (let i2 = sym.startLine - 1;i2 < sym.endLine && i2 < lines.length; i2++) {
|
|
221887
|
+
byteEnd += lines[i2].length + 1;
|
|
221888
|
+
}
|
|
221889
|
+
return {
|
|
221890
|
+
name: sym.name,
|
|
221891
|
+
kind: sym.kind,
|
|
221892
|
+
start_line: sym.startLine,
|
|
221893
|
+
end_line: sym.endLine,
|
|
221894
|
+
byte_offset_start: byteStart,
|
|
221895
|
+
byte_offset_end: byteEnd,
|
|
221896
|
+
signature: sym.signature || null,
|
|
221897
|
+
is_exported: sym.isExported ?? false
|
|
221898
|
+
};
|
|
221899
|
+
});
|
|
221900
|
+
totalSymbols += symbols.length;
|
|
221901
|
+
fileBatch.push({
|
|
221902
|
+
filePath: file2.relativePath,
|
|
221903
|
+
contentHash: file2.hash,
|
|
221904
|
+
language: file2.language,
|
|
221905
|
+
lineCount,
|
|
221906
|
+
symbols
|
|
221907
|
+
});
|
|
221908
|
+
} catch (err2) {
|
|
221909
|
+
logger.warn(`Indexer: failed to parse ${file2.relativePath}: ${err2.message}`);
|
|
221910
|
+
}
|
|
221911
|
+
}
|
|
221912
|
+
if (fileBatch.length > 0) {
|
|
221913
|
+
db.indexFiles(fileBatch);
|
|
221914
|
+
}
|
|
221915
|
+
return {
|
|
221916
|
+
files_scanned: filePaths.length,
|
|
221917
|
+
files_indexed: fileBatch.length,
|
|
221918
|
+
files_skipped: filePaths.length - fileBatch.length - fromCache,
|
|
221919
|
+
symbols_indexed: totalSymbols,
|
|
221920
|
+
duration_ms: Date.now() - start2,
|
|
221921
|
+
from_cache: fromCache
|
|
221922
|
+
};
|
|
221923
|
+
}
|
|
221924
|
+
function hasIndex(projectRoot) {
|
|
221925
|
+
const db = getIndexDB(projectRoot);
|
|
221926
|
+
const stats = db.getStats();
|
|
221927
|
+
return stats.files > 0;
|
|
221928
|
+
}
|
|
221929
|
+
async function discoverFiles(root) {
|
|
221930
|
+
const files = [];
|
|
221931
|
+
const glob = new Bun.Glob("**/*");
|
|
221932
|
+
for await (const entry of glob.scan({ cwd: root, onlyFiles: true })) {
|
|
221933
|
+
if (files.length >= MAX_FILES)
|
|
221934
|
+
break;
|
|
221935
|
+
const parts2 = entry.split("/");
|
|
221936
|
+
if (parts2.some((p) => EXCLUDE_DIRS.has(p)))
|
|
221937
|
+
continue;
|
|
221938
|
+
const ext = "." + (entry.split(".").pop()?.toLowerCase() || "");
|
|
221939
|
+
if (!SOURCE_EXTENSIONS2.has(ext))
|
|
221940
|
+
continue;
|
|
221941
|
+
files.push({
|
|
221942
|
+
relativePath: entry,
|
|
221943
|
+
absolutePath: join19(root, entry)
|
|
221944
|
+
});
|
|
221945
|
+
}
|
|
221946
|
+
return files;
|
|
221947
|
+
}
|
|
221948
|
+
async function readFileContent(absolutePath) {
|
|
221949
|
+
try {
|
|
221950
|
+
const file2 = Bun.file(absolutePath);
|
|
221951
|
+
const size = file2.size;
|
|
221952
|
+
if (size > MAX_FILE_SIZE2)
|
|
221953
|
+
return null;
|
|
221954
|
+
return await file2.text();
|
|
221955
|
+
} catch {
|
|
221956
|
+
return null;
|
|
221957
|
+
}
|
|
221958
|
+
}
|
|
221959
|
+
function hashContent(content) {
|
|
221960
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
221961
|
+
hasher.update(content);
|
|
221962
|
+
return hasher.digest("hex");
|
|
221963
|
+
}
|
|
221964
|
+
var MAX_FILES = 2000, MAX_FILE_SIZE2 = 1e6, EXCLUDE_DIRS, SOURCE_EXTENSIONS2;
|
|
221965
|
+
var init_indexer = __esm(() => {
|
|
221966
|
+
init_index_db();
|
|
221967
|
+
init_parser();
|
|
221968
|
+
init_logger();
|
|
221969
|
+
EXCLUDE_DIRS = new Set([
|
|
221970
|
+
"node_modules",
|
|
221971
|
+
".git",
|
|
221972
|
+
"dist",
|
|
221973
|
+
"build",
|
|
221974
|
+
".next",
|
|
221975
|
+
".nuxt",
|
|
221976
|
+
".output",
|
|
221977
|
+
"coverage",
|
|
221978
|
+
"__pycache__",
|
|
221979
|
+
".venv",
|
|
221980
|
+
"venv",
|
|
221981
|
+
"target",
|
|
221982
|
+
"vendor",
|
|
221983
|
+
".zephex",
|
|
221984
|
+
".cache",
|
|
221985
|
+
".turbo",
|
|
221986
|
+
"out"
|
|
221987
|
+
]);
|
|
221988
|
+
SOURCE_EXTENSIONS2 = new Set([
|
|
221989
|
+
".ts",
|
|
221990
|
+
".tsx",
|
|
221991
|
+
".js",
|
|
221992
|
+
".jsx",
|
|
221993
|
+
".mjs",
|
|
221994
|
+
".cjs",
|
|
221995
|
+
".py",
|
|
221996
|
+
".go",
|
|
221997
|
+
".java",
|
|
221998
|
+
".rb",
|
|
221999
|
+
".php",
|
|
222000
|
+
".rs",
|
|
222001
|
+
".cs",
|
|
222002
|
+
".cpp",
|
|
222003
|
+
".cc",
|
|
222004
|
+
".c",
|
|
222005
|
+
".h",
|
|
222006
|
+
".hpp",
|
|
222007
|
+
".sh",
|
|
222008
|
+
".kt",
|
|
222009
|
+
".kts",
|
|
222010
|
+
".swift",
|
|
222011
|
+
".scala",
|
|
222012
|
+
".dart",
|
|
222013
|
+
".ex",
|
|
222014
|
+
".exs",
|
|
222015
|
+
".zig",
|
|
222016
|
+
".lua",
|
|
222017
|
+
".vue",
|
|
222018
|
+
".svelte",
|
|
222019
|
+
".astro"
|
|
222020
|
+
]);
|
|
222021
|
+
});
|
|
222022
|
+
|
|
221470
222023
|
// src/tools/reader/readCode.ts
|
|
221471
|
-
import { isAbsolute as isAbsolute6, normalize as normalize4, relative } from "path";
|
|
222024
|
+
import { isAbsolute as isAbsolute6, normalize as normalize4, relative, join as join20 } from "path";
|
|
221472
222025
|
import { access as access6, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
221473
222026
|
function iterativeUrlDecode2(input) {
|
|
221474
222027
|
let decoded = input;
|
|
@@ -221684,7 +222237,18 @@ function extractDirectImports(fileContent) {
|
|
|
221684
222237
|
}
|
|
221685
222238
|
function isBinaryContent(content) {
|
|
221686
222239
|
const check2 = content.slice(0, 512);
|
|
221687
|
-
|
|
222240
|
+
if (check2.includes("\x00"))
|
|
222241
|
+
return true;
|
|
222242
|
+
const head2 = check2.slice(0, 8);
|
|
222243
|
+
if (head2.startsWith("PNG") || head2.startsWith("ÿØÿ") || head2.startsWith("GIF8") || head2.startsWith("PK\x03\x04") || head2.startsWith("ELF") || head2.startsWith("MZ") || head2.startsWith("Êþº¾") || head2.startsWith("\x1F") || head2.startsWith("RIFF") || head2.startsWith("%PDF"))
|
|
222244
|
+
return true;
|
|
222245
|
+
let nonPrintable = 0;
|
|
222246
|
+
for (let i2 = 0;i2 < check2.length; i2++) {
|
|
222247
|
+
const c = check2.charCodeAt(i2);
|
|
222248
|
+
if (c < 8 || c > 13 && c < 32 && c !== 27)
|
|
222249
|
+
nonPrintable++;
|
|
222250
|
+
}
|
|
222251
|
+
return nonPrintable / check2.length > 0.1;
|
|
221688
222252
|
}
|
|
221689
222253
|
function isBinaryFile(filePath) {
|
|
221690
222254
|
const ext = "." + (filePath.split(".").pop()?.toLowerCase() || "");
|
|
@@ -221889,7 +222453,7 @@ function addLineNumbers(body2, startLine) {
|
|
|
221889
222453
|
}).join(`
|
|
221890
222454
|
`);
|
|
221891
222455
|
}
|
|
221892
|
-
function extractSymbolContent(symbol2, detailLevel, fileContent) {
|
|
222456
|
+
function extractSymbolContent(symbol2, detailLevel, fileContent, compact) {
|
|
221893
222457
|
const signature = extractSignature(symbol2);
|
|
221894
222458
|
const base = {
|
|
221895
222459
|
name: symbol2.name,
|
|
@@ -221910,14 +222474,16 @@ function extractSymbolContent(symbol2, detailLevel, fileContent) {
|
|
|
221910
222474
|
base.token_estimate = estimateTokens(signature);
|
|
221911
222475
|
return base;
|
|
221912
222476
|
case "body": {
|
|
221913
|
-
const numberedBody = addLineNumbers(symbol2.body, symbol2.startLine);
|
|
222477
|
+
const numberedBody = compact ? symbol2.body : addLineNumbers(symbol2.body, symbol2.startLine);
|
|
221914
222478
|
base.body = numberedBody;
|
|
221915
222479
|
base.token_estimate = estimateTokens(numberedBody);
|
|
222480
|
+
base.diagnostics = computeDiagnostics(symbol2.body, symbol2.params?.length ?? 0);
|
|
221916
222481
|
return base;
|
|
221917
222482
|
}
|
|
221918
222483
|
case "context": {
|
|
221919
|
-
const numberedBody = addLineNumbers(symbol2.body, symbol2.startLine);
|
|
222484
|
+
const numberedBody = compact ? symbol2.body : addLineNumbers(symbol2.body, symbol2.startLine);
|
|
221920
222485
|
base.body = numberedBody;
|
|
222486
|
+
base.diagnostics = computeDiagnostics(symbol2.body, symbol2.params?.length ?? 0);
|
|
221921
222487
|
base.direct_imports = extractDirectImports(fileContent);
|
|
221922
222488
|
const contextText = numberedBody + `
|
|
221923
222489
|
` + base.direct_imports.join(`
|
|
@@ -221958,8 +222524,8 @@ async function findTestFiles(symbolName, symbolFile, filesToSearch) {
|
|
|
221958
222524
|
async function scanLocalDirectory(dirPath) {
|
|
221959
222525
|
const { readFile: readFile2 } = await import("fs/promises");
|
|
221960
222526
|
const files = {};
|
|
221961
|
-
const
|
|
221962
|
-
const
|
|
222527
|
+
const MAX_FILES2 = 400;
|
|
222528
|
+
const MAX_FILE_SIZE3 = 1048576;
|
|
221963
222529
|
const priorityPatterns = [
|
|
221964
222530
|
"src/**/*",
|
|
221965
222531
|
"lib/**/*",
|
|
@@ -221985,7 +222551,7 @@ async function scanLocalDirectory(dirPath) {
|
|
|
221985
222551
|
return false;
|
|
221986
222552
|
try {
|
|
221987
222553
|
const stats = await stat4(filePath);
|
|
221988
|
-
if (!stats.isFile() || stats.size >
|
|
222554
|
+
if (!stats.isFile() || stats.size > MAX_FILE_SIZE3 || stats.size === 0)
|
|
221989
222555
|
return false;
|
|
221990
222556
|
const content = await readFile2(filePath, "utf-8");
|
|
221991
222557
|
if (isBinaryContent(content))
|
|
@@ -221999,26 +222565,307 @@ async function scanLocalDirectory(dirPath) {
|
|
|
221999
222565
|
}
|
|
222000
222566
|
}
|
|
222001
222567
|
for (const pattern of priorityPatterns) {
|
|
222002
|
-
if (fileCount >=
|
|
222568
|
+
if (fileCount >= MAX_FILES2)
|
|
222003
222569
|
break;
|
|
222004
222570
|
const glob = new Bun.Glob(pattern);
|
|
222005
222571
|
for await (const filePath of glob.scan({ cwd: dirPath, absolute: true })) {
|
|
222006
|
-
if (fileCount >=
|
|
222572
|
+
if (fileCount >= MAX_FILES2)
|
|
222007
222573
|
break;
|
|
222008
222574
|
await ingest(filePath);
|
|
222009
222575
|
}
|
|
222010
222576
|
}
|
|
222011
|
-
if (fileCount <
|
|
222577
|
+
if (fileCount < MAX_FILES2) {
|
|
222012
222578
|
const glob = new Bun.Glob(fallbackPattern);
|
|
222013
222579
|
for await (const filePath of glob.scan({ cwd: dirPath, absolute: true })) {
|
|
222014
|
-
if (fileCount >=
|
|
222580
|
+
if (fileCount >= MAX_FILES2)
|
|
222015
222581
|
break;
|
|
222016
222582
|
await ingest(filePath);
|
|
222017
222583
|
}
|
|
222018
222584
|
}
|
|
222019
222585
|
return files;
|
|
222020
222586
|
}
|
|
222587
|
+
async function handleFileMode(params, filesToSearch) {
|
|
222588
|
+
const maxTokens = Math.min(params.max_tokens ?? DEFAULT_MAX_TOKENS, MAX_TOKENS_LIMIT);
|
|
222589
|
+
const requestedFiles = params.files ?? [];
|
|
222590
|
+
const offsetLine = Math.max(1, params.offset_line ?? 1);
|
|
222591
|
+
const limitLines = params.limit_lines;
|
|
222592
|
+
const compact = params.compact ?? false;
|
|
222593
|
+
if (requestedFiles.length === 0) {
|
|
222594
|
+
throw new ReadCodeError("mode:'file' requires a `files` array with at least one path", -32602);
|
|
222595
|
+
}
|
|
222596
|
+
const readResults = await Promise.all(requestedFiles.map(async (filePath) => {
|
|
222597
|
+
const content = filesToSearch[filePath] ?? filesToSearch[filePath.startsWith("/") ? filePath.slice(1) : filePath];
|
|
222598
|
+
if (!content) {
|
|
222599
|
+
return {
|
|
222600
|
+
file: filePath,
|
|
222601
|
+
content: `// File not found: ${filePath}`,
|
|
222602
|
+
total_lines: 0,
|
|
222603
|
+
returned_lines: 0,
|
|
222604
|
+
offset: offsetLine,
|
|
222605
|
+
has_more: false,
|
|
222606
|
+
token_estimate: 10,
|
|
222607
|
+
truncated: false
|
|
222608
|
+
};
|
|
222609
|
+
}
|
|
222610
|
+
const allLines = content.split(`
|
|
222611
|
+
`);
|
|
222612
|
+
const totalLines = allLines.length;
|
|
222613
|
+
const startIdx = offsetLine - 1;
|
|
222614
|
+
let endIdx = totalLines;
|
|
222615
|
+
if (limitLines)
|
|
222616
|
+
endIdx = Math.min(startIdx + limitLines, totalLines);
|
|
222617
|
+
const slicedLines = allLines.slice(startIdx, endIdx);
|
|
222618
|
+
let outputContent;
|
|
222619
|
+
if (compact) {
|
|
222620
|
+
outputContent = slicedLines.join(`
|
|
222621
|
+
`);
|
|
222622
|
+
} else {
|
|
222623
|
+
const padWidth = String(startIdx + slicedLines.length).length;
|
|
222624
|
+
outputContent = slicedLines.map((line, i2) => `${String(startIdx + i2 + 1).padStart(padWidth)} | ${line}`).join(`
|
|
222625
|
+
`);
|
|
222626
|
+
}
|
|
222627
|
+
const tokenEst = estimateTokens(outputContent);
|
|
222628
|
+
const hasMore = startIdx + slicedLines.length < totalLines;
|
|
222629
|
+
return {
|
|
222630
|
+
file: filePath,
|
|
222631
|
+
content: outputContent,
|
|
222632
|
+
total_lines: totalLines,
|
|
222633
|
+
returned_lines: slicedLines.length,
|
|
222634
|
+
offset: offsetLine,
|
|
222635
|
+
has_more: hasMore,
|
|
222636
|
+
token_estimate: tokenEst,
|
|
222637
|
+
truncated: false
|
|
222638
|
+
};
|
|
222639
|
+
}));
|
|
222640
|
+
const results = [];
|
|
222641
|
+
const remaining = [];
|
|
222642
|
+
let totalTokens = 0;
|
|
222643
|
+
for (const entry of readResults) {
|
|
222644
|
+
if (totalTokens + entry.token_estimate > maxTokens && results.length > 0) {
|
|
222645
|
+
remaining.push(entry.file);
|
|
222646
|
+
continue;
|
|
222647
|
+
}
|
|
222648
|
+
if (totalTokens + entry.token_estimate > maxTokens) {
|
|
222649
|
+
const availableTokens = maxTokens - totalTokens;
|
|
222650
|
+
const availableChars = Math.floor(availableTokens * CHARS_PER_TOKEN);
|
|
222651
|
+
entry.content = entry.content.slice(0, availableChars);
|
|
222652
|
+
entry.returned_lines = entry.content.split(`
|
|
222653
|
+
`).length;
|
|
222654
|
+
entry.token_estimate = estimateTokens(entry.content);
|
|
222655
|
+
entry.truncated = true;
|
|
222656
|
+
}
|
|
222657
|
+
results.push(entry);
|
|
222658
|
+
totalTokens += entry.token_estimate;
|
|
222659
|
+
}
|
|
222660
|
+
return {
|
|
222661
|
+
mode: "file",
|
|
222662
|
+
files: results,
|
|
222663
|
+
total_tokens_returned: totalTokens,
|
|
222664
|
+
remaining: remaining.length > 0 ? remaining : undefined
|
|
222665
|
+
};
|
|
222666
|
+
}
|
|
222667
|
+
async function handleOutlineMode(params, filesToSearch) {
|
|
222668
|
+
const requestedFiles = params.files ?? [];
|
|
222669
|
+
if (requestedFiles.length === 0) {
|
|
222670
|
+
throw new ReadCodeError("mode:'outline' requires a `files` array with at least one path", -32602);
|
|
222671
|
+
}
|
|
222672
|
+
const filePath = requestedFiles[0];
|
|
222673
|
+
const content = filesToSearch[filePath] ?? filesToSearch[filePath.startsWith("/") ? filePath.slice(1) : filePath];
|
|
222674
|
+
if (!content) {
|
|
222675
|
+
throw new ReadCodeError(`File not found: ${filePath}`, -32602);
|
|
222676
|
+
}
|
|
222677
|
+
const totalLines = content.split(`
|
|
222678
|
+
`).length;
|
|
222679
|
+
const symbols = [];
|
|
222680
|
+
const lang = detectLanguage2(filePath);
|
|
222681
|
+
if (lang) {
|
|
222682
|
+
try {
|
|
222683
|
+
const cached2 = await getOrParse(filePath, content);
|
|
222684
|
+
for (const sym of cached2.symbols) {
|
|
222685
|
+
if (sym.name === "anonymous" || sym.kind === "method")
|
|
222686
|
+
continue;
|
|
222687
|
+
symbols.push({
|
|
222688
|
+
name: sym.name,
|
|
222689
|
+
kind: sym.kind,
|
|
222690
|
+
line: sym.startLine,
|
|
222691
|
+
end_line: sym.endLine,
|
|
222692
|
+
signature: sym.signature || sym.body.split(`
|
|
222693
|
+
`)[0]?.trim().slice(0, 200) || sym.name,
|
|
222694
|
+
is_exported: sym.isExported ?? false
|
|
222695
|
+
});
|
|
222696
|
+
}
|
|
222697
|
+
} catch {}
|
|
222698
|
+
}
|
|
222699
|
+
if (symbols.length === 0) {
|
|
222700
|
+
const lines = content.split(`
|
|
222701
|
+
`);
|
|
222702
|
+
const defPattern = /^(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function\*?|class|interface|type|enum|struct|trait|const|let|var|def|fn|pub\s+fn|func|module|namespace)\s+(\w+)/;
|
|
222703
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
222704
|
+
const match = lines[i2]?.match(defPattern);
|
|
222705
|
+
if (match) {
|
|
222706
|
+
symbols.push({
|
|
222707
|
+
name: match[1],
|
|
222708
|
+
kind: "function",
|
|
222709
|
+
line: i2 + 1,
|
|
222710
|
+
end_line: i2 + 1,
|
|
222711
|
+
signature: lines[i2].trim().slice(0, 200),
|
|
222712
|
+
is_exported: /^export\b/.test(lines[i2])
|
|
222713
|
+
});
|
|
222714
|
+
}
|
|
222715
|
+
}
|
|
222716
|
+
}
|
|
222717
|
+
const topLevel = [];
|
|
222718
|
+
for (let i2 = 0;i2 < symbols.length; i2++) {
|
|
222719
|
+
const sym = symbols[i2];
|
|
222720
|
+
const isNested = symbols.some((other, j2) => j2 !== i2 && other.line < sym.line && other.end_line > sym.end_line);
|
|
222721
|
+
if (!isNested)
|
|
222722
|
+
topLevel.push(sym);
|
|
222723
|
+
}
|
|
222724
|
+
const outputText = topLevel.map((s) => `${s.line}: ${s.signature}`).join(`
|
|
222725
|
+
`);
|
|
222726
|
+
return {
|
|
222727
|
+
mode: "outline",
|
|
222728
|
+
file: filePath,
|
|
222729
|
+
total_lines: totalLines,
|
|
222730
|
+
symbols: topLevel,
|
|
222731
|
+
total_tokens_returned: estimateTokens(outputText)
|
|
222732
|
+
};
|
|
222733
|
+
}
|
|
222021
222734
|
async function handleReadCode(params) {
|
|
222735
|
+
const mode = params.mode ?? "symbol";
|
|
222736
|
+
if (mode === "callers" || mode === "blast_radius" || mode === "dead_code") {
|
|
222737
|
+
if (!params.path || isRemoteGitUrl(params.path)) {
|
|
222738
|
+
throw new ReadCodeError(`mode:'${mode}' requires a local 'path' with an existing index`, -32602);
|
|
222739
|
+
}
|
|
222740
|
+
const validatedPath = await validatePath3(params.path);
|
|
222741
|
+
const { getIndexDB: getIndexDB2 } = await Promise.resolve().then(() => (init_index_db(), exports_index_db));
|
|
222742
|
+
const { hasIndex: hasIndex2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
|
|
222743
|
+
if (!hasIndex2(validatedPath)) {
|
|
222744
|
+
throw new ReadCodeError(`No index found. Call read_code with mode:'symbol' first to build the index.`, -32602);
|
|
222745
|
+
}
|
|
222746
|
+
const db = getIndexDB2(validatedPath);
|
|
222747
|
+
if (mode === "dead_code") {
|
|
222748
|
+
const dead = db.findDeadCode();
|
|
222749
|
+
return {
|
|
222750
|
+
mode: "dead_code",
|
|
222751
|
+
symbols: dead.slice(0, 50).map((s) => ({
|
|
222752
|
+
name: s.name,
|
|
222753
|
+
kind: s.kind,
|
|
222754
|
+
file: s.file_path,
|
|
222755
|
+
line: s.start_line,
|
|
222756
|
+
end_line: s.end_line,
|
|
222757
|
+
symbol_id: s.symbol_id,
|
|
222758
|
+
is_exported: s.is_exported === 1,
|
|
222759
|
+
signature: s.signature || s.name
|
|
222760
|
+
})),
|
|
222761
|
+
total_found: dead.length
|
|
222762
|
+
};
|
|
222763
|
+
}
|
|
222764
|
+
if (!params.target && !params.symbol_id) {
|
|
222765
|
+
throw new ReadCodeError(`mode:'${mode}' requires a 'target' or 'symbol_id'`, -32602);
|
|
222766
|
+
}
|
|
222767
|
+
let targetSym = null;
|
|
222768
|
+
if (params.symbol_id) {
|
|
222769
|
+
const found = db.findById(params.symbol_id);
|
|
222770
|
+
if (found)
|
|
222771
|
+
targetSym = found;
|
|
222772
|
+
}
|
|
222773
|
+
if (!targetSym && params.target) {
|
|
222774
|
+
const matches = db.findByName(params.target);
|
|
222775
|
+
if (matches.length > 0)
|
|
222776
|
+
targetSym = matches[0];
|
|
222777
|
+
}
|
|
222778
|
+
if (!targetSym) {
|
|
222779
|
+
throw new ReadCodeError(`Symbol not found in index: ${params.target || params.symbol_id}`, -32602);
|
|
222780
|
+
}
|
|
222781
|
+
const depth = mode === "blast_radius" ? 10 : 3;
|
|
222782
|
+
const results = db.findCallers(targetSym.id, depth);
|
|
222783
|
+
return {
|
|
222784
|
+
mode,
|
|
222785
|
+
target: targetSym.name,
|
|
222786
|
+
target_symbol_id: targetSym.symbol_id,
|
|
222787
|
+
callers: results.map((r) => ({
|
|
222788
|
+
name: r.name,
|
|
222789
|
+
kind: r.kind,
|
|
222790
|
+
file: r.file_path,
|
|
222791
|
+
line: r.start_line,
|
|
222792
|
+
call_line: r.call_line,
|
|
222793
|
+
depth: r.depth,
|
|
222794
|
+
symbol_id: r.symbol_id
|
|
222795
|
+
})),
|
|
222796
|
+
total_found: results.length
|
|
222797
|
+
};
|
|
222798
|
+
}
|
|
222799
|
+
if (mode === "file" || mode === "outline") {
|
|
222800
|
+
let filesToSearch2;
|
|
222801
|
+
if (params.inline_files && Object.keys(params.inline_files).length > 0) {
|
|
222802
|
+
filesToSearch2 = params.inline_files;
|
|
222803
|
+
} else if (params.path) {
|
|
222804
|
+
if (isRemoteGitUrl(params.path)) {
|
|
222805
|
+
return await withResolvedPath(params.path, async (localPath) => {
|
|
222806
|
+
const files = await scanLocalDirectory(localPath);
|
|
222807
|
+
if (mode === "file")
|
|
222808
|
+
return handleFileMode(params, files);
|
|
222809
|
+
return handleOutlineMode(params, files);
|
|
222810
|
+
});
|
|
222811
|
+
}
|
|
222812
|
+
const validatedPath = await validatePath3(params.path);
|
|
222813
|
+
filesToSearch2 = await scanLocalDirectory(validatedPath);
|
|
222814
|
+
} else {
|
|
222815
|
+
throw new ReadCodeError("Either 'path' or 'inline_files' is required", -32602);
|
|
222816
|
+
}
|
|
222817
|
+
if (mode === "file")
|
|
222818
|
+
return handleFileMode(params, filesToSearch2);
|
|
222819
|
+
return handleOutlineMode(params, filesToSearch2);
|
|
222820
|
+
}
|
|
222821
|
+
if (!params.target && !params.symbol_id) {
|
|
222822
|
+
throw new ReadCodeError("mode:'symbol' requires a `target` or `symbol_id` parameter", -32602);
|
|
222823
|
+
}
|
|
222824
|
+
if (params.symbol_id && params.path && !isRemoteGitUrl(params.path)) {
|
|
222825
|
+
try {
|
|
222826
|
+
const { getIndexDB: getIndexDB2 } = await Promise.resolve().then(() => (init_index_db(), exports_index_db));
|
|
222827
|
+
const { hasIndex: hasIndex2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
|
|
222828
|
+
const validatedPath = await validatePath3(params.path);
|
|
222829
|
+
if (hasIndex2(validatedPath)) {
|
|
222830
|
+
const db = getIndexDB2(validatedPath);
|
|
222831
|
+
const sym = db.findById(params.symbol_id);
|
|
222832
|
+
if (sym) {
|
|
222833
|
+
const abs = join20(validatedPath, sym.file_path);
|
|
222834
|
+
const content = await Bun.file(abs).text();
|
|
222835
|
+
const lines = content.split(`
|
|
222836
|
+
`);
|
|
222837
|
+
const body2 = lines.slice(sym.start_line - 1, sym.end_line).join(`
|
|
222838
|
+
`);
|
|
222839
|
+
const numberedBody = params.compact ? body2 : addLineNumbers(body2, sym.start_line);
|
|
222840
|
+
return {
|
|
222841
|
+
target: sym.name,
|
|
222842
|
+
symbols: [{
|
|
222843
|
+
name: sym.name,
|
|
222844
|
+
kind: sym.kind,
|
|
222845
|
+
file: sym.file_path,
|
|
222846
|
+
line: sym.start_line,
|
|
222847
|
+
end_line: sym.end_line,
|
|
222848
|
+
language: detectLanguage2(sym.file_path) || "unknown",
|
|
222849
|
+
signature: sym.signature || lines[sym.start_line - 1]?.trim().slice(0, 200) || sym.name,
|
|
222850
|
+
confidence: 1,
|
|
222851
|
+
is_definition: true,
|
|
222852
|
+
is_exported: sym.is_exported === 1,
|
|
222853
|
+
body: numberedBody,
|
|
222854
|
+
token_estimate: estimateTokens(numberedBody),
|
|
222855
|
+
truncated: false,
|
|
222856
|
+
symbol_id: sym.symbol_id
|
|
222857
|
+
}],
|
|
222858
|
+
total_found: 1,
|
|
222859
|
+
returned_count: 1,
|
|
222860
|
+
truncated: false,
|
|
222861
|
+
total_tokens_returned: estimateTokens(numberedBody),
|
|
222862
|
+
tokens_saved_vs_full_files: estimateTokens(content) - estimateTokens(numberedBody),
|
|
222863
|
+
session_dedup_skipped: 0
|
|
222864
|
+
};
|
|
222865
|
+
}
|
|
222866
|
+
}
|
|
222867
|
+
} catch {}
|
|
222868
|
+
}
|
|
222022
222869
|
let maxTokens = params.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
222023
222870
|
if (params.max_lines && !params.max_tokens) {
|
|
222024
222871
|
maxTokens = Math.ceil(params.max_lines * 12);
|
|
@@ -222045,7 +222892,15 @@ async function handleReadCode(params) {
|
|
|
222045
222892
|
include_tests = false,
|
|
222046
222893
|
session_id
|
|
222047
222894
|
} = params;
|
|
222048
|
-
|
|
222895
|
+
if (!target) {
|
|
222896
|
+
const fallbackTarget = params.symbol_id?.split("::")[1]?.split("#")[0];
|
|
222897
|
+
if (!fallbackTarget) {
|
|
222898
|
+
throw new ReadCodeError("mode:'symbol' requires a `target` or valid `symbol_id`", -32602);
|
|
222899
|
+
}
|
|
222900
|
+
params.target = fallbackTarget;
|
|
222901
|
+
}
|
|
222902
|
+
const effectiveTarget = target || params.symbol_id?.split("::")[1]?.split("#")[0] || "";
|
|
222903
|
+
const allTargets = [effectiveTarget, ...targets || []].slice(0, 9);
|
|
222049
222904
|
const validatedTargets = allTargets.map(validateTarget);
|
|
222050
222905
|
const clampedMaxResults = Math.min(Math.max(1, max_results), MAX_RESULTS_LIMIT);
|
|
222051
222906
|
const clampedThreshold = Math.min(Math.max(0, confidence_threshold), 1);
|
|
@@ -222072,7 +222927,40 @@ async function handleReadCode(params) {
|
|
|
222072
222927
|
return result;
|
|
222073
222928
|
} else {
|
|
222074
222929
|
const validatedPath = await validatePath3(projectPath);
|
|
222075
|
-
|
|
222930
|
+
if (params.use_index !== false) {
|
|
222931
|
+
try {
|
|
222932
|
+
const { hasIndex: hasIndex2, buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
|
|
222933
|
+
const { getIndexDB: getIndexDB2 } = await Promise.resolve().then(() => (init_index_db(), exports_index_db));
|
|
222934
|
+
if (!hasIndex2(validatedPath)) {
|
|
222935
|
+
buildIndex2(validatedPath).catch((err2) => logger.warn(`Index build failed: ${err2.message}`));
|
|
222936
|
+
} else {
|
|
222937
|
+
const db = getIndexDB2(validatedPath);
|
|
222938
|
+
const indexHits = [];
|
|
222939
|
+
for (const t of validatedTargets) {
|
|
222940
|
+
const matches = db.searchSymbols(t, clampedMaxResults);
|
|
222941
|
+
for (const m of matches) {
|
|
222942
|
+
if (!kind || m.kind === kind) {
|
|
222943
|
+
indexHits.push({ file_path: m.file_path, name: m.name });
|
|
222944
|
+
}
|
|
222945
|
+
}
|
|
222946
|
+
}
|
|
222947
|
+
if (indexHits.length > 0) {
|
|
222948
|
+
const uniqueFiles = [...new Set(indexHits.map((h) => h.file_path))];
|
|
222949
|
+
filesToSearch = {};
|
|
222950
|
+
for (const fp of uniqueFiles.slice(0, 50)) {
|
|
222951
|
+
try {
|
|
222952
|
+
const abs = join20(validatedPath, fp);
|
|
222953
|
+
const content = await Bun.file(abs).text();
|
|
222954
|
+
filesToSearch[fp] = content;
|
|
222955
|
+
} catch {}
|
|
222956
|
+
}
|
|
222957
|
+
}
|
|
222958
|
+
}
|
|
222959
|
+
} catch {}
|
|
222960
|
+
}
|
|
222961
|
+
if (!filesToSearch || Object.keys(filesToSearch).length === 0) {
|
|
222962
|
+
filesToSearch = await scanLocalDirectory(validatedPath);
|
|
222963
|
+
}
|
|
222076
222964
|
if (Object.keys(filesToSearch).length === 0) {
|
|
222077
222965
|
throw new ReadCodeError(`No supported source files found in ${projectPath}. Supported extensions: .ts, .tsx, .js, .jsx, .mjs, .cjs, .py, .go, .java, .rb, .php, .rs, .cs, .cpp, .cc, .c, .h, .hpp, .sh, .kt, .swift, .scala, .dart, .ex, .exs, .zig, .lua, .vue, .svelte, .astro, .sql, .prisma, .graphql, .proto`, -32602);
|
|
222078
222966
|
}
|
|
@@ -222151,18 +223039,36 @@ async function handleReadCode(params) {
|
|
|
222151
223039
|
filteredMatches = allMatches.filter((s) => s.kind === kind);
|
|
222152
223040
|
}
|
|
222153
223041
|
const primaryTarget = validatedTargets[0];
|
|
222154
|
-
const
|
|
222155
|
-
|
|
223042
|
+
const isBatched = validatedTargets.length > 1;
|
|
223043
|
+
let scoredMatches;
|
|
223044
|
+
if (isBatched) {
|
|
223045
|
+
const perTargetResults = [];
|
|
223046
|
+
const usedKeys = new Set;
|
|
222156
223047
|
for (const t of validatedTargets) {
|
|
222157
|
-
const
|
|
222158
|
-
|
|
222159
|
-
|
|
223048
|
+
const targetMatches = filteredMatches.map((symbol2) => ({
|
|
223049
|
+
...symbol2,
|
|
223050
|
+
confidence: computeConfidence(symbol2, t, context_path)
|
|
223051
|
+
})).filter((s) => s.confidence >= clampedThreshold).sort((a, b) => b.confidence - a.confidence).slice(0, clampedMaxResults);
|
|
223052
|
+
for (const m of targetMatches) {
|
|
223053
|
+
const key = `${m.name}::${m.file}::${m.startLine}`;
|
|
223054
|
+
if (!usedKeys.has(key)) {
|
|
223055
|
+
usedKeys.add(key);
|
|
223056
|
+
perTargetResults.push(m);
|
|
223057
|
+
}
|
|
223058
|
+
}
|
|
222160
223059
|
}
|
|
222161
|
-
|
|
222162
|
-
|
|
222163
|
-
|
|
222164
|
-
|
|
222165
|
-
|
|
223060
|
+
scoredMatches = perTargetResults.sort((a, b) => b.confidence - a.confidence);
|
|
223061
|
+
} else {
|
|
223062
|
+
scoredMatches = filteredMatches.map((symbol2) => {
|
|
223063
|
+
let bestConfidence = 0;
|
|
223064
|
+
for (const t of validatedTargets) {
|
|
223065
|
+
const conf = computeConfidence(symbol2, t, context_path);
|
|
223066
|
+
if (conf > bestConfidence)
|
|
223067
|
+
bestConfidence = conf;
|
|
223068
|
+
}
|
|
223069
|
+
return { ...symbol2, confidence: bestConfidence };
|
|
223070
|
+
}).filter((s) => s.confidence >= clampedThreshold).sort((a, b) => b.confidence - a.confidence).slice(0, clampedMaxResults);
|
|
223071
|
+
}
|
|
222166
223072
|
const totalFileTokens = estimateTotalFileTokens(filesToSearch);
|
|
222167
223073
|
if (scoredMatches.length === 0) {
|
|
222168
223074
|
const insufficientHint = buildInsufficientSourceHint(primaryTarget, inline_files, { tool: "read_code" });
|
|
@@ -222208,7 +223114,7 @@ async function handleReadCode(params) {
|
|
|
222208
223114
|
continue;
|
|
222209
223115
|
}
|
|
222210
223116
|
const fileContent = filesToSearch[symbol2.file] ?? "";
|
|
222211
|
-
const extracted = extractSymbolContent(symbol2, detail_level, fileContent);
|
|
223117
|
+
const extracted = extractSymbolContent(symbol2, detail_level, fileContent, params.compact);
|
|
222212
223118
|
extracted.confidence = symbol2.confidence;
|
|
222213
223119
|
if (params.max_lines && extracted.body) {
|
|
222214
223120
|
const bodyLines = extracted.body.split(`
|
|
@@ -222242,13 +223148,15 @@ async function handleReadCode(params) {
|
|
|
222242
223148
|
totalTokensUsed += extracted.token_estimate;
|
|
222243
223149
|
resultSymbols.push(extracted);
|
|
222244
223150
|
}
|
|
223151
|
+
const shouldIncludeUsages = include_usages || detail_level === "context";
|
|
223152
|
+
const shouldIncludeTests = include_tests || detail_level === "context";
|
|
222245
223153
|
for (const symbol2 of resultSymbols) {
|
|
222246
223154
|
if (symbol2.already_returned_this_session)
|
|
222247
223155
|
continue;
|
|
222248
|
-
if (
|
|
223156
|
+
if (shouldIncludeUsages) {
|
|
222249
223157
|
symbol2.usages = await findUsages(symbol2.name, filesToSearch, 10);
|
|
222250
223158
|
}
|
|
222251
|
-
if (
|
|
223159
|
+
if (shouldIncludeTests) {
|
|
222252
223160
|
symbol2.tests = await findTestFiles(symbol2.name, symbol2.file, filesToSearch);
|
|
222253
223161
|
}
|
|
222254
223162
|
}
|
|
@@ -222284,13 +223192,13 @@ async function handleRemoteRepo(target, url2, options) {
|
|
|
222284
223192
|
];
|
|
222285
223193
|
let fileCount = 0;
|
|
222286
223194
|
let skippedCount = 0;
|
|
222287
|
-
const
|
|
222288
|
-
const
|
|
223195
|
+
const MAX_FILES2 = 200;
|
|
223196
|
+
const MAX_FILE_SIZE3 = 1048576;
|
|
222289
223197
|
for await (const filePath of glob.scan({
|
|
222290
223198
|
cwd: localPath,
|
|
222291
223199
|
absolute: true
|
|
222292
223200
|
})) {
|
|
222293
|
-
if (fileCount >=
|
|
223201
|
+
if (fileCount >= MAX_FILES2) {
|
|
222294
223202
|
skippedCount++;
|
|
222295
223203
|
continue;
|
|
222296
223204
|
}
|
|
@@ -222299,7 +223207,7 @@ async function handleRemoteRepo(target, url2, options) {
|
|
|
222299
223207
|
continue;
|
|
222300
223208
|
try {
|
|
222301
223209
|
const stats = await stat4(filePath);
|
|
222302
|
-
if (stats.size >
|
|
223210
|
+
if (stats.size > MAX_FILE_SIZE3)
|
|
222303
223211
|
continue;
|
|
222304
223212
|
const content = await Bun.file(filePath).text();
|
|
222305
223213
|
if (isBinaryContent(content))
|
|
@@ -222325,7 +223233,7 @@ async function handleRemoteRepo(target, url2, options) {
|
|
|
222325
223233
|
session_id: options.session_id
|
|
222326
223234
|
});
|
|
222327
223235
|
if (skippedCount > 0 && !result.error_hint) {
|
|
222328
|
-
result.error_hint = `Warning: Scanned ${
|
|
223236
|
+
result.error_hint = `Warning: Scanned ${MAX_FILES2} of ${MAX_FILES2 + skippedCount} source files. ${skippedCount} files were skipped. If the symbol wasn't found, try using inline_files with the specific file content, or narrow with context_path.`;
|
|
222329
223237
|
}
|
|
222330
223238
|
return result;
|
|
222331
223239
|
});
|
|
@@ -251222,7 +252130,7 @@ var init_childProcess = __esm(() => {
|
|
|
251222
252130
|
import { execFile } from "node:child_process";
|
|
251223
252131
|
import { readFile as readFile2, readdir as readdir6 } from "node:fs";
|
|
251224
252132
|
import * as os from "node:os";
|
|
251225
|
-
import { join as
|
|
252133
|
+
import { join as join22 } from "node:path";
|
|
251226
252134
|
import { promisify } from "node:util";
|
|
251227
252135
|
function _updateContext(contexts) {
|
|
251228
252136
|
if (contexts.app?.app_memory) {
|
|
@@ -251347,7 +252255,7 @@ async function getLinuxInfo() {
|
|
|
251347
252255
|
if (!distroFile) {
|
|
251348
252256
|
return linuxInfo;
|
|
251349
252257
|
}
|
|
251350
|
-
const distroPath =
|
|
252258
|
+
const distroPath = join22("/etc", distroFile.name);
|
|
251351
252259
|
const contents = (await readFileAsync(distroPath, { encoding: "utf-8" })).toLowerCase();
|
|
251352
252260
|
const { distros } = distroFile;
|
|
251353
252261
|
linuxInfo.name = distros.find((d) => contents.indexOf(getLinuxDistroId(d)) >= 0) || distros[0];
|
|
@@ -252242,8 +253150,8 @@ var init_detection = __esm(() => {
|
|
|
252242
253150
|
});
|
|
252243
253151
|
|
|
252244
253152
|
// node_modules/.bun/@sentry+node-core@10.50.0+07afda4183cd9459/node_modules/@sentry/node-core/build/esm/integrations/modules.js
|
|
252245
|
-
import { existsSync as
|
|
252246
|
-
import { dirname as dirname5, join as
|
|
253153
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
|
|
253154
|
+
import { dirname as dirname5, join as join23 } from "node:path";
|
|
252247
253155
|
function getRequireCachePaths() {
|
|
252248
253156
|
try {
|
|
252249
253157
|
return __require.cache ? Object.keys(__require.cache) : [];
|
|
@@ -252274,9 +253182,9 @@ function collectRequireModules() {
|
|
|
252274
253182
|
if (mainPaths.indexOf(dir) < 0) {
|
|
252275
253183
|
return updir();
|
|
252276
253184
|
}
|
|
252277
|
-
const pkgfile =
|
|
253185
|
+
const pkgfile = join23(orig, "package.json");
|
|
252278
253186
|
seen.add(orig);
|
|
252279
|
-
if (!
|
|
253187
|
+
if (!existsSync7(pkgfile)) {
|
|
252280
253188
|
return updir();
|
|
252281
253189
|
}
|
|
252282
253190
|
try {
|
|
@@ -252296,7 +253204,7 @@ function _getModules() {
|
|
|
252296
253204
|
}
|
|
252297
253205
|
function getPackageJson() {
|
|
252298
253206
|
try {
|
|
252299
|
-
const filePath =
|
|
253207
|
+
const filePath = join23(process.cwd(), "package.json");
|
|
252300
253208
|
const packageJson = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
252301
253209
|
return packageJson;
|
|
252302
253210
|
} catch {
|
|
@@ -274256,7 +275164,7 @@ var init_findCodeSchema = __esm(() => {
|
|
|
274256
275164
|
});
|
|
274257
275165
|
|
|
274258
275166
|
// src/tools/search/findCode.ts
|
|
274259
|
-
import { delimiter as delimiter2, isAbsolute as isAbsolute8, join as
|
|
275167
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute8, join as join24, normalize as normalize7, relative as relative3 } from "path";
|
|
274260
275168
|
import { access as access7, realpath as realpath4 } from "fs/promises";
|
|
274261
275169
|
import { constants as constants3 } from "node:fs";
|
|
274262
275170
|
import { spawn as spawn8 } from "node:child_process";
|
|
@@ -274286,7 +275194,7 @@ async function resolveRipgrepExecutable2() {
|
|
|
274286
275194
|
const exeName = process.platform === "win32" ? "rg.exe" : "rg";
|
|
274287
275195
|
const pathParts = pathEnv.split(delimiter2).map((p) => p.trim()).filter(Boolean);
|
|
274288
275196
|
for (const dir of pathParts) {
|
|
274289
|
-
const candidate =
|
|
275197
|
+
const candidate = join24(dir, exeName);
|
|
274290
275198
|
try {
|
|
274291
275199
|
if (process.platform === "win32") {
|
|
274292
275200
|
await access7(candidate);
|
|
@@ -275006,7 +275914,7 @@ async function _executeSearch({
|
|
|
275006
275914
|
return filesToSearch[file2];
|
|
275007
275915
|
}
|
|
275008
275916
|
try {
|
|
275009
|
-
const fullPath =
|
|
275917
|
+
const fullPath = join24(validatedPath, file2);
|
|
275010
275918
|
const realFilePath = await resolveReal(fullPath);
|
|
275011
275919
|
if (!realFilePath.startsWith(validatedPath + "/") && realFilePath !== validatedPath) {
|
|
275012
275920
|
return "";
|
|
@@ -277469,7 +278377,7 @@ var init_scope_task_rate_limit = __esm(() => {
|
|
|
277469
278377
|
// src/tools/scope_task/index.ts
|
|
277470
278378
|
import { spawn as spawn9 } from "node:child_process";
|
|
277471
278379
|
import { access as access8, realpath as realpath5, readFile as readFile3 } from "fs/promises";
|
|
277472
|
-
import { isAbsolute as isAbsolute9, normalize as normalize8, join as
|
|
278380
|
+
import { isAbsolute as isAbsolute9, normalize as normalize8, join as join25, dirname as dirname6, basename as basename3 } from "path";
|
|
277473
278381
|
function iterativeUrlDecode4(input) {
|
|
277474
278382
|
let decoded = input;
|
|
277475
278383
|
let previous;
|
|
@@ -277768,11 +278676,11 @@ async function extractImportsFromFile(filePath, projectPath) {
|
|
|
277768
278676
|
const lang = detectLanguage2(filePath);
|
|
277769
278677
|
if (!lang)
|
|
277770
278678
|
return [];
|
|
277771
|
-
const fullPath =
|
|
278679
|
+
const fullPath = join25(projectPath, filePath);
|
|
277772
278680
|
const file2 = Bun.file(fullPath);
|
|
277773
|
-
const
|
|
278681
|
+
const MAX_FILE_SIZE3 = 1024 * 1024;
|
|
277774
278682
|
const fileSize = file2.size;
|
|
277775
|
-
if (fileSize >
|
|
278683
|
+
if (fileSize > MAX_FILE_SIZE3)
|
|
277776
278684
|
return [];
|
|
277777
278685
|
const content = await file2.text();
|
|
277778
278686
|
const tree = await parseCode(content, lang);
|
|
@@ -277845,7 +278753,7 @@ async function extractImportsFromFile(filePath, projectPath) {
|
|
|
277845
278753
|
}
|
|
277846
278754
|
} else {
|
|
277847
278755
|
const dir = dirname6(filePath);
|
|
277848
|
-
candidatePath = normalize8(
|
|
278756
|
+
candidatePath = normalize8(join25(dir, source));
|
|
277849
278757
|
}
|
|
277850
278758
|
} else if (DOT_MODULE.has(lang)) {
|
|
277851
278759
|
candidatePath = source.replace(/\./g, "/");
|
|
@@ -277863,7 +278771,7 @@ async function extractImportsFromFile(filePath, projectPath) {
|
|
|
277863
278771
|
const KNOWN_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|cs|php|cpp|c|cc|cxx|h|hpp|rb|sh|bash)$/i;
|
|
277864
278772
|
if (KNOWN_EXT_RE.test(candidatePath)) {
|
|
277865
278773
|
try {
|
|
277866
|
-
await access8(
|
|
278774
|
+
await access8(join25(projectPath, candidatePath));
|
|
277867
278775
|
resolved = candidatePath;
|
|
277868
278776
|
} catch {}
|
|
277869
278777
|
}
|
|
@@ -277871,7 +278779,7 @@ async function extractImportsFromFile(filePath, projectPath) {
|
|
|
277871
278779
|
for (const ext of extFallbacks) {
|
|
277872
278780
|
const candidate = candidatePath + ext;
|
|
277873
278781
|
try {
|
|
277874
|
-
await access8(
|
|
278782
|
+
await access8(join25(projectPath, candidate));
|
|
277875
278783
|
resolved = candidate;
|
|
277876
278784
|
break;
|
|
277877
278785
|
} catch {}
|
|
@@ -277901,7 +278809,7 @@ async function findUtilitiesByBehavior(allMatches, symbols, projectPath) {
|
|
|
277901
278809
|
const frequentlyImported = Array.from(importCounts.entries()).filter(([, count2]) => count2 >= 3).map(([file2]) => file2);
|
|
277902
278810
|
for (const file2 of frequentlyImported.slice(0, 10)) {
|
|
277903
278811
|
try {
|
|
277904
|
-
const fullPath =
|
|
278812
|
+
const fullPath = join25(projectPath, file2);
|
|
277905
278813
|
const content = await readFile3(fullPath, "utf-8").catch(() => null);
|
|
277906
278814
|
if (!content)
|
|
277907
278815
|
continue;
|
|
@@ -277972,7 +278880,7 @@ function assessRisk(callers, files, allMatches) {
|
|
|
277972
278880
|
}
|
|
277973
278881
|
async function extractFilePreview(filePath, projectPath) {
|
|
277974
278882
|
try {
|
|
277975
|
-
const fullPath =
|
|
278883
|
+
const fullPath = join25(projectPath, filePath);
|
|
277976
278884
|
const content = await readFile3(fullPath, "utf-8");
|
|
277977
278885
|
const lines = content.split(`
|
|
277978
278886
|
`);
|
|
@@ -279864,10 +280772,15 @@ async function handleKnowledgeBase(name2, params) {
|
|
|
279864
280772
|
if (name2 !== KNOWLEDGE_BASE_TOOL_NAME) {
|
|
279865
280773
|
throw new Error(`Unknown tool: ${name2}`);
|
|
279866
280774
|
}
|
|
280775
|
+
const operation = typeof params.operation === "string" ? params.operation : "search";
|
|
279867
280776
|
const query = typeof params.query === "string" ? params.query.trim() : "";
|
|
280777
|
+
const slug = typeof params.slug === "string" ? params.slug.trim() : "";
|
|
279868
280778
|
const category = typeof params.category === "string" ? params.category : undefined;
|
|
279869
|
-
if (!query) {
|
|
279870
|
-
throw new Error("query is required");
|
|
280779
|
+
if (operation === "search" && !query) {
|
|
280780
|
+
throw new Error("query is required for search operation");
|
|
280781
|
+
}
|
|
280782
|
+
if (operation === "get" && !slug) {
|
|
280783
|
+
throw new Error("slug is required for get operation");
|
|
279871
280784
|
}
|
|
279872
280785
|
const KB_URL = process.env.KB_SERVICE_URL;
|
|
279873
280786
|
const KB_SECRET = process.env.KB_SECRET;
|
|
@@ -279876,7 +280789,14 @@ async function handleKnowledgeBase(name2, params) {
|
|
|
279876
280789
|
content: [
|
|
279877
280790
|
{
|
|
279878
280791
|
type: "text",
|
|
279879
|
-
text:
|
|
280792
|
+
text: `KB_SERVICE_URL not configured.
|
|
280793
|
+
|
|
280794
|
+
` + `Zephex_dev_info requires a separate Knowledge Base (KB) service.
|
|
280795
|
+
|
|
280796
|
+
` + `To fix:
|
|
280797
|
+
` + `1. Deploy the /kb directory as a separate service.
|
|
280798
|
+
` + `2. Set KB_SERVICE_URL on the mcp-proxy service to the KB service URL.
|
|
280799
|
+
` + "3. (Optional) Set KB_SECRET on both services for authentication."
|
|
279880
280800
|
}
|
|
279881
280801
|
]
|
|
279882
280802
|
};
|
|
@@ -279892,7 +280812,7 @@ async function handleKnowledgeBase(name2, params) {
|
|
|
279892
280812
|
const response = await fetch(`${KB_URL}/query`, {
|
|
279893
280813
|
method: "POST",
|
|
279894
280814
|
headers,
|
|
279895
|
-
body: JSON.stringify({ query, category }),
|
|
280815
|
+
body: JSON.stringify({ operation, query, slug, category }),
|
|
279896
280816
|
signal: controller.signal
|
|
279897
280817
|
});
|
|
279898
280818
|
clearTimeout(timeout);
|
|
@@ -279901,7 +280821,7 @@ async function handleKnowledgeBase(name2, params) {
|
|
|
279901
280821
|
content: [
|
|
279902
280822
|
{
|
|
279903
280823
|
type: "text",
|
|
279904
|
-
text: `No KB entry found for: "${query}". Try rephrasing or different category.`
|
|
280824
|
+
text: `No KB entry found for: "${query || slug}". Try rephrasing or different category.`
|
|
279905
280825
|
}
|
|
279906
280826
|
]
|
|
279907
280827
|
};
|
|
@@ -279944,26 +280864,37 @@ var init_kb = __esm(async () => {
|
|
|
279944
280864
|
KNOWLEDGE_BASE_TOOL = {
|
|
279945
280865
|
name: "Zephex_dev_info",
|
|
279946
280866
|
title: "Zephex Developer Knowledge Base",
|
|
279947
|
-
description: "Expert developer knowledge base by Zephex. Use when user asks
|
|
280867
|
+
description: "Expert developer knowledge base by Zephex. Use when the user asks how to build, structure, secure, or deploy anything. Covers: database schemas (Stripe, Supabase, Convex, Postgres), security (CSP, CORS, OWASP, JWT hardening), frontend (Next.js, React 19, Tailwind CSS), authentication (Supabase Auth, OAuth, refresh tokens), backend (AWS ECS, Docker, Bun), and mobile (Android, iOS, Expo, Play Store signing). Two operations: use 'search' with a query to find the right entry, then 'get' with the returned slug to fetch full expert knowledge. Always search first, then get.",
|
|
279948
280868
|
inputSchema: {
|
|
279949
280869
|
type: "object",
|
|
279950
280870
|
properties: {
|
|
280871
|
+
operation: {
|
|
280872
|
+
type: "string",
|
|
280873
|
+
enum: ["search", "get"],
|
|
280874
|
+
default: "search",
|
|
280875
|
+
description: "Use 'search' to find the right entry by query. " + "Use 'get' to fetch full expert knowledge by slug. " + "Always search first, then get."
|
|
280876
|
+
},
|
|
279951
280877
|
query: {
|
|
279952
280878
|
type: "string",
|
|
279953
280879
|
minLength: 1,
|
|
279954
|
-
description: "
|
|
280880
|
+
description: "Required for 'search'. Natural language question."
|
|
280881
|
+
},
|
|
280882
|
+
slug: {
|
|
280883
|
+
type: "string",
|
|
280884
|
+
description: "Required for 'get'. Exact slug from a previous search result."
|
|
279955
280885
|
},
|
|
279956
280886
|
category: {
|
|
279957
280887
|
type: "string",
|
|
279958
280888
|
enum: [
|
|
279959
280889
|
"databases",
|
|
279960
|
-
"mobile",
|
|
279961
|
-
"frontend",
|
|
279962
280890
|
"security",
|
|
279963
|
-
"
|
|
279964
|
-
"
|
|
280891
|
+
"frontend",
|
|
280892
|
+
"backend",
|
|
280893
|
+
"auth",
|
|
280894
|
+
"mobile",
|
|
280895
|
+
"android"
|
|
279965
280896
|
],
|
|
279966
|
-
description: "Optional
|
|
280897
|
+
description: "Optional filter for 'search'. Narrows results to one domain."
|
|
279967
280898
|
}
|
|
279968
280899
|
},
|
|
279969
280900
|
required: ["query"]
|
|
@@ -280040,7 +280971,7 @@ await __promiseAll([
|
|
|
280040
280971
|
]);
|
|
280041
280972
|
var API_KEY = (process.env.ZEPHEX_API_KEY || "").trim();
|
|
280042
280973
|
if (!process.env.KB_SERVICE_URL) {
|
|
280043
|
-
console.error("[WARN] KB_SERVICE_URL not set — Zephex_dev_info will return config errors");
|
|
280974
|
+
console.error("[WARN] KB_SERVICE_URL not set — Zephex_dev_info will return config errors. " + "Deploy the /kb service and set KB_SERVICE_URL to its URL.");
|
|
280044
280975
|
}
|
|
280045
280976
|
if (!API_KEY) {
|
|
280046
280977
|
process.stderr.write(`
|