@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,437 @@
1
+ /**
2
+ * Kit Plugin Validators
3
+ *
4
+ * Input validation and security utilities for safe Kit CLI operations.
5
+ */
6
+
7
+ import { normalizePath, pathExistsSync, statSync } from '@side-quest/core/fs'
8
+ import {
9
+ validateGlob,
10
+ validateInteger,
11
+ validateRegex,
12
+ } from '@side-quest/core/validation'
13
+ import { getDefaultKitPath } from './types.js'
14
+
15
+ // ============================================================================
16
+ // Path Validation
17
+ // ============================================================================
18
+
19
+ /**
20
+ * Result of path validation.
21
+ */
22
+ export interface PathValidationResult {
23
+ /** Whether the path is valid */
24
+ valid: boolean
25
+ /** Normalized absolute path (only if valid) */
26
+ path?: string
27
+ /** Error message (only if invalid) */
28
+ error?: string
29
+ }
30
+
31
+ /**
32
+ * Validate a path for Kit operations.
33
+ *
34
+ * Checks:
35
+ * - Path exists
36
+ * - Path is a directory (for search operations)
37
+ * - No path traversal attacks (.. sequences escaping base)
38
+ *
39
+ * @param inputPath - Path to validate
40
+ * @param options - Validation options
41
+ * @returns Validation result with normalized path or error
42
+ */
43
+ export function validatePath(
44
+ inputPath: string,
45
+ options: {
46
+ /** Base directory to restrict access within (optional) */
47
+ basePath?: string
48
+ /** Whether the path must be a directory (default: true) */
49
+ mustBeDirectory?: boolean
50
+ /** Whether the path must exist (default: true) */
51
+ mustExist?: boolean
52
+ } = {},
53
+ ): PathValidationResult {
54
+ const { basePath, mustBeDirectory = true, mustExist = true } = options
55
+
56
+ // Empty path check
57
+ if (!inputPath || inputPath.trim() === '') {
58
+ return { valid: false, error: 'Path cannot be empty' }
59
+ }
60
+
61
+ // Normalize the path
62
+ const normalizedPath = normalizePath(inputPath, basePath)
63
+
64
+ // Path traversal check - if basePath is specified, ensure we stay within it
65
+ if (basePath) {
66
+ const normalizedBase = normalizePath(basePath)
67
+ if (!normalizedPath.startsWith(normalizedBase)) {
68
+ return {
69
+ valid: false,
70
+ error: 'Path traversal detected: path escapes base directory',
71
+ }
72
+ }
73
+ }
74
+
75
+ // Existence check
76
+ if (mustExist && !pathExistsSync(normalizedPath)) {
77
+ return { valid: false, error: `Path does not exist: ${normalizedPath}` }
78
+ }
79
+
80
+ // Directory check
81
+ if (mustExist && mustBeDirectory) {
82
+ try {
83
+ const stats = statSync(normalizedPath)
84
+ if (!stats.isDirectory()) {
85
+ return {
86
+ valid: false,
87
+ error: `Path is not a directory: ${normalizedPath}`,
88
+ }
89
+ }
90
+ } catch {
91
+ return { valid: false, error: `Cannot access path: ${normalizedPath}` }
92
+ }
93
+ }
94
+
95
+ return { valid: true, path: normalizedPath }
96
+ }
97
+
98
+ // ============================================================================
99
+ // Integer Validation
100
+ // ============================================================================
101
+
102
+ /**
103
+ * Validate a number is a positive integer within bounds.
104
+ * @deprecated Use validateInteger from @sidequest/core/validation instead
105
+ * @param value - Value to validate
106
+ * @param options - Validation options
107
+ * @returns Validation result
108
+ */
109
+ export function validatePositiveInt(
110
+ value: unknown,
111
+ options: {
112
+ /** Field name for error messages */
113
+ name: string
114
+ /** Minimum allowed value (default: 1) */
115
+ min?: number
116
+ /** Maximum allowed value (default: 10000) */
117
+ max?: number
118
+ /** Default value if undefined */
119
+ defaultValue?: number
120
+ },
121
+ ): { valid: boolean; value?: number; error?: string } {
122
+ // Delegate to core validateInteger with backward-compatible defaults
123
+ return validateInteger(value, {
124
+ name: options.name,
125
+ min: options.min ?? 1,
126
+ max: options.max ?? 10000,
127
+ defaultValue: options.defaultValue,
128
+ })
129
+ }
130
+
131
+ // ============================================================================
132
+ // Composite Validators
133
+ // ============================================================================
134
+
135
+ /**
136
+ * Validate all inputs for a grep search operation.
137
+ * @param inputs - Grep inputs to validate
138
+ * @returns Combined validation result
139
+ */
140
+ export function validateGrepInputs(inputs: {
141
+ pattern: string
142
+ path?: string
143
+ include?: string
144
+ exclude?: string
145
+ maxResults?: number
146
+ }): {
147
+ valid: boolean
148
+ errors: string[]
149
+ validated?: {
150
+ pattern: string
151
+ path: string
152
+ include?: string
153
+ exclude?: string
154
+ maxResults: number
155
+ }
156
+ } {
157
+ const errors: string[] = []
158
+
159
+ // Validate pattern - core returns ValidationResult<RegExp>
160
+ const patternResult = validateRegex(inputs.pattern)
161
+ if (!patternResult.valid) {
162
+ errors.push(patternResult.error!)
163
+ }
164
+
165
+ // Validate path
166
+ const pathResult = validatePath(inputs.path || getDefaultKitPath())
167
+ if (!pathResult.valid) {
168
+ errors.push(pathResult.error!)
169
+ }
170
+
171
+ // Validate include glob (optional)
172
+ let validatedInclude: string | undefined
173
+ if (inputs.include) {
174
+ const includeResult = validateGlob(inputs.include)
175
+ if (!includeResult.valid) {
176
+ errors.push(`Include pattern: ${includeResult.error}`)
177
+ } else {
178
+ validatedInclude = includeResult.value
179
+ }
180
+ }
181
+
182
+ // Validate exclude glob (optional)
183
+ let validatedExclude: string | undefined
184
+ if (inputs.exclude) {
185
+ const excludeResult = validateGlob(inputs.exclude)
186
+ if (!excludeResult.valid) {
187
+ errors.push(`Exclude pattern: ${excludeResult.error}`)
188
+ } else {
189
+ validatedExclude = excludeResult.value
190
+ }
191
+ }
192
+
193
+ // Validate maxResults
194
+ const maxResultsResult = validatePositiveInt(inputs.maxResults, {
195
+ name: 'maxResults',
196
+ min: 1,
197
+ max: 1000,
198
+ defaultValue: 100,
199
+ })
200
+ if (!maxResultsResult.valid) {
201
+ errors.push(maxResultsResult.error!)
202
+ }
203
+
204
+ if (errors.length > 0) {
205
+ return { valid: false, errors }
206
+ }
207
+
208
+ return {
209
+ valid: true,
210
+ errors: [],
211
+ validated: {
212
+ pattern: patternResult.value!.source, // Extract source from compiled RegExp
213
+ path: pathResult.path!,
214
+ include: validatedInclude,
215
+ exclude: validatedExclude,
216
+ maxResults: maxResultsResult.value!,
217
+ },
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Validate all inputs for a semantic search operation.
223
+ * @param inputs - Semantic search inputs to validate
224
+ * @returns Combined validation result
225
+ */
226
+ export function validateSemanticInputs(inputs: {
227
+ query: string
228
+ path?: string
229
+ topK?: number
230
+ }): {
231
+ valid: boolean
232
+ errors: string[]
233
+ validated?: {
234
+ query: string
235
+ path: string
236
+ topK: number
237
+ }
238
+ } {
239
+ const errors: string[] = []
240
+
241
+ // Validate query (not a regex, just non-empty)
242
+ if (!inputs.query || inputs.query.trim() === '') {
243
+ errors.push('Query cannot be empty')
244
+ }
245
+
246
+ // Validate path
247
+ const pathResult = validatePath(inputs.path || getDefaultKitPath())
248
+ if (!pathResult.valid) {
249
+ errors.push(pathResult.error!)
250
+ }
251
+
252
+ // Validate topK
253
+ const topKResult = validatePositiveInt(inputs.topK, {
254
+ name: 'topK',
255
+ min: 1,
256
+ max: 50,
257
+ defaultValue: 5,
258
+ })
259
+ if (!topKResult.valid) {
260
+ errors.push(topKResult.error!)
261
+ }
262
+
263
+ if (errors.length > 0) {
264
+ return { valid: false, errors }
265
+ }
266
+
267
+ return {
268
+ valid: true,
269
+ errors: [],
270
+ validated: {
271
+ query: inputs.query.trim(),
272
+ path: pathResult.path!,
273
+ topK: topKResult.value!,
274
+ },
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Validate all inputs for a symbol usages operation.
280
+ * @param inputs - Usages inputs to validate
281
+ * @returns Combined validation result
282
+ */
283
+ export function validateUsagesInputs(inputs: {
284
+ path?: string
285
+ symbolName: string
286
+ symbolType?: string
287
+ }): {
288
+ valid: boolean
289
+ errors: string[]
290
+ validated?: {
291
+ path: string
292
+ symbolName: string
293
+ symbolType?: string
294
+ }
295
+ } {
296
+ const errors: string[] = []
297
+
298
+ // Validate path
299
+ const pathResult = validatePath(inputs.path || getDefaultKitPath())
300
+ if (!pathResult.valid) {
301
+ errors.push(pathResult.error!)
302
+ }
303
+
304
+ // Validate symbol name
305
+ if (!inputs.symbolName || inputs.symbolName.trim() === '') {
306
+ errors.push('Symbol name is required')
307
+ }
308
+
309
+ // Validate symbol type (optional)
310
+ let validatedSymbolType: string | undefined
311
+ if (inputs.symbolType) {
312
+ const sanitized = inputs.symbolType.trim().toLowerCase()
313
+ const validTypes = [
314
+ 'function',
315
+ 'class',
316
+ 'variable',
317
+ 'type',
318
+ 'interface',
319
+ 'method',
320
+ 'property',
321
+ 'constant',
322
+ ]
323
+ if (sanitized && !validTypes.includes(sanitized)) {
324
+ errors.push(
325
+ `Invalid symbol type: ${inputs.symbolType}. Valid types: ${validTypes.join(', ')}`,
326
+ )
327
+ } else {
328
+ validatedSymbolType = sanitized || undefined
329
+ }
330
+ }
331
+
332
+ if (errors.length > 0) {
333
+ return { valid: false, errors }
334
+ }
335
+
336
+ return {
337
+ valid: true,
338
+ errors: [],
339
+ validated: {
340
+ path: pathResult.path!,
341
+ symbolName: inputs.symbolName.trim(),
342
+ symbolType: validatedSymbolType,
343
+ },
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Validate all inputs for an AST search operation.
349
+ * @param inputs - AST search inputs to validate
350
+ * @returns Combined validation result
351
+ */
352
+ export function validateAstSearchInputs(inputs: {
353
+ pattern: string
354
+ mode?: string
355
+ filePattern?: string
356
+ path?: string
357
+ maxResults?: number
358
+ }): {
359
+ valid: boolean
360
+ errors: string[]
361
+ validated?: {
362
+ pattern: string
363
+ mode: 'simple' | 'pattern'
364
+ filePattern?: string
365
+ path: string
366
+ maxResults: number
367
+ }
368
+ } {
369
+ const errors: string[] = []
370
+
371
+ // Validate pattern
372
+ if (!inputs.pattern || inputs.pattern.trim() === '') {
373
+ errors.push('Pattern cannot be empty')
374
+ }
375
+
376
+ // Validate mode
377
+ const validModes = ['simple', 'pattern']
378
+ const mode = (inputs.mode || 'simple').toLowerCase()
379
+ if (!validModes.includes(mode)) {
380
+ errors.push(
381
+ `Invalid mode: ${inputs.mode}. Valid modes: ${validModes.join(', ')}`,
382
+ )
383
+ }
384
+
385
+ // Validate pattern mode JSON if mode is 'pattern'
386
+ if (mode === 'pattern' && inputs.pattern) {
387
+ try {
388
+ JSON.parse(inputs.pattern)
389
+ } catch {
390
+ // Allow non-JSON patterns - they'll be treated as textMatch
391
+ }
392
+ }
393
+
394
+ // Validate file pattern (optional glob)
395
+ let validatedFilePattern: string | undefined
396
+ if (inputs.filePattern) {
397
+ const patternResult = validateGlob(inputs.filePattern)
398
+ if (!patternResult.valid) {
399
+ errors.push(`File pattern: ${patternResult.error}`)
400
+ } else {
401
+ validatedFilePattern = patternResult.value
402
+ }
403
+ }
404
+
405
+ // Validate path
406
+ const pathResult = validatePath(inputs.path || getDefaultKitPath())
407
+ if (!pathResult.valid) {
408
+ errors.push(pathResult.error!)
409
+ }
410
+
411
+ // Validate maxResults
412
+ const maxResultsResult = validatePositiveInt(inputs.maxResults, {
413
+ name: 'maxResults',
414
+ min: 1,
415
+ max: 500,
416
+ defaultValue: 100,
417
+ })
418
+ if (!maxResultsResult.valid) {
419
+ errors.push(maxResultsResult.error!)
420
+ }
421
+
422
+ if (errors.length > 0) {
423
+ return { valid: false, errors }
424
+ }
425
+
426
+ return {
427
+ valid: true,
428
+ errors: [],
429
+ validated: {
430
+ pattern: inputs.pattern.trim(),
431
+ mode: mode as 'simple' | 'pattern',
432
+ filePattern: validatedFilePattern,
433
+ path: pathResult.path!,
434
+ maxResults: maxResultsResult.value!,
435
+ },
436
+ }
437
+ }