@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.
Files changed (127) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +54 -352
  3. package/dist/cli.d.ts +14 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +156 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/index.d.ts +8 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +8 -2509
  10. package/dist/index.js.map +1 -0
  11. package/dist/lib/ast/index.d.ts +11 -0
  12. package/dist/lib/ast/index.d.ts.map +1 -0
  13. package/dist/lib/ast/index.js +15 -0
  14. package/dist/lib/ast/index.js.map +1 -0
  15. package/dist/lib/ast/languages.d.ts +55 -0
  16. package/dist/lib/ast/languages.d.ts.map +1 -0
  17. package/dist/lib/ast/languages.js +146 -0
  18. package/dist/lib/ast/languages.js.map +1 -0
  19. package/dist/lib/ast/pattern.d.ts +84 -0
  20. package/dist/lib/ast/pattern.d.ts.map +1 -0
  21. package/dist/lib/ast/pattern.js +268 -0
  22. package/dist/lib/ast/pattern.js.map +1 -0
  23. package/dist/lib/ast/searcher.d.ts +89 -0
  24. package/dist/lib/ast/searcher.d.ts.map +1 -0
  25. package/dist/lib/ast/searcher.js +316 -0
  26. package/dist/lib/ast/searcher.js.map +1 -0
  27. package/dist/lib/ast/types.d.ts +93 -0
  28. package/dist/lib/ast/types.d.ts.map +1 -0
  29. package/dist/lib/ast/types.js +23 -0
  30. package/dist/lib/ast/types.js.map +1 -0
  31. package/dist/lib/commands/callers.d.ts +20 -0
  32. package/dist/lib/commands/callers.d.ts.map +1 -0
  33. package/dist/lib/commands/callers.js +162 -0
  34. package/dist/lib/commands/callers.js.map +1 -0
  35. package/dist/lib/commands/find.d.ts +15 -0
  36. package/dist/lib/commands/find.d.ts.map +1 -0
  37. package/dist/lib/commands/find.js +113 -0
  38. package/dist/lib/commands/find.js.map +1 -0
  39. package/dist/lib/commands/overview.d.ts +6 -0
  40. package/dist/lib/commands/overview.d.ts.map +1 -0
  41. package/dist/lib/commands/overview.js +52 -0
  42. package/dist/lib/commands/overview.js.map +1 -0
  43. package/dist/lib/commands/prime.d.ts +16 -0
  44. package/dist/lib/commands/prime.d.ts.map +1 -0
  45. package/dist/lib/commands/prime.js +168 -0
  46. package/dist/lib/commands/prime.js.map +1 -0
  47. package/dist/lib/commands/search.d.ts +20 -0
  48. package/dist/lib/commands/search.d.ts.map +1 -0
  49. package/dist/lib/commands/search.js +111 -0
  50. package/dist/lib/commands/search.js.map +1 -0
  51. package/dist/lib/errors.d.ts +80 -0
  52. package/dist/lib/errors.d.ts.map +1 -0
  53. package/dist/lib/errors.js +189 -0
  54. package/dist/lib/errors.js.map +1 -0
  55. package/dist/lib/formatters/output.d.ts +5 -0
  56. package/dist/lib/formatters/output.d.ts.map +1 -0
  57. package/dist/lib/formatters/output.js +5 -0
  58. package/dist/lib/formatters/output.js.map +1 -0
  59. package/dist/lib/formatters.d.ts +29 -0
  60. package/dist/lib/formatters.d.ts.map +1 -0
  61. package/dist/lib/formatters.js +141 -0
  62. package/dist/lib/formatters.js.map +1 -0
  63. package/dist/lib/index-tools.d.ts +108 -0
  64. package/dist/lib/index-tools.d.ts.map +1 -0
  65. package/dist/lib/index-tools.js +311 -0
  66. package/dist/lib/index-tools.js.map +1 -0
  67. package/dist/lib/index.d.ts +21 -0
  68. package/dist/lib/index.d.ts.map +1 -0
  69. package/dist/lib/index.js +42 -0
  70. package/dist/lib/index.js.map +1 -0
  71. package/dist/lib/kit-wrapper.d.ts +70 -0
  72. package/dist/lib/kit-wrapper.d.ts.map +1 -0
  73. package/dist/lib/kit-wrapper.js +462 -0
  74. package/dist/lib/kit-wrapper.js.map +1 -0
  75. package/dist/lib/logger.d.ts +28 -0
  76. package/dist/lib/logger.d.ts.map +1 -0
  77. package/dist/lib/logger.js +39 -0
  78. package/dist/lib/logger.js.map +1 -0
  79. package/dist/lib/types.d.ts +179 -0
  80. package/dist/lib/types.d.ts.map +1 -0
  81. package/dist/lib/types.js +48 -0
  82. package/dist/lib/types.js.map +1 -0
  83. package/dist/lib/utils/args.d.ts +40 -0
  84. package/dist/lib/utils/args.d.ts.map +1 -0
  85. package/dist/lib/utils/args.js +58 -0
  86. package/dist/lib/utils/args.js.map +1 -0
  87. package/dist/lib/utils/git.d.ts +23 -0
  88. package/dist/lib/utils/git.d.ts.map +1 -0
  89. package/dist/lib/utils/git.js +50 -0
  90. package/dist/lib/utils/git.js.map +1 -0
  91. package/dist/lib/utils/index-parser.d.ts +155 -0
  92. package/dist/lib/utils/index-parser.d.ts.map +1 -0
  93. package/dist/lib/utils/index-parser.js +252 -0
  94. package/dist/lib/utils/index-parser.js.map +1 -0
  95. package/dist/lib/validators.d.ts +138 -0
  96. package/dist/lib/validators.d.ts.map +1 -0
  97. package/dist/lib/validators.js +302 -0
  98. package/dist/lib/validators.js.map +1 -0
  99. package/dist/mcp/index.d.ts +19 -0
  100. package/dist/mcp/index.d.ts.map +1 -0
  101. package/dist/mcp/index.js +769 -0
  102. package/dist/mcp/index.js.map +1 -0
  103. package/package.json +5 -2
  104. package/src/cli.ts +170 -0
  105. package/src/lib/ast/index.ts +32 -0
  106. package/src/lib/ast/languages.ts +172 -0
  107. package/src/lib/ast/pattern.ts +299 -0
  108. package/src/lib/ast/searcher.ts +381 -0
  109. package/src/lib/ast/types.ts +99 -0
  110. package/src/lib/commands/callers.ts +226 -0
  111. package/src/lib/commands/find.ts +159 -0
  112. package/src/lib/commands/overview.ts +73 -0
  113. package/src/lib/commands/prime.ts +271 -0
  114. package/src/lib/commands/search.ts +146 -0
  115. package/src/lib/errors.ts +221 -0
  116. package/src/lib/formatters/output.ts +9 -0
  117. package/src/lib/formatters.ts +189 -0
  118. package/src/lib/index-tools.ts +471 -0
  119. package/src/lib/index.ts +122 -0
  120. package/src/lib/kit-wrapper.ts +675 -0
  121. package/src/lib/logger.ts +57 -0
  122. package/src/lib/types.ts +228 -0
  123. package/src/lib/utils/args.ts +72 -0
  124. package/src/lib/utils/git.ts +65 -0
  125. package/src/lib/utils/index-parser.ts +350 -0
  126. package/src/lib/validators.ts +437 -0
  127. package/src/mcp/index.ts +144 -79
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Prime command - Generate or refresh PROJECT_INDEX.json
3
+ *
4
+ * Migrated from prime-reporter.ts to integrate with kit-index CLI.
5
+ * Supports dual output formats (markdown with colors, or JSON).
6
+ */
7
+
8
+ import { join } from 'node:path'
9
+ import { getFileAgeHours, getFileSizeMB } from '@side-quest/core/fs'
10
+ import {
11
+ ensureCommandAvailable,
12
+ spawnWithTimeout,
13
+ } from '@side-quest/core/spawn'
14
+ import { color, OutputFormat } from '../formatters/output'
15
+ import { getTargetDir, INDEX_FILE, MAX_AGE_HOURS } from '../utils/git.js'
16
+
17
+ interface IndexStats {
18
+ files: number
19
+ symbols: number
20
+ hasTree: boolean
21
+ }
22
+
23
+ /**
24
+ * Parse index stats from PROJECT_INDEX.json
25
+ */
26
+ async function parseIndexStats(indexPath: string): Promise<IndexStats> {
27
+ const file = Bun.file(indexPath)
28
+ const json = (await file.json()) as {
29
+ files: unknown[]
30
+ symbols: Record<string, unknown[]>
31
+ file_tree?: unknown
32
+ }
33
+
34
+ const symbolCount = Object.values(json.symbols).reduce(
35
+ (sum, arr) => sum + arr.length,
36
+ 0,
37
+ )
38
+
39
+ return {
40
+ files: json.files.length,
41
+ symbols: symbolCount,
42
+ hasTree: json.file_tree !== undefined,
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Generate the index using kit CLI
48
+ */
49
+ async function generateIndex(
50
+ targetDir: string,
51
+ indexPath: string,
52
+ ): Promise<{ durationSec: number }> {
53
+ const kitCmd = ensureCommandAvailable('kit')
54
+ const startTime = Date.now()
55
+ const result = await spawnWithTimeout(
56
+ [kitCmd, 'index', targetDir, '-o', indexPath],
57
+ 60_000,
58
+ )
59
+
60
+ if (result.timedOut) {
61
+ throw new Error('kit index timed out')
62
+ }
63
+
64
+ if (result.exitCode !== 0) {
65
+ throw new Error(`kit index failed: ${result.stderr}`)
66
+ }
67
+
68
+ const durationSec = (Date.now() - startTime) / 1000
69
+ return { durationSec }
70
+ }
71
+
72
+ /**
73
+ * Report existing index (markdown format)
74
+ */
75
+ async function reportExistingMarkdown(
76
+ ageHours: number,
77
+ indexPath: string,
78
+ targetDir: string,
79
+ ): Promise<void> {
80
+ const stats = await parseIndexStats(indexPath)
81
+ const size = getFileSizeMB(indexPath)
82
+
83
+ console.log(color('cyan', '\n📊 PROJECT_INDEX.json exists\n'))
84
+ console.log(color('dim', `Location: ${targetDir}`))
85
+ console.log(color('dim', `Age: ${ageHours.toFixed(1)} hours`))
86
+ console.log(color('dim', `Files: ${stats.files}`))
87
+ console.log(color('dim', `Symbols: ${stats.symbols}`))
88
+ console.log(color('dim', `Size: ${size} MB`))
89
+ console.log(
90
+ color(
91
+ 'yellow',
92
+ '\n⚠️ Index is less than 24 hours old. Use --force to regenerate.',
93
+ ),
94
+ )
95
+ }
96
+
97
+ /**
98
+ * Report existing index (JSON format)
99
+ */
100
+ async function reportExistingJSON(
101
+ ageHours: number,
102
+ indexPath: string,
103
+ targetDir: string,
104
+ ): Promise<void> {
105
+ const stats = await parseIndexStats(indexPath)
106
+ const sizeMB = getFileSizeMB(indexPath)
107
+
108
+ console.log(
109
+ JSON.stringify(
110
+ {
111
+ status: 'exists',
112
+ location: targetDir,
113
+ ageHours: Number.parseFloat(ageHours.toFixed(1)),
114
+ files: stats.files,
115
+ symbols: stats.symbols,
116
+ size: `${sizeMB} MB`,
117
+ message: 'Index is less than 24 hours old. Use --force to regenerate.',
118
+ },
119
+ null,
120
+ 2,
121
+ ),
122
+ )
123
+ }
124
+
125
+ /**
126
+ * Report successful generation (markdown format)
127
+ */
128
+ async function reportSuccessMarkdown(
129
+ durationSec: number,
130
+ indexPath: string,
131
+ targetDir: string,
132
+ ): Promise<void> {
133
+ const stats = await parseIndexStats(indexPath)
134
+ const sizeMB = getFileSizeMB(indexPath)
135
+
136
+ console.log(
137
+ color('green', '\n✅ PROJECT_INDEX.json generated successfully\n'),
138
+ )
139
+
140
+ console.log(color('cyan', 'Stats:'))
141
+ console.log(` ${color('dim', '•')} Location: ${color('blue', targetDir)}`)
142
+ console.log(
143
+ ` ${color('dim', '•')} Files indexed: ${color('blue', stats.files.toString())}`,
144
+ )
145
+ console.log(
146
+ ` ${color('dim', '•')} Symbols extracted: ${color('blue', stats.symbols.toString())}`,
147
+ )
148
+ console.log(
149
+ ` ${color('dim', '•')} Index size: ${color('blue', `${sizeMB} MB`)}`,
150
+ )
151
+ console.log(
152
+ ` ${color('dim', '•')} Time taken: ${color('blue', `${durationSec.toFixed(1)}s`)}`,
153
+ )
154
+
155
+ console.log(color('cyan', '\nYou can now use:'))
156
+ console.log(
157
+ ` ${color('dim', '•')} ${color('magenta', 'bun run src/cli.ts find <symbol>')} - Find symbol definitions`,
158
+ )
159
+ console.log(
160
+ ` ${color('dim', '•')} ${color('magenta', 'bun run src/cli.ts callers <fn>')} - Find who calls a function`,
161
+ )
162
+ console.log(
163
+ ` ${color('dim', '•')} ${color('magenta', 'bun run src/cli.ts overview <file>')} - Get file symbol summary`,
164
+ )
165
+ console.log(
166
+ ` ${color('dim', '•')} ${color('magenta', 'bun run src/cli.ts stats')} - Codebase overview`,
167
+ )
168
+ }
169
+
170
+ /**
171
+ * Report successful generation (JSON format)
172
+ */
173
+ async function reportSuccessJSON(
174
+ durationSec: number,
175
+ indexPath: string,
176
+ targetDir: string,
177
+ ): Promise<void> {
178
+ const stats = await parseIndexStats(indexPath)
179
+ const sizeMB = getFileSizeMB(indexPath)
180
+
181
+ console.log(
182
+ JSON.stringify(
183
+ {
184
+ success: true,
185
+ location: targetDir,
186
+ stats: {
187
+ files: stats.files,
188
+ symbols: stats.symbols,
189
+ hasTree: stats.hasTree,
190
+ size: `${sizeMB} MB`,
191
+ durationSec: Number.parseFloat(durationSec.toFixed(1)),
192
+ },
193
+ },
194
+ null,
195
+ 2,
196
+ ),
197
+ )
198
+ }
199
+
200
+ /**
201
+ * Execute prime command
202
+ *
203
+ * @param force - Force regenerate even if index is fresh
204
+ * @param format - Output format (markdown or JSON)
205
+ * @param customPath - Optional custom path to index (defaults to git root or CWD)
206
+ */
207
+ export async function executePrime(
208
+ force: boolean,
209
+ format: OutputFormat,
210
+ customPath?: string,
211
+ ): Promise<void> {
212
+ try {
213
+ // Determine target directory and index path
214
+ const targetDir = await getTargetDir(customPath)
215
+ const indexPath = join(targetDir, INDEX_FILE)
216
+
217
+ const ageHours = getFileAgeHours(indexPath)
218
+
219
+ // Check if index exists and is fresh
220
+ if (ageHours !== null && ageHours <= MAX_AGE_HOURS && !force) {
221
+ if (format === OutputFormat.JSON) {
222
+ await reportExistingJSON(ageHours, indexPath, targetDir)
223
+ } else {
224
+ await reportExistingMarkdown(ageHours, indexPath, targetDir)
225
+ }
226
+ return
227
+ }
228
+
229
+ // Generate progress indicator (markdown only)
230
+ if (format === OutputFormat.MARKDOWN) {
231
+ console.log(color('blue', '▶ Generating index...'))
232
+ }
233
+
234
+ // Generate new index
235
+ const { durationSec } = await generateIndex(targetDir, indexPath)
236
+
237
+ // Report success
238
+ if (format === OutputFormat.JSON) {
239
+ await reportSuccessJSON(durationSec, indexPath, targetDir)
240
+ } else {
241
+ await reportSuccessMarkdown(durationSec, indexPath, targetDir)
242
+ }
243
+ } catch (error) {
244
+ if (format === OutputFormat.JSON) {
245
+ console.error(
246
+ JSON.stringify(
247
+ {
248
+ error: error instanceof Error ? error.message : 'Unknown error',
249
+ isError: true,
250
+ },
251
+ null,
252
+ 2,
253
+ ),
254
+ )
255
+ } else {
256
+ console.error(color('red', '\n❌ Error:'), error)
257
+
258
+ // Check if kit is installed
259
+ try {
260
+ ensureCommandAvailable('kit')
261
+ } catch {
262
+ console.error(color('yellow', '\n💡 Kit CLI not found. Install with:'))
263
+ console.error(color('dim', ' uv tool install cased-kit'))
264
+ console.error(color('dim', ' # or'))
265
+ console.error(color('dim', ' pipx install cased-kit'))
266
+ }
267
+ }
268
+
269
+ process.exit(1)
270
+ }
271
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Semantic search command - Natural language code search
3
+ *
4
+ * Uses kit semantic search to find code by meaning rather than exact text.
5
+ * Gracefully falls back to grep if ML dependencies unavailable.
6
+ */
7
+
8
+ import { color, OutputFormat } from '../formatters/output'
9
+ import { executeKitSemantic } from '../kit-wrapper'
10
+ import type { SemanticMatch, SemanticOptions, SemanticResult } from '../types'
11
+
12
+ /**
13
+ * Execute semantic search command
14
+ *
15
+ * Performs natural language search over codebase using vector embeddings.
16
+ * Shows warning with install instructions if semantic unavailable.
17
+ *
18
+ * @param query - Natural language search query
19
+ * @param options - Search options (path, topK, chunkBy, buildIndex)
20
+ * @param format - Output format (markdown or JSON)
21
+ */
22
+ export async function executeSearch(
23
+ query: string,
24
+ options: Omit<SemanticOptions, 'query'>,
25
+ format: OutputFormat,
26
+ ): Promise<void> {
27
+ try {
28
+ // Execute semantic search
29
+ const result = executeKitSemantic({ query, ...options })
30
+
31
+ // Handle errors
32
+ if ('error' in result) {
33
+ if (format === OutputFormat.JSON) {
34
+ console.error(
35
+ JSON.stringify(
36
+ {
37
+ error: result.error,
38
+ query,
39
+ isError: true,
40
+ },
41
+ null,
42
+ 2,
43
+ ),
44
+ )
45
+ } else {
46
+ console.error(color('red', '\n❌ Error:'), result.error, '\n')
47
+
48
+ // Show install hint if semantic unavailable
49
+ if ('hint' in result && result.hint) {
50
+ console.error(color('yellow', '💡 Tip:'), result.hint, '\n')
51
+ }
52
+ }
53
+ process.exit(1)
54
+ }
55
+
56
+ // Show fallback warning if grep was used
57
+ if (result.fallback && format === OutputFormat.MARKDOWN) {
58
+ console.log(
59
+ color(
60
+ 'yellow',
61
+ '\n⚠️ Semantic search unavailable - using grep fallback\n',
62
+ ),
63
+ )
64
+ if (result.installHint) {
65
+ console.log(color('dim', result.installHint), '\n')
66
+ }
67
+ }
68
+
69
+ // Output results
70
+ if (format === OutputFormat.JSON) {
71
+ console.log(JSON.stringify(result, null, 2))
72
+ } else {
73
+ console.log(formatMarkdown(result))
74
+ }
75
+ } catch (error) {
76
+ if (format === OutputFormat.JSON) {
77
+ console.error(
78
+ JSON.stringify(
79
+ {
80
+ error: error instanceof Error ? error.message : 'Unknown error',
81
+ query,
82
+ isError: true,
83
+ },
84
+ null,
85
+ 2,
86
+ ),
87
+ )
88
+ } else {
89
+ console.error(
90
+ color('red', '\n❌ Error:'),
91
+ error instanceof Error ? error.message : error,
92
+ '\n',
93
+ )
94
+ }
95
+ process.exit(1)
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Format semantic results as markdown
101
+ */
102
+ function formatMarkdown(result: SemanticResult): string {
103
+ const { query, count, matches, fallback } = result
104
+
105
+ if (count === 0) {
106
+ return color('yellow', `\n⚠️ No matches found for query: "${query}"\n`)
107
+ }
108
+
109
+ let output = color('cyan', `\n🔍 Found ${count} semantic match(es) for: `)
110
+ output += color('blue', `"${query}"`)
111
+ if (fallback) {
112
+ output += color('dim', ' (grep fallback)')
113
+ }
114
+ output += '\n\n'
115
+
116
+ // Group by file
117
+ const byFile = new Map<string, SemanticMatch[]>()
118
+ for (const match of matches) {
119
+ if (!byFile.has(match.file)) {
120
+ byFile.set(match.file, [])
121
+ }
122
+ byFile.get(match.file)?.push(match)
123
+ }
124
+
125
+ // Output each file's matches
126
+ for (const [file, fileMatches] of byFile.entries()) {
127
+ output += color('dim', `${file}:\n`)
128
+ for (const match of fileMatches) {
129
+ const lineInfo = match.startLine
130
+ ? `L${match.startLine}${match.endLine ? `-${match.endLine}` : ''}`
131
+ : ''
132
+ const scoreStr = `(${(match.score * 100).toFixed(1)}%)`
133
+
134
+ output += ` ${color('dim', lineInfo)} ${color('green', scoreStr)}\n`
135
+
136
+ // Show chunk preview (first 2 lines max)
137
+ const lines = match.chunk.split('\n').slice(0, 2)
138
+ for (const line of lines) {
139
+ output += ` ${color('dim', line.trim())}\n`
140
+ }
141
+ output += '\n'
142
+ }
143
+ }
144
+
145
+ return output
146
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Kit Plugin Error Taxonomy
3
+ *
4
+ * Comprehensive error handling with clear recovery instructions.
5
+ */
6
+
7
+ import {
8
+ detectErrorFromOutput as coreDetectErrorFromOutput,
9
+ type ErrorPattern,
10
+ isTimeoutOutput,
11
+ PluginError,
12
+ } from '@side-quest/core/instrumentation'
13
+
14
+ // ============================================================================
15
+ // Error Types
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Kit error types with associated recovery strategies.
20
+ */
21
+ export enum KitErrorType {
22
+ /** Kit CLI not found in PATH */
23
+ KitNotInstalled = 'KitNotInstalled',
24
+ /** Invalid repository path */
25
+ InvalidPath = 'InvalidPath',
26
+ /** Invalid input (regex, glob, etc.) */
27
+ InvalidInput = 'InvalidInput',
28
+ /** Semantic search not available (missing ML deps) */
29
+ SemanticNotAvailable = 'SemanticNotAvailable',
30
+ /** Semantic search index not yet built for repository */
31
+ SemanticIndexNotBuilt = 'SemanticIndexNotBuilt',
32
+ /** Too many results returned */
33
+ TooManyResults = 'TooManyResults',
34
+ /** Kit command failed */
35
+ KitCommandFailed = 'KitCommandFailed',
36
+ /** Failed to parse Kit output */
37
+ OutputParseError = 'OutputParseError',
38
+ /** Operation timed out */
39
+ Timeout = 'Timeout',
40
+ }
41
+
42
+ // ============================================================================
43
+ // Error Messages & Recovery Hints
44
+ // ============================================================================
45
+
46
+ /**
47
+ * User-facing error messages with recovery hints.
48
+ */
49
+ export const ERROR_MESSAGES: Record<
50
+ KitErrorType,
51
+ { message: string; hint: string }
52
+ > = {
53
+ [KitErrorType.KitNotInstalled]: {
54
+ message: 'Kit CLI is not installed or not found in PATH.',
55
+ hint: 'Install Kit with: uv tool install cased-kit',
56
+ },
57
+ [KitErrorType.InvalidPath]: {
58
+ message: 'The specified path does not exist or is not accessible.',
59
+ hint: 'Check the path exists and you have read permissions.',
60
+ },
61
+ [KitErrorType.InvalidInput]: {
62
+ message: 'Invalid input provided.',
63
+ hint: 'Check your search pattern or options for syntax errors.',
64
+ },
65
+ [KitErrorType.SemanticNotAvailable]: {
66
+ message: 'Semantic search is not available.',
67
+ hint: `Install ML dependencies: pip install 'cased-kit[ml]' or uv tool install 'cased-kit[ml]'`,
68
+ },
69
+ [KitErrorType.SemanticIndexNotBuilt]: {
70
+ message: 'Vector index has not been built for this repository yet.',
71
+ hint: 'Build it by running the CLI command provided in the error details. After building (one-time), semantic search will be fast and cached.',
72
+ },
73
+ [KitErrorType.TooManyResults]: {
74
+ message: 'Query returned too many results.',
75
+ hint: 'Try a more specific query or use --max-results to limit output.',
76
+ },
77
+ [KitErrorType.KitCommandFailed]: {
78
+ message: 'Kit command failed to execute.',
79
+ hint: 'Check the error details below for more information.',
80
+ },
81
+ [KitErrorType.OutputParseError]: {
82
+ message: 'Failed to parse Kit output.',
83
+ hint: 'This may be a bug. Check logs for details.',
84
+ },
85
+ [KitErrorType.Timeout]: {
86
+ message:
87
+ 'Operation timed out (this may take longer on large repositories).',
88
+ hint: 'Try again—the vector index will be cached. If it times out again, clear .kit/vector_db and rebuild with build_index: true.',
89
+ },
90
+ }
91
+
92
+ // ============================================================================
93
+ // Error Class
94
+ // ============================================================================
95
+
96
+ /**
97
+ * Custom error class for Kit operations.
98
+ *
99
+ * Extends PluginError to provide Kit-specific error handling with:
100
+ * - Typed error classification (KitErrorType enum)
101
+ * - User-friendly messages with recovery hints
102
+ * - Structured JSON serialization for MCP responses
103
+ */
104
+ export class KitError extends PluginError<KitErrorType> {
105
+ constructor(type: KitErrorType, details?: string, stderr?: string) {
106
+ const errorInfo = ERROR_MESSAGES[type]
107
+ super('KitError', type, errorInfo.message, errorInfo.hint, details, stderr)
108
+ }
109
+ }
110
+
111
+ // ============================================================================
112
+ // Semantic Search Fallback
113
+ // ============================================================================
114
+
115
+ /**
116
+ * Install hint shown when semantic search is unavailable.
117
+ */
118
+ export const SEMANTIC_INSTALL_HINT = `
119
+ Semantic search is not available. Using text search instead.
120
+
121
+ To enable semantic search, install the ML dependencies:
122
+ pip install 'cased-kit[ml]'
123
+
124
+ Or if using uv:
125
+ uv tool install 'cased-kit[ml]'
126
+ `.trim()
127
+
128
+ /**
129
+ * Check if an error indicates semantic search is unavailable.
130
+ * @param stderr - Standard error output from Kit
131
+ */
132
+ export function isSemanticUnavailableError(stderr: string): boolean {
133
+ const patterns = [
134
+ 'sentence-transformers',
135
+ 'chromadb',
136
+ 'semantic search',
137
+ 'vector index',
138
+ 'embedding',
139
+ ]
140
+ const lowerStderr = stderr.toLowerCase()
141
+ return patterns.some((p) => lowerStderr.includes(p))
142
+ }
143
+
144
+ /**
145
+ * Check if an error indicates a timeout occurred.
146
+ *
147
+ * Re-exports the core utility for backward compatibility.
148
+ *
149
+ * @param stderr - Standard error output
150
+ */
151
+ export const isTimeoutError = isTimeoutOutput
152
+
153
+ // ============================================================================
154
+ // Error Detection Helpers
155
+ // ============================================================================
156
+
157
+ /**
158
+ * Kit-specific error patterns for subprocess output detection.
159
+ *
160
+ * These patterns are tested in order by detectErrorType().
161
+ */
162
+ const KIT_ERROR_PATTERNS: ErrorPattern<KitErrorType>[] = [
163
+ // Semantic search unavailable (must be first - most specific)
164
+ {
165
+ type: KitErrorType.SemanticNotAvailable,
166
+ patterns: [
167
+ 'sentence-transformers',
168
+ 'chromadb',
169
+ 'semantic search',
170
+ 'vector index',
171
+ 'embedding',
172
+ ],
173
+ },
174
+ // Path errors
175
+ {
176
+ type: KitErrorType.InvalidPath,
177
+ patterns: ['no such file', 'not found', 'does not exist'],
178
+ },
179
+ // Timeout errors
180
+ {
181
+ type: KitErrorType.Timeout,
182
+ patterns: ['timeout', 'timed out', 'etimedout'],
183
+ },
184
+ // Too many results
185
+ {
186
+ type: KitErrorType.TooManyResults,
187
+ patterns: ['too many', 'limit'],
188
+ },
189
+ ]
190
+
191
+ /**
192
+ * Detect error type from Kit stderr output.
193
+ *
194
+ * Uses the generic core pattern matcher with Kit-specific patterns.
195
+ *
196
+ * @param stderr - Standard error output from Kit
197
+ * @param _exitCode - Exit code from Kit process (currently unused)
198
+ */
199
+ export function detectErrorType(
200
+ stderr: string,
201
+ _exitCode: number,
202
+ ): KitErrorType {
203
+ return coreDetectErrorFromOutput(
204
+ stderr,
205
+ KIT_ERROR_PATTERNS,
206
+ KitErrorType.KitCommandFailed, // Default
207
+ )
208
+ }
209
+
210
+ /**
211
+ * Create a KitError from process output.
212
+ * @param stderr - Standard error output
213
+ * @param exitCode - Process exit code
214
+ */
215
+ export function createErrorFromOutput(
216
+ stderr: string,
217
+ exitCode: number,
218
+ ): KitError {
219
+ const errorType = detectErrorType(stderr, exitCode)
220
+ return new KitError(errorType, undefined, stderr.trim())
221
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Output format enum
3
+ */
4
+ export {
5
+ color,
6
+ colors,
7
+ OutputFormat,
8
+ parseOutputFormat,
9
+ } from '@side-quest/core/formatters'