@side-quest/kit 0.0.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 +36 -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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST Search Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for AST-based code search using tree-sitter.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Search mode for AST patterns.
|
|
9
|
+
*
|
|
10
|
+
* - `simple`: Natural language patterns like "async function", "class"
|
|
11
|
+
* - `pattern`: JSON object criteria like {"type": "function_declaration"}
|
|
12
|
+
*/
|
|
13
|
+
export enum SearchMode {
|
|
14
|
+
/** Natural language patterns: "async function", "class", "try" */
|
|
15
|
+
SIMPLE = 'simple',
|
|
16
|
+
/** JSON criteria: {"type": "function_declaration", "async": true} */
|
|
17
|
+
PATTERN = 'pattern',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A single AST match found during search.
|
|
22
|
+
*/
|
|
23
|
+
export interface ASTMatch {
|
|
24
|
+
/** File path relative to repository root */
|
|
25
|
+
file: string
|
|
26
|
+
/** Line number (1-indexed) */
|
|
27
|
+
line: number
|
|
28
|
+
/** Column number (0-indexed) */
|
|
29
|
+
column: number
|
|
30
|
+
/** Tree-sitter node type (e.g., "function_declaration") */
|
|
31
|
+
nodeType: string
|
|
32
|
+
/** Matched source text (truncated to 500 chars) */
|
|
33
|
+
text: string
|
|
34
|
+
/** Context about where the match was found */
|
|
35
|
+
context: ASTMatchContext
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Context information for an AST match.
|
|
40
|
+
*/
|
|
41
|
+
export interface ASTMatchContext {
|
|
42
|
+
/** The tree-sitter node type */
|
|
43
|
+
nodeType: string
|
|
44
|
+
/** Parent function name, if match is inside a function */
|
|
45
|
+
parentFunction?: string
|
|
46
|
+
/** Parent class name, if match is inside a class */
|
|
47
|
+
parentClass?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Options for AST search.
|
|
52
|
+
*/
|
|
53
|
+
export interface ASTSearchOptions {
|
|
54
|
+
/** Search pattern (natural language or JSON depending on mode) */
|
|
55
|
+
pattern: string
|
|
56
|
+
/** Search mode: 'simple' or 'pattern' */
|
|
57
|
+
mode?: SearchMode
|
|
58
|
+
/** File glob pattern (default: all supported files) */
|
|
59
|
+
filePattern?: string
|
|
60
|
+
/** Repository path to search */
|
|
61
|
+
path?: string
|
|
62
|
+
/** Maximum results to return (default: 100) */
|
|
63
|
+
maxResults?: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Result of an AST search operation.
|
|
68
|
+
*/
|
|
69
|
+
export interface ASTSearchResult {
|
|
70
|
+
/** Number of matches found */
|
|
71
|
+
count: number
|
|
72
|
+
/** Array of AST matches */
|
|
73
|
+
matches: ASTMatch[]
|
|
74
|
+
/** The search pattern used */
|
|
75
|
+
pattern: string
|
|
76
|
+
/** The search mode used */
|
|
77
|
+
mode: SearchMode
|
|
78
|
+
/** Repository path searched */
|
|
79
|
+
path: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Criteria for pattern mode matching.
|
|
84
|
+
*/
|
|
85
|
+
export interface PatternCriteria {
|
|
86
|
+
/** Tree-sitter node type to match */
|
|
87
|
+
type?: string
|
|
88
|
+
/** Whether node should be async */
|
|
89
|
+
async?: boolean
|
|
90
|
+
/** Text that must appear in the node */
|
|
91
|
+
textMatch?: string
|
|
92
|
+
/** Name of the symbol to match */
|
|
93
|
+
name?: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Timeout for AST search operations (ms).
|
|
98
|
+
*/
|
|
99
|
+
export const AST_SEARCH_TIMEOUT = 60000
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callers command - Find who calls a function
|
|
3
|
+
*
|
|
4
|
+
* Uses kit grep to find all call sites of a function, filtering out the
|
|
5
|
+
* definition itself to show only actual usage locations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { color, OutputFormat } from '../formatters/output'
|
|
9
|
+
import { executeKitGrep } from '../kit-wrapper'
|
|
10
|
+
import { findSymbol, loadProjectIndex } from '../utils/index-parser'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Call site result - a location where the function is called
|
|
14
|
+
*/
|
|
15
|
+
interface CallSite {
|
|
16
|
+
file: string
|
|
17
|
+
line: number
|
|
18
|
+
context: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Result of callers search
|
|
23
|
+
*/
|
|
24
|
+
interface CallersResult {
|
|
25
|
+
functionName: string
|
|
26
|
+
definitionFile?: string
|
|
27
|
+
definitionLine?: number
|
|
28
|
+
callSites: CallSite[]
|
|
29
|
+
count: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format callers result as markdown
|
|
34
|
+
*
|
|
35
|
+
* @param result - Callers result to format
|
|
36
|
+
* @returns Formatted markdown string
|
|
37
|
+
*/
|
|
38
|
+
function formatMarkdown(result: CallersResult): string {
|
|
39
|
+
const { functionName, definitionFile, definitionLine, callSites, count } =
|
|
40
|
+
result
|
|
41
|
+
|
|
42
|
+
if (count === 0) {
|
|
43
|
+
return color(
|
|
44
|
+
'yellow',
|
|
45
|
+
`\n⚠️ No call sites found for function: ${functionName}\n`,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let output = color('cyan', `\n📞 Found ${count} call site(s) for: `)
|
|
50
|
+
output += color('blue', functionName)
|
|
51
|
+
output += '\n\n'
|
|
52
|
+
|
|
53
|
+
// Show definition location if available
|
|
54
|
+
if (definitionFile && definitionLine) {
|
|
55
|
+
output += color(
|
|
56
|
+
'dim',
|
|
57
|
+
`Definition: ${definitionFile}:${definitionLine}\n\n`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Group by file
|
|
62
|
+
const byFile = new Map<string, CallSite[]>()
|
|
63
|
+
for (const callSite of callSites) {
|
|
64
|
+
if (!byFile.has(callSite.file)) {
|
|
65
|
+
byFile.set(callSite.file, [])
|
|
66
|
+
}
|
|
67
|
+
byFile.get(callSite.file)?.push(callSite)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Output each file
|
|
71
|
+
for (const [file, sites] of byFile.entries()) {
|
|
72
|
+
output += color('dim', `${file}:\n`)
|
|
73
|
+
for (const site of sites) {
|
|
74
|
+
output += ` ${color('dim', `L${site.line}:`)} ${site.context.trim()}\n`
|
|
75
|
+
}
|
|
76
|
+
output += '\n'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return output
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format callers result as JSON
|
|
84
|
+
*
|
|
85
|
+
* @param result - Callers result to format
|
|
86
|
+
* @returns JSON string
|
|
87
|
+
*/
|
|
88
|
+
function formatJSON(result: CallersResult): string {
|
|
89
|
+
return JSON.stringify(result, null, 2)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Execute callers command
|
|
94
|
+
*
|
|
95
|
+
* Finds all locations where a function is called by:
|
|
96
|
+
* 1. Looking up the function definition in PROJECT_INDEX.json
|
|
97
|
+
* 2. Using kit grep to find all occurrences
|
|
98
|
+
* 3. Filtering out the definition line to show only call sites
|
|
99
|
+
*
|
|
100
|
+
* @param functionName - Name of the function to find callers for
|
|
101
|
+
* @param format - Output format (markdown or JSON)
|
|
102
|
+
*/
|
|
103
|
+
export async function executeCallers(
|
|
104
|
+
functionName: string,
|
|
105
|
+
format: OutputFormat,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
try {
|
|
108
|
+
// Find the function definition in the index
|
|
109
|
+
let definitionFile: string | undefined
|
|
110
|
+
let definitionLine: number | undefined
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const index = await loadProjectIndex()
|
|
114
|
+
const definitions = findSymbol(index, functionName)
|
|
115
|
+
|
|
116
|
+
// Use the first definition found (functions should be unique)
|
|
117
|
+
const firstDef = definitions[0]
|
|
118
|
+
if (firstDef) {
|
|
119
|
+
definitionFile = firstDef.file
|
|
120
|
+
definitionLine = firstDef.symbol.start_line
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
// Index not available - we'll proceed without filtering the definition
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Use kit grep to find all occurrences
|
|
127
|
+
const grepResult = executeKitGrep({
|
|
128
|
+
pattern: functionName,
|
|
129
|
+
caseSensitive: true,
|
|
130
|
+
maxResults: 500,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Handle errors
|
|
134
|
+
if ('error' in grepResult) {
|
|
135
|
+
if (format === OutputFormat.JSON) {
|
|
136
|
+
console.error(
|
|
137
|
+
JSON.stringify(
|
|
138
|
+
{
|
|
139
|
+
error: grepResult.error,
|
|
140
|
+
functionName,
|
|
141
|
+
isError: true,
|
|
142
|
+
},
|
|
143
|
+
null,
|
|
144
|
+
2,
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
} else {
|
|
148
|
+
console.error(color('red', '\n❌ Error:'), grepResult.error, '\n')
|
|
149
|
+
}
|
|
150
|
+
process.exit(1)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Filter matches to exclude the definition line
|
|
154
|
+
let matches = grepResult.matches
|
|
155
|
+
|
|
156
|
+
if (definitionFile && definitionLine) {
|
|
157
|
+
matches = matches.filter(
|
|
158
|
+
(match) =>
|
|
159
|
+
!(match.file === definitionFile && match.line === definitionLine),
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Filter out matches that look like definitions rather than calls
|
|
164
|
+
// Heuristics:
|
|
165
|
+
// - Lines starting with "function ", "const ", "let ", "var ", "export function"
|
|
166
|
+
// - Lines with "= function" or "= (" (function assignments)
|
|
167
|
+
// - Lines with "export async function" (exported async functions)
|
|
168
|
+
matches = matches.filter((match) => {
|
|
169
|
+
const trimmed = match.content.trim()
|
|
170
|
+
const definitionPatterns = [
|
|
171
|
+
/^function\s+/, // function declarations
|
|
172
|
+
/^export\s+(async\s+)?function\s+/, // exported functions (sync or async)
|
|
173
|
+
/^(const|let|var)\s+\w+\s*=\s*function/, // function expressions
|
|
174
|
+
/^(const|let|var)\s+\w+\s*=\s*\(/, // arrow functions
|
|
175
|
+
/^(const|let|var)\s+\w+\s*=\s*async\s*\(/, // async arrow functions
|
|
176
|
+
/^async\s+function\s+/, // async function declarations
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
return !definitionPatterns.some((pattern) => pattern.test(trimmed))
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// Convert to call sites
|
|
183
|
+
const callSites: CallSite[] = matches.map((match) => ({
|
|
184
|
+
file: match.file,
|
|
185
|
+
line: match.line || 0,
|
|
186
|
+
context: match.content,
|
|
187
|
+
}))
|
|
188
|
+
|
|
189
|
+
// Build result
|
|
190
|
+
const result: CallersResult = {
|
|
191
|
+
functionName,
|
|
192
|
+
definitionFile,
|
|
193
|
+
definitionLine,
|
|
194
|
+
callSites,
|
|
195
|
+
count: callSites.length,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Output results
|
|
199
|
+
if (format === OutputFormat.JSON) {
|
|
200
|
+
console.log(formatJSON(result))
|
|
201
|
+
} else {
|
|
202
|
+
console.log(formatMarkdown(result))
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (format === OutputFormat.JSON) {
|
|
206
|
+
console.error(
|
|
207
|
+
JSON.stringify(
|
|
208
|
+
{
|
|
209
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
210
|
+
functionName,
|
|
211
|
+
isError: true,
|
|
212
|
+
},
|
|
213
|
+
null,
|
|
214
|
+
2,
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
} else {
|
|
218
|
+
console.error(
|
|
219
|
+
color('red', '\n❌ Error:'),
|
|
220
|
+
error instanceof Error ? error.message : error,
|
|
221
|
+
'\n',
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
process.exit(1)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find command - Locate symbol definitions by name
|
|
3
|
+
*
|
|
4
|
+
* Searches PROJECT_INDEX.json for symbol definitions with exact or fuzzy matching.
|
|
5
|
+
* Supports dual output formats (markdown table or JSON).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { color, OutputFormat } from '../formatters/output'
|
|
9
|
+
import {
|
|
10
|
+
findSymbol,
|
|
11
|
+
findSymbolFuzzy,
|
|
12
|
+
type Symbol as IndexSymbol,
|
|
13
|
+
loadProjectIndex,
|
|
14
|
+
} from '../utils/index-parser'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format symbol results as markdown table
|
|
18
|
+
*/
|
|
19
|
+
function formatMarkdown(
|
|
20
|
+
results: Array<{ file: string; symbol: IndexSymbol }>,
|
|
21
|
+
query: string,
|
|
22
|
+
): string {
|
|
23
|
+
if (results.length === 0) {
|
|
24
|
+
return color('yellow', `\n⚠️ No symbols found matching: ${query}\n`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let output = color('cyan', `\n📍 Found ${results.length} symbol(s)\n\n`)
|
|
28
|
+
|
|
29
|
+
// Group by file
|
|
30
|
+
const byFile = new Map<string, IndexSymbol[]>()
|
|
31
|
+
for (const { file, symbol } of results) {
|
|
32
|
+
if (!byFile.has(file)) {
|
|
33
|
+
byFile.set(file, [])
|
|
34
|
+
}
|
|
35
|
+
byFile.get(file)?.push(symbol)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Output each file
|
|
39
|
+
for (const [file, symbols] of byFile.entries()) {
|
|
40
|
+
output += color('dim', `${file}:\n`)
|
|
41
|
+
for (const symbol of symbols) {
|
|
42
|
+
const typeColor = getTypeColor(symbol.type)
|
|
43
|
+
output += ` ${color('dim', '•')} ${color(typeColor, symbol.type.padEnd(10))} ${color('blue', symbol.name)} ${color('dim', `(line ${symbol.start_line}`)}\n`
|
|
44
|
+
}
|
|
45
|
+
output += '\n'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return output
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get color for symbol type
|
|
53
|
+
*/
|
|
54
|
+
function getTypeColor(
|
|
55
|
+
type: string,
|
|
56
|
+
): 'blue' | 'green' | 'magenta' | 'cyan' | 'yellow' {
|
|
57
|
+
switch (type) {
|
|
58
|
+
case 'function':
|
|
59
|
+
return 'blue'
|
|
60
|
+
case 'class':
|
|
61
|
+
return 'green'
|
|
62
|
+
case 'interface':
|
|
63
|
+
case 'type':
|
|
64
|
+
return 'magenta'
|
|
65
|
+
case 'constant':
|
|
66
|
+
return 'cyan'
|
|
67
|
+
default:
|
|
68
|
+
return 'yellow'
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Format symbol results as JSON
|
|
74
|
+
*/
|
|
75
|
+
function formatJSON(
|
|
76
|
+
results: Array<{ file: string; symbol: IndexSymbol }>,
|
|
77
|
+
query: string,
|
|
78
|
+
): string {
|
|
79
|
+
return JSON.stringify(
|
|
80
|
+
{
|
|
81
|
+
query,
|
|
82
|
+
count: results.length,
|
|
83
|
+
results: results.map(({ file, symbol }) => ({
|
|
84
|
+
file,
|
|
85
|
+
name: symbol.name,
|
|
86
|
+
type: symbol.type,
|
|
87
|
+
line: symbol.start_line,
|
|
88
|
+
code: symbol.code,
|
|
89
|
+
})),
|
|
90
|
+
},
|
|
91
|
+
null,
|
|
92
|
+
2,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Execute find command
|
|
98
|
+
*
|
|
99
|
+
* @param symbol - Symbol name to search for
|
|
100
|
+
* @param format - Output format (markdown or JSON)
|
|
101
|
+
*/
|
|
102
|
+
export async function executeFind(
|
|
103
|
+
symbol: string,
|
|
104
|
+
format: OutputFormat,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
const index = await loadProjectIndex()
|
|
108
|
+
|
|
109
|
+
// Try exact match first
|
|
110
|
+
let results: Array<{ file: string; symbol: IndexSymbol }> = findSymbol(
|
|
111
|
+
index,
|
|
112
|
+
symbol,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
// If no exact match, try fuzzy search
|
|
116
|
+
if (results.length === 0) {
|
|
117
|
+
const fuzzyResults = findSymbolFuzzy(index, symbol)
|
|
118
|
+
if (fuzzyResults.length > 0 && format === OutputFormat.MARKDOWN) {
|
|
119
|
+
console.log(
|
|
120
|
+
color(
|
|
121
|
+
'yellow',
|
|
122
|
+
`\n⚠️ No exact match for "${symbol}". Showing fuzzy matches:\n`,
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
results = fuzzyResults.map(({ file, symbol: sym }) => ({
|
|
127
|
+
file,
|
|
128
|
+
symbol: sym,
|
|
129
|
+
}))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Output results
|
|
133
|
+
if (format === OutputFormat.JSON) {
|
|
134
|
+
console.log(formatJSON(results, symbol))
|
|
135
|
+
} else {
|
|
136
|
+
console.log(formatMarkdown(results, symbol))
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (format === OutputFormat.JSON) {
|
|
140
|
+
console.error(
|
|
141
|
+
JSON.stringify(
|
|
142
|
+
{
|
|
143
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
144
|
+
isError: true,
|
|
145
|
+
},
|
|
146
|
+
null,
|
|
147
|
+
2,
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
} else {
|
|
151
|
+
console.error(
|
|
152
|
+
color('red', '\n❌ Error:'),
|
|
153
|
+
error instanceof Error ? error.message : error,
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
process.exit(1)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overview command - List all symbols in a file
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { color, OutputFormat } from '../formatters/output'
|
|
6
|
+
import {
|
|
7
|
+
getFileSymbols,
|
|
8
|
+
type Symbol as IndexSymbol,
|
|
9
|
+
loadProjectIndex,
|
|
10
|
+
} from '../utils/index-parser'
|
|
11
|
+
|
|
12
|
+
export async function executeOverview(
|
|
13
|
+
file: string,
|
|
14
|
+
format: OutputFormat,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
try {
|
|
17
|
+
const index = await loadProjectIndex()
|
|
18
|
+
const symbols = getFileSymbols(index, file)
|
|
19
|
+
|
|
20
|
+
if (symbols.length === 0) {
|
|
21
|
+
if (format === OutputFormat.JSON) {
|
|
22
|
+
console.log(JSON.stringify({ file, symbols: [] }, null, 2))
|
|
23
|
+
} else {
|
|
24
|
+
console.log(color('yellow', `\n⚠️ No symbols found in: ${file}\n`))
|
|
25
|
+
}
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (format === OutputFormat.JSON) {
|
|
30
|
+
console.log(
|
|
31
|
+
JSON.stringify({ file, count: symbols.length, symbols }, null, 2),
|
|
32
|
+
)
|
|
33
|
+
} else {
|
|
34
|
+
// Group by type
|
|
35
|
+
const grouped = symbols.reduce(
|
|
36
|
+
(acc, sym) => {
|
|
37
|
+
if (!acc[sym.type]) acc[sym.type] = []
|
|
38
|
+
acc[sym.type]?.push(sym)
|
|
39
|
+
return acc
|
|
40
|
+
},
|
|
41
|
+
{} as Record<string, IndexSymbol[]>,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
console.log(color('cyan', `\n📄 ${file}\n`))
|
|
45
|
+
console.log(color('dim', `Total symbols: ${symbols.length}\n`))
|
|
46
|
+
|
|
47
|
+
for (const [type, syms] of Object.entries(grouped)) {
|
|
48
|
+
console.log(color('magenta', `${type}s:`))
|
|
49
|
+
for (const sym of syms) {
|
|
50
|
+
console.log(
|
|
51
|
+
` ${color('dim', '•')} ${color('blue', sym.name)} ${color('dim', `(line ${sym.start_line})`)}`,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
console.log('')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(
|
|
59
|
+
format === OutputFormat.JSON
|
|
60
|
+
? JSON.stringify(
|
|
61
|
+
{
|
|
62
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
63
|
+
isError: true,
|
|
64
|
+
},
|
|
65
|
+
null,
|
|
66
|
+
2,
|
|
67
|
+
)
|
|
68
|
+
: color('red', '\n❌ Error:') +
|
|
69
|
+
` ${error instanceof Error ? error.message : error}`,
|
|
70
|
+
)
|
|
71
|
+
process.exit(1)
|
|
72
|
+
}
|
|
73
|
+
}
|