@getmikk/core 2.0.13 → 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.
Files changed (71) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -1
  3. package/src/analysis/index.ts +9 -0
  4. package/src/analysis/taint-analysis.ts +419 -0
  5. package/src/analysis/type-flow.ts +247 -0
  6. package/src/cache/incremental-cache.ts +278 -0
  7. package/src/cache/index.ts +1 -0
  8. package/src/contract/contract-generator.ts +31 -3
  9. package/src/contract/contract-reader.ts +1 -0
  10. package/src/contract/lock-compiler.ts +125 -12
  11. package/src/contract/schema.ts +4 -0
  12. package/src/error-handler.ts +2 -1
  13. package/src/graph/cluster-detector.ts +2 -4
  14. package/src/graph/dead-code-detector.ts +303 -117
  15. package/src/graph/graph-builder.ts +21 -161
  16. package/src/graph/impact-analyzer.ts +1 -0
  17. package/src/graph/index.ts +2 -0
  18. package/src/graph/rich-function-index.ts +1080 -0
  19. package/src/graph/symbol-table.ts +252 -0
  20. package/src/hash/hash-store.ts +1 -0
  21. package/src/index.ts +4 -0
  22. package/src/parser/base-extractor.ts +19 -0
  23. package/src/parser/boundary-checker.ts +31 -12
  24. package/src/parser/error-recovery.ts +647 -0
  25. package/src/parser/function-body-extractor.ts +248 -0
  26. package/src/parser/go/go-extractor.ts +249 -676
  27. package/src/parser/index.ts +138 -295
  28. package/src/parser/language-registry.ts +57 -0
  29. package/src/parser/oxc-parser.ts +166 -28
  30. package/src/parser/oxc-resolver.ts +179 -11
  31. package/src/parser/parser-constants.ts +1 -0
  32. package/src/parser/rust/rust-extractor.ts +109 -0
  33. package/src/parser/tree-sitter/parser.ts +400 -66
  34. package/src/parser/tree-sitter/queries.ts +106 -10
  35. package/src/parser/types.ts +20 -1
  36. package/src/search/bm25.ts +21 -8
  37. package/src/search/direct-search.ts +472 -0
  38. package/src/search/embedding-provider.ts +249 -0
  39. package/src/search/index.ts +12 -0
  40. package/src/search/semantic-search.ts +435 -0
  41. package/src/security/index.ts +1 -0
  42. package/src/security/scanner.ts +342 -0
  43. package/src/utils/artifact-transaction.ts +1 -0
  44. package/src/utils/atomic-write.ts +1 -0
  45. package/src/utils/errors.ts +89 -4
  46. package/src/utils/fs.ts +150 -65
  47. package/src/utils/json.ts +1 -0
  48. package/src/utils/language-registry.ts +96 -5
  49. package/src/utils/minimatch.ts +49 -6
  50. package/src/utils/path.ts +26 -0
  51. package/tests/dead-code.test.ts +3 -2
  52. package/tests/direct-search.test.ts +435 -0
  53. package/tests/error-recovery.test.ts +143 -0
  54. package/tests/fixtures/simple-api/src/index.ts +1 -1
  55. package/tests/go-parser.test.ts +19 -335
  56. package/tests/js-parser.test.ts +18 -1089
  57. package/tests/language-registry-all.test.ts +276 -0
  58. package/tests/language-registry.test.ts +6 -4
  59. package/tests/parse-diagnostics.test.ts +9 -96
  60. package/tests/parser.test.ts +42 -771
  61. package/tests/polyglot-parser.test.ts +117 -0
  62. package/tests/rich-function-index.test.ts +703 -0
  63. package/tests/tree-sitter-parser.test.ts +108 -80
  64. package/tests/ts-parser.test.ts +8 -8
  65. package/tests/verification.test.ts +175 -0
  66. package/src/parser/base-parser.ts +0 -16
  67. package/src/parser/go/go-parser.ts +0 -43
  68. package/src/parser/javascript/js-extractor.ts +0 -278
  69. package/src/parser/javascript/js-parser.ts +0 -101
  70. package/src/parser/typescript/ts-extractor.ts +0 -447
  71. package/src/parser/typescript/ts-parser.ts +0 -36
@@ -0,0 +1,647 @@
1
+ import type { ParsedFile, ParsedFunction, ParsedClass, ParsedImport, ParsedParam } from './types.js'
2
+ import * as path from 'node:path'
3
+ import { hashContent } from '../hash/file-hasher.js'
4
+ import { toParsedFileLanguage, type RegistryLanguage } from '../utils/language-registry.js'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Error Recovery Engine — graceful degradation when parsing fails
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface RecoveryResult {
11
+ success: boolean
12
+ strategy: string
13
+ parsed: ParsedFile
14
+ confidence: number
15
+ errors: string[]
16
+ }
17
+
18
+ export class ErrorRecoveryEngine {
19
+ async recover(filePath: string, content: string, language: string): Promise<RecoveryResult> {
20
+ const ext = path.extname(filePath).toLowerCase()
21
+
22
+ const regexResult = await this.recoverWithRegex(filePath, content, ext, language)
23
+ if (regexResult.confidence > 0.3) {
24
+ return regexResult
25
+ }
26
+
27
+ return this.recoverMinimal(filePath, content, ext, language)
28
+ }
29
+
30
+ private async recoverWithRegex(
31
+ filePath: string,
32
+ content: string,
33
+ ext: string,
34
+ language: string
35
+ ): Promise<RecoveryResult> {
36
+ const errors: string[] = []
37
+ const functions: ParsedFunction[] = []
38
+ const classes: ParsedClass[] = []
39
+ const imports: ParsedImport[] = []
40
+
41
+ try {
42
+ const lines = content.split('\n')
43
+
44
+ if (language === 'python' || ext === '.py') {
45
+ this.recoverPython(filePath, content, lines, functions, classes, imports)
46
+ } else if (language === 'typescript' || language === 'javascript' || ext === '.ts' || ext === '.tsx' || ext === '.js' || ext === '.jsx') {
47
+ this.recoverJavaScript(filePath, content, lines, functions, classes, imports)
48
+ } else if (language === 'go' || ext === '.go') {
49
+ this.recoverGo(filePath, content, lines, functions, classes, imports)
50
+ } else if (language === 'rust' || ext === '.rs') {
51
+ this.recoverRust(filePath, content, lines, functions, classes, imports)
52
+ } else if (language === 'java' || ext === '.java') {
53
+ this.recoverJava(filePath, content, lines, functions, classes, imports)
54
+ } else {
55
+ this.recoverGeneric(filePath, content, lines, functions, classes, imports)
56
+ }
57
+ } catch (err) {
58
+ errors.push(`Regex recovery failed: ${err instanceof Error ? err.message : String(err)}`)
59
+ }
60
+
61
+ const confidence = this.calculateConfidence(functions, classes, imports, content)
62
+
63
+ return {
64
+ success: functions.length > 0 || classes.length > 0 || imports.length > 0,
65
+ strategy: 'regex-recovery',
66
+ parsed: {
67
+ path: filePath,
68
+ language: toParsedFileLanguage(language as RegistryLanguage),
69
+ hash: hashContent(content),
70
+ parsedAt: Date.now(),
71
+ functions,
72
+ classes,
73
+ imports,
74
+ generics: [],
75
+ variables: [],
76
+ exports: [],
77
+ routes: [],
78
+ calls: [],
79
+ },
80
+ confidence,
81
+ errors,
82
+ }
83
+ }
84
+
85
+ private async recoverMinimal(
86
+ filePath: string,
87
+ content: string,
88
+ ext: string,
89
+ language: string
90
+ ): Promise<RecoveryResult> {
91
+ return {
92
+ success: false,
93
+ strategy: 'minimal-fallback',
94
+ parsed: {
95
+ path: filePath,
96
+ language: toParsedFileLanguage(language as RegistryLanguage),
97
+ hash: hashContent(content),
98
+ parsedAt: Date.now(),
99
+ functions: [],
100
+ classes: [],
101
+ imports: [],
102
+ generics: [],
103
+ variables: [],
104
+ exports: [],
105
+ routes: [],
106
+ calls: [],
107
+ },
108
+ confidence: 0,
109
+ errors: ['All recovery strategies failed'],
110
+ }
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Language-specific regex recovery
115
+ // ---------------------------------------------------------------------------
116
+
117
+ private recoverPython(
118
+ filePath: string,
119
+ content: string,
120
+ lines: string[],
121
+ functions: ParsedFunction[],
122
+ classes: ParsedClass[],
123
+ imports: ParsedImport[]
124
+ ): void {
125
+ const funcRegex = /^\s*(async\s+)?def\s+(\w+)\s*\(([^)]*)\)/
126
+ for (let i = 0; i < lines.length; i++) {
127
+ const match = lines[i].match(funcRegex)
128
+ if (match) {
129
+ const [, isAsync, name, params] = match
130
+ const paramsList: ParsedParam[] = params.split(',').map(p => p.trim()).filter(Boolean).map(p => ({
131
+ name: p.split(':')[0].split('=')[0].trim(),
132
+ type: p.includes(':') ? p.split(':')[1].split('=')[0].trim() : '',
133
+ optional: p.includes('=') || p.startsWith('self') || p.startsWith('cls'),
134
+ }))
135
+ functions.push({
136
+ id: `fn:${filePath}:${name.toLowerCase()}`,
137
+ name,
138
+ file: filePath,
139
+ startLine: i + 1,
140
+ endLine: this.findPythonFunctionEnd(lines, i),
141
+ isAsync: !!isAsync,
142
+ isExported: !name.startsWith('_'),
143
+ params: paramsList,
144
+ returnType: '',
145
+ purpose: this.extractPythonDocstring(lines, i),
146
+ calls: [],
147
+ hash: '',
148
+ edgeCasesHandled: [],
149
+ errorHandling: [],
150
+ detailedLines: [],
151
+ })
152
+ }
153
+ }
154
+
155
+ const classRegex = /^\s*class\s+(\w+)/
156
+ for (let i = 0; i < lines.length; i++) {
157
+ const match = lines[i].match(classRegex)
158
+ if (match) {
159
+ const [, name] = match
160
+ classes.push({
161
+ id: `class:${filePath}:${name.toLowerCase()}`,
162
+ name,
163
+ file: filePath,
164
+ startLine: i + 1,
165
+ endLine: this.findPythonClassEnd(lines, i),
166
+ isExported: !name.startsWith('_'),
167
+ methods: [],
168
+ purpose: '',
169
+ hash: '',
170
+ properties: [],
171
+ })
172
+ }
173
+ }
174
+
175
+ const importRegex = /^\s*(?:from\s+(\S+)\s+)?import\s+(.+)/
176
+ for (let i = 0; i < lines.length; i++) {
177
+ const match = lines[i].match(importRegex)
178
+ if (match) {
179
+ const [, fromModule, importsStr] = match
180
+ const names = importsStr.split(',').map(s => s.trim().split(' as ')[0].trim()).filter(Boolean)
181
+ if (names.length > 0) {
182
+ imports.push({
183
+ source: fromModule || names[0],
184
+ names,
185
+ resolvedPath: '',
186
+ isDefault: !fromModule,
187
+ isDynamic: false,
188
+ })
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ private recoverJavaScript(
195
+ filePath: string,
196
+ content: string,
197
+ lines: string[],
198
+ functions: ParsedFunction[],
199
+ classes: ParsedClass[],
200
+ imports: ParsedImport[]
201
+ ): void {
202
+ const funcRegex = /^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/
203
+ const arrowRegex = /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)/
204
+
205
+ for (let i = 0; i < lines.length; i++) {
206
+ let match = lines[i].match(funcRegex)
207
+ if (!match) {
208
+ match = lines[i].match(arrowRegex)
209
+ }
210
+ if (match) {
211
+ const [, name, params] = match
212
+ const isAsync = lines[i].includes('async')
213
+ const paramsList: ParsedParam[] = params.split(',').map(p => p.trim()).filter(Boolean).map(p => ({
214
+ name: p.split(':')[0].split('=')[0].trim(),
215
+ type: p.includes(':') ? p.split(':')[1].split('=')[0].trim() : '',
216
+ optional: p.includes('?') || p.includes('='),
217
+ }))
218
+ functions.push({
219
+ id: `fn:${filePath}:${name.toLowerCase()}`,
220
+ name,
221
+ file: filePath,
222
+ startLine: i + 1,
223
+ endLine: this.findJSBraceEnd(lines, i),
224
+ isAsync,
225
+ isExported: lines[i].includes('export'),
226
+ params: paramsList,
227
+ returnType: '',
228
+ purpose: '',
229
+ calls: [],
230
+ hash: '',
231
+ edgeCasesHandled: [],
232
+ errorHandling: [],
233
+ detailedLines: [],
234
+ })
235
+ }
236
+ }
237
+
238
+ const classRegex = /^\s*(?:export\s+)?(?:default\s+)?class\s+(\w+)/
239
+ for (let i = 0; i < lines.length; i++) {
240
+ const match = lines[i].match(classRegex)
241
+ if (match) {
242
+ const [, name] = match
243
+ classes.push({
244
+ id: `class:${filePath}:${name.toLowerCase()}`,
245
+ name,
246
+ file: filePath,
247
+ startLine: i + 1,
248
+ endLine: this.findJSBraceEnd(lines, i),
249
+ isExported: lines[i].includes('export'),
250
+ methods: [],
251
+ purpose: '',
252
+ hash: '',
253
+ properties: [],
254
+ })
255
+ }
256
+ }
257
+
258
+ const importRegex = /^\s*import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/
259
+ for (let i = 0; i < lines.length; i++) {
260
+ const match = lines[i].match(importRegex)
261
+ if (match) {
262
+ const [, named, defaultImport, source] = match
263
+ const names = named ? named.split(',').map(s => s.trim()).filter(Boolean) : defaultImport ? [defaultImport] : []
264
+ if (names.length > 0 || source) {
265
+ imports.push({
266
+ source: source || '',
267
+ names,
268
+ resolvedPath: '',
269
+ isDefault: !!defaultImport,
270
+ isDynamic: false,
271
+ })
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ private recoverGo(
278
+ filePath: string,
279
+ content: string,
280
+ lines: string[],
281
+ functions: ParsedFunction[],
282
+ classes: ParsedClass[],
283
+ imports: ParsedImport[]
284
+ ): void {
285
+ const funcRegex = /^\s*func\s+(?:\(\s*\w+\s+\*?\w+\s*\)\s+)?(\w+)\s*\(/
286
+ for (let i = 0; i < lines.length; i++) {
287
+ const match = lines[i].match(funcRegex)
288
+ if (match) {
289
+ const [, name] = match
290
+ const isExported = name.length > 0 && name[0] === name[0].toUpperCase()
291
+ functions.push({
292
+ id: `fn:${filePath}:${name.toLowerCase()}`,
293
+ name,
294
+ file: filePath,
295
+ startLine: i + 1,
296
+ endLine: this.findJSBraceEnd(lines, i),
297
+ isAsync: false,
298
+ isExported,
299
+ params: [],
300
+ returnType: '',
301
+ purpose: '',
302
+ calls: [],
303
+ hash: '',
304
+ edgeCasesHandled: [],
305
+ errorHandling: [],
306
+ detailedLines: [],
307
+ })
308
+ }
309
+ }
310
+
311
+ const structRegex = /^\s*type\s+(\w+)\s+struct/
312
+ for (let i = 0; i < lines.length; i++) {
313
+ const match = lines[i].match(structRegex)
314
+ if (match) {
315
+ const [, name] = match
316
+ const isExported = name.length > 0 && name[0] === name[0].toUpperCase()
317
+ classes.push({
318
+ id: `class:${filePath}:${name.toLowerCase()}`,
319
+ name,
320
+ file: filePath,
321
+ startLine: i + 1,
322
+ endLine: this.findGoStructEnd(lines, i),
323
+ isExported,
324
+ methods: [],
325
+ purpose: '',
326
+ hash: '',
327
+ properties: [],
328
+ })
329
+ }
330
+ }
331
+
332
+ let inImportBlock = false
333
+ for (let i = 0; i < lines.length; i++) {
334
+ const line = lines[i].trim()
335
+ if (line.startsWith('import (')) {
336
+ inImportBlock = true
337
+ continue
338
+ }
339
+ if (inImportBlock) {
340
+ if (line === ')') {
341
+ inImportBlock = false
342
+ continue
343
+ }
344
+ const pkg = line.replace(/"/g, '').trim()
345
+ if (pkg) {
346
+ imports.push({
347
+ source: pkg,
348
+ names: [],
349
+ resolvedPath: '',
350
+ isDefault: false,
351
+ isDynamic: false,
352
+ })
353
+ }
354
+ }
355
+ const singleImport = line.match(/^import\s+"([^"]+)"/)
356
+ if (singleImport) {
357
+ imports.push({
358
+ source: singleImport[1],
359
+ names: [],
360
+ resolvedPath: '',
361
+ isDefault: false,
362
+ isDynamic: false,
363
+ })
364
+ }
365
+ }
366
+ }
367
+
368
+ private recoverRust(
369
+ filePath: string,
370
+ content: string,
371
+ lines: string[],
372
+ functions: ParsedFunction[],
373
+ classes: ParsedClass[],
374
+ imports: ParsedImport[]
375
+ ): void {
376
+ const funcRegex = /^\s*(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*[<(]/
377
+ for (let i = 0; i < lines.length; i++) {
378
+ const match = lines[i].match(funcRegex)
379
+ if (match) {
380
+ const [, name] = match
381
+ const isExported = lines[i].includes('pub')
382
+ functions.push({
383
+ id: `fn:${filePath}:${name.toLowerCase()}`,
384
+ name,
385
+ file: filePath,
386
+ startLine: i + 1,
387
+ endLine: this.findJSBraceEnd(lines, i),
388
+ isAsync: lines[i].includes('async'),
389
+ isExported,
390
+ params: [],
391
+ returnType: '',
392
+ purpose: '',
393
+ calls: [],
394
+ hash: '',
395
+ edgeCasesHandled: [],
396
+ errorHandling: [],
397
+ detailedLines: [],
398
+ })
399
+ }
400
+ }
401
+
402
+ const structRegex = /^\s*(?:pub\s+)?struct\s+(\w+)/
403
+ for (let i = 0; i < lines.length; i++) {
404
+ const match = lines[i].match(structRegex)
405
+ if (match) {
406
+ const [, name] = match
407
+ classes.push({
408
+ id: `class:${filePath}:${name.toLowerCase()}`,
409
+ name,
410
+ file: filePath,
411
+ startLine: i + 1,
412
+ endLine: this.findJSBraceEnd(lines, i),
413
+ isExported: lines[i].includes('pub'),
414
+ methods: [],
415
+ purpose: '',
416
+ hash: '',
417
+ properties: [],
418
+ })
419
+ }
420
+ }
421
+
422
+ const useRegex = /^\s*(?:pub\s+)?use\s+(.+);/
423
+ for (let i = 0; i < lines.length; i++) {
424
+ const match = lines[i].match(useRegex)
425
+ if (match) {
426
+ imports.push({
427
+ source: match[1].trim(),
428
+ names: [],
429
+ resolvedPath: '',
430
+ isDefault: false,
431
+ isDynamic: false,
432
+ })
433
+ }
434
+ }
435
+ }
436
+
437
+ private recoverJava(
438
+ filePath: string,
439
+ content: string,
440
+ lines: string[],
441
+ functions: ParsedFunction[],
442
+ classes: ParsedClass[],
443
+ imports: ParsedImport[]
444
+ ): void {
445
+ const methodRegex = /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:\w+(?:<[^>]+>)?(?:\[\])?)\s+(\w+)\s*\(/
446
+ for (let i = 0; i < lines.length; i++) {
447
+ const match = lines[i].match(methodRegex)
448
+ if (match) {
449
+ const [, name] = match
450
+ if (name !== 'if' && name !== 'for' && name !== 'while' && name !== 'switch' && name !== 'class' && name !== 'interface') {
451
+ const isExported = lines[i].includes('public')
452
+ functions.push({
453
+ id: `fn:${filePath}:${name.toLowerCase()}`,
454
+ name,
455
+ file: filePath,
456
+ startLine: i + 1,
457
+ endLine: this.findJSBraceEnd(lines, i),
458
+ isAsync: false,
459
+ isExported,
460
+ params: [],
461
+ returnType: '',
462
+ purpose: '',
463
+ calls: [],
464
+ hash: '',
465
+ edgeCasesHandled: [],
466
+ errorHandling: [],
467
+ detailedLines: [],
468
+ })
469
+ }
470
+ }
471
+ }
472
+
473
+ const classRegex = /^\s*(?:public\s+)?(?:abstract\s+)?class\s+(\w+)/
474
+ for (let i = 0; i < lines.length; i++) {
475
+ const match = lines[i].match(classRegex)
476
+ if (match) {
477
+ const [, name] = match
478
+ classes.push({
479
+ id: `class:${filePath}:${name.toLowerCase()}`,
480
+ name,
481
+ file: filePath,
482
+ startLine: i + 1,
483
+ endLine: this.findJSBraceEnd(lines, i),
484
+ isExported: lines[i].includes('public'),
485
+ methods: [],
486
+ purpose: '',
487
+ hash: '',
488
+ properties: [],
489
+ })
490
+ }
491
+ }
492
+
493
+ const importRegex = /^\s*import\s+([\w.]+)\s*;/
494
+ for (let i = 0; i < lines.length; i++) {
495
+ const match = lines[i].match(importRegex)
496
+ if (match) {
497
+ imports.push({
498
+ source: match[1],
499
+ names: [],
500
+ resolvedPath: '',
501
+ isDefault: false,
502
+ isDynamic: false,
503
+ })
504
+ }
505
+ }
506
+ }
507
+
508
+ private recoverGeneric(
509
+ filePath: string,
510
+ content: string,
511
+ lines: string[],
512
+ functions: ParsedFunction[],
513
+ classes: ParsedClass[],
514
+ _imports: ParsedImport[]
515
+ ): void {
516
+ const funcPatterns = [
517
+ /function\s+(\w+)\s*\(/,
518
+ /def\s+(\w+)\s*\(/,
519
+ /fn\s+(\w+)\s*[<(]/,
520
+ /func\s+(\w+)\s*\(/,
521
+ ]
522
+
523
+ const classPatterns = [
524
+ /class\s+(\w+)/,
525
+ /struct\s+(\w+)/,
526
+ /type\s+(\w+)\s+struct/,
527
+ ]
528
+
529
+ for (let i = 0; i < lines.length; i++) {
530
+ for (const pattern of funcPatterns) {
531
+ const match = lines[i].match(pattern)
532
+ if (match) {
533
+ functions.push({
534
+ id: `fn:${filePath}:${match[1].toLowerCase()}`,
535
+ name: match[1],
536
+ file: filePath,
537
+ startLine: i + 1,
538
+ endLine: i + 10,
539
+ isAsync: false,
540
+ isExported: false,
541
+ params: [],
542
+ returnType: '',
543
+ purpose: '',
544
+ calls: [],
545
+ hash: '',
546
+ edgeCasesHandled: [],
547
+ errorHandling: [],
548
+ detailedLines: [],
549
+ })
550
+ break
551
+ }
552
+ }
553
+
554
+ for (const pattern of classPatterns) {
555
+ const match = lines[i].match(pattern)
556
+ if (match) {
557
+ classes.push({
558
+ id: `class:${filePath}:${match[1].toLowerCase()}`,
559
+ name: match[1],
560
+ file: filePath,
561
+ startLine: i + 1,
562
+ endLine: i + 20,
563
+ isExported: false,
564
+ methods: [],
565
+ purpose: '',
566
+ hash: '',
567
+ properties: [],
568
+ })
569
+ break
570
+ }
571
+ }
572
+ }
573
+ }
574
+
575
+ // ---------------------------------------------------------------------------
576
+ // Helper methods
577
+ // ---------------------------------------------------------------------------
578
+
579
+ private findPythonFunctionEnd(lines: string[], startLine: number): number {
580
+ const baseIndent = this.getIndentLevel(lines[startLine])
581
+ for (let i = startLine + 1; i < lines.length; i++) {
582
+ if (lines[i].trim() === '') continue
583
+ if (this.getIndentLevel(lines[i]) <= baseIndent && lines[i].trim() !== '') {
584
+ return i
585
+ }
586
+ }
587
+ return lines.length
588
+ }
589
+
590
+ private findPythonClassEnd(lines: string[], startLine: number): number {
591
+ return this.findPythonFunctionEnd(lines, startLine)
592
+ }
593
+
594
+ private findJSBraceEnd(lines: string[], startLine: number): number {
595
+ let braces = 0
596
+ let started = false
597
+ for (let i = startLine; i < lines.length; i++) {
598
+ for (const char of lines[i]) {
599
+ if (char === '{') { braces++; started = true }
600
+ if (char === '}') braces--
601
+ }
602
+ if (started && braces === 0) return i + 1
603
+ }
604
+ return lines.length
605
+ }
606
+
607
+ private findGoStructEnd(lines: string[], startLine: number): number {
608
+ for (let i = startLine + 1; i < lines.length; i++) {
609
+ if (lines[i].trim() === '}') return i + 1
610
+ }
611
+ return lines.length
612
+ }
613
+
614
+ private getIndentLevel(line: string): number {
615
+ const match = line.match(/^(\s*)/)
616
+ return match ? match[1].length : 0
617
+ }
618
+
619
+ private extractPythonDocstring(lines: string[], funcLine: number): string {
620
+ for (let i = funcLine + 1; i < Math.min(funcLine + 5, lines.length); i++) {
621
+ const trimmed = lines[i].trim()
622
+ if (trimmed.startsWith('"""') || trimmed.startsWith("'''")) {
623
+ return trimmed.replace(/['"]{3}/g, '').trim()
624
+ }
625
+ if (trimmed.startsWith('#')) {
626
+ return trimmed.substring(1).trim()
627
+ }
628
+ }
629
+ return ''
630
+ }
631
+
632
+ private calculateConfidence(
633
+ functions: ParsedFunction[],
634
+ classes: ParsedClass[],
635
+ imports: ParsedImport[],
636
+ content: string
637
+ ): number {
638
+ const lineCount = content.split('\n').length
639
+ const extracted = functions.length + classes.length + imports.length
640
+ const ratio = Math.min(1, extracted / Math.max(1, lineCount / 10))
641
+ let confidence = ratio * 0.7
642
+ if (functions.length > 0 && classes.length > 0) confidence += 0.1
643
+ if (imports.length > 0) confidence += 0.1
644
+ if (extracted === 0) confidence = 0
645
+ return Math.min(1, Math.max(0, confidence))
646
+ }
647
+ }