@getmikk/core 2.0.10 → 2.0.12

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.
@@ -1,15 +1,8 @@
1
- /**
2
- * Tree-sitter queries for extracting code definitions across 13 languages.
3
- * Ported from GitNexus.
4
- *
5
- * NOTE: Some grammars (like Python and Ruby) use slightly different AST node
6
- * types for equivalent structures. These queries are written against
7
- * the standard tree-sitter grammars.
8
- */
9
-
10
1
  export const TYPESCRIPT_QUERIES = `
11
2
  (class_declaration name: (type_identifier) @name) @definition.class
12
3
  (interface_declaration name: (type_identifier) @name) @definition.interface
4
+ (class_declaration name: (type_identifier) @heritage.class (class_heritage (extends_clause value: (identifier) @heritage.extends))) @heritage
5
+ (class_declaration name: (type_identifier) @heritage.class (class_heritage (implements_clause (type_identifier) @heritage.implements))) @heritage.impl
13
6
  (function_declaration name: (identifier) @name) @definition.function
14
7
  (method_definition name: (property_identifier) @name) @definition.method
15
8
  (lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function))) @definition.function
@@ -21,15 +14,16 @@ export const TYPESCRIPT_QUERIES = `
21
14
  (call_expression function: (identifier) @call.name) @call
22
15
  (call_expression function: (member_expression property: (property_identifier) @call.name)) @call
23
16
  (new_expression constructor: (identifier) @call.name) @call
17
+ (new_expression constructor: (member_expression property: (property_identifier) @call.name)) @call
24
18
  (public_field_definition name: (property_identifier) @name) @definition.property
25
19
  (public_field_definition name: (private_property_identifier) @name) @definition.property
26
20
  (required_parameter (accessibility_modifier) pattern: (identifier) @name) @definition.property
27
- (class_declaration name: (type_identifier) @heritage.class (class_heritage (extends_clause value: (identifier) @heritage.extends))) @heritage
28
- (class_declaration name: (type_identifier) @heritage.class (class_heritage (implements_clause (type_identifier) @heritage.implements))) @heritage.impl
21
+ (field_declaration name: (property_identifier) @name) @definition.property
29
22
  `;
30
23
 
31
24
  export const JAVASCRIPT_QUERIES = `
32
25
  (class_declaration name: (identifier) @name) @definition.class
26
+ (class_declaration name: (identifier) @heritage.class (class_heritage (identifier) @heritage.extends)) @heritage
33
27
  (function_declaration name: (identifier) @name) @definition.function
34
28
  (method_definition name: (property_identifier) @name) @definition.method
35
29
  (lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function))) @definition.function
@@ -42,11 +36,11 @@ export const JAVASCRIPT_QUERIES = `
42
36
  (call_expression function: (member_expression property: (property_identifier) @call.name)) @call
43
37
  (new_expression constructor: (identifier) @call.name) @call
44
38
  (field_definition property: (property_identifier) @name) @definition.property
45
- (class_declaration name: (identifier) @heritage.class (class_heritage (identifier) @heritage.extends)) @heritage
46
39
  `;
47
40
 
48
41
  export const PYTHON_QUERIES = `
49
42
  (class_definition name: (identifier) @name) @definition.class
43
+ (class_definition name: (identifier) @heritage.class superclasses: (argument_list (identifier) @heritage.extends)) @heritage
50
44
  (function_definition name: (identifier) @name) @definition.function
51
45
  (import_statement name: (dotted_name) @import.source) @import
52
46
  (import_from_statement module_name: (dotted_name) @import.source) @import
@@ -54,7 +48,6 @@ export const PYTHON_QUERIES = `
54
48
  (call function: (identifier) @call.name) @call
55
49
  (call function: (attribute attribute: (identifier) @call.name)) @call
56
50
  (expression_statement (assignment left: (identifier) @name type: (type))) @definition.property
57
- (class_definition name: (identifier) @heritage.class superclasses: (argument_list (identifier) @heritage.extends)) @heritage
58
51
  `;
59
52
 
60
53
  export const JAVA_QUERIES = `
@@ -111,8 +104,6 @@ export const CPP_QUERIES = `
111
104
  (function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function
112
105
  (function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method
113
106
  (declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function
114
- (field_declaration declarator: (field_identifier) @name) @definition.property
115
- (field_declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.method
116
107
  (preproc_include path: (_) @import.source) @import
117
108
  (call_expression function: (identifier) @call.name) @call
118
109
  (call_expression function: (field_expression field: (field_identifier) @call.name)) @call
@@ -153,7 +144,6 @@ export const RUST_QUERIES = `
153
144
  (call_expression function: (field_expression field: (field_identifier) @call.name)) @call
154
145
  (call_expression function: (scoped_identifier name: (identifier) @call.name)) @call
155
146
  (struct_expression name: (type_identifier) @call.name) @call
156
- (field_declaration_list (field_declaration name: (field_identifier) @name)) @definition.property
157
147
  `;
158
148
 
159
149
  export const PHP_QUERIES = `
@@ -175,7 +165,103 @@ export const PHP_QUERIES = `
175
165
  export const RUBY_QUERIES = `
176
166
  (module name: (constant) @name) @definition.module
177
167
  (class name: (constant) @name) @definition.class
168
+ (singleton_class value: (class name: (constant) @name)) @definition.class
178
169
  (method name: (identifier) @name) @definition.method
179
170
  (singleton_method name: (identifier) @name) @definition.method
180
171
  (call method: (identifier) @call.name) @call
172
+ (call method: (call method: (identifier) @call.name)) @call
173
+ `;
174
+
175
+ // Route detection queries - Express.js style (JS/TS)
176
+ export const EXPRESS_ROUTE_QUERIES = `
177
+ (call_expression
178
+ function: (member_expression
179
+ object: (call_expression
180
+ function: (identifier) @route.app)
181
+ property: (property_identifier) @route.method)
182
+ arguments: (argument_list
183
+ (string (string_content) @route.path)?)
184
+ ) @route.def
185
+ (call_expression
186
+ function: (member_expression
187
+ object: (identifier) @route.app
188
+ property: (property_identifier) @route.method)
189
+ arguments: (argument_list
190
+ (string (string_content) @route.path)?)
191
+ ) @route.def
192
+ `;
193
+
194
+ // Route detection queries - Flask/FastAPI style (Python)
195
+ export const FLASK_ROUTE_QUERIES = `
196
+ (decorated_definition
197
+ decorator: (call
198
+ function: (attribute
199
+ object: (identifier) @route.decorator)
200
+ arguments: (argument_list (string (string_content) @route.path)))
201
+ definition: (function_definition name: (identifier) @route.name))
202
+ ) @route.def
203
+
204
+ (decorated_definition
205
+ decorator: (call
206
+ function: (identifier) @route.method
207
+ arguments: (argument_list (string (string_content) @route.path)))
208
+ definition: (function_definition name: (identifier) @route.name))
209
+ ) @route.def
210
+ `;
211
+
212
+ // Route detection queries - Spring style (Java)
213
+ export const SPRING_ROUTE_QUERIES = `
214
+ (method_declaration
215
+ name: (identifier) @route.name
216
+ (annotation (name (identifier) @route.anno))
217
+ ) @route.def
218
+
219
+ (class_declaration
220
+ name: (identifier) @route.name
221
+ (annotation (name (identifier) @route.anno))
222
+ ) @route.def
223
+ `;
224
+
225
+ // Route detection queries - Gin style (Go)
226
+ export const GIN_ROUTE_QUERIES = `
227
+ (call_expression
228
+ function: (identifier) @route.method
229
+ arguments: (argument_list (string (string_content) @route.path)))
230
+ ) @route.def
231
+
232
+ (call_expression
233
+ function: (call_expression
234
+ function: (identifier) @route.method)
235
+ arguments: (argument_list
236
+ (string (string_content) @route.path)?)
237
+ ) @route.def
238
+
239
+ (call_expression
240
+ function: (selector_expression
241
+ field: (identifier) @route.method
242
+ object: (call_expression function: (identifier)))
243
+ arguments: (argument_list (string (string_content) @route.path)))
244
+ ) @route.def
245
+ `;
246
+
247
+ // Route detection queries - Laravel style (PHP)
248
+ export const LARAVEL_ROUTE_QUERIES = `
249
+ (call_expression
250
+ function: (call_expression
251
+ function: (identifier) @route.method)
252
+ arguments: (argument_list (string (string_content) @route.path)))
253
+ ) @route.def
254
+
255
+ (call_expression
256
+ function: (identifier) @route.method
257
+ arguments: (argument_list (string (string_content) @route.path)))
258
+ ) @route.def
259
+ `;
260
+
261
+ // Route detection queries - Rails style (Ruby)
262
+ export const RAILS_ROUTE_QUERIES = `
263
+ (call
264
+ method: (identifier) @route.method
265
+ arguments: (argument_list (string (string_content) @route.path)))
266
+ ) @route.def
181
267
  `;
@@ -0,0 +1,261 @@
1
+ import * as path from 'node:path'
2
+ import type { ParsedImport } from '../types.js'
3
+
4
+ export class TreeSitterResolver {
5
+ constructor(
6
+ private readonly projectRoot: string,
7
+ private readonly language: string,
8
+ ) {}
9
+
10
+ resolve(imp: ParsedImport, fromFile: string, allProjectFiles: string[] = []): ParsedImport {
11
+ const source = imp.source
12
+
13
+ if (!this.isRelativeImport(source)) {
14
+ return { ...imp, resolvedPath: '' }
15
+ }
16
+
17
+ const fileSet = allProjectFiles.length > 0 ? new Set(allProjectFiles.map(f => f.replace(/\\/g, '/'))) : null
18
+ const resolved = this.resolvePath(source, fromFile, fileSet)
19
+
20
+ return { ...imp, resolvedPath: resolved }
21
+ }
22
+
23
+ resolveAll(imports: ParsedImport[], fromFile: string, allProjectFiles: string[] = []): ParsedImport[] {
24
+ const fileSet = allProjectFiles.length > 0
25
+ ? new Set(allProjectFiles.map(f => f.replace(/\\/g, '/')))
26
+ : null
27
+
28
+ return imports.map(imp => {
29
+ if (!this.isRelativeImport(imp.source)) {
30
+ // For Java, also try resolving package imports if we have file set
31
+ if (this.language === 'java' && fileSet) {
32
+ const resolved = this.resolvePath(imp.source, fromFile, fileSet)
33
+ return { ...imp, resolvedPath: resolved }
34
+ }
35
+ return { ...imp, resolvedPath: '' }
36
+ }
37
+ const resolved = this.resolvePath(imp.source, fromFile, fileSet)
38
+ return { ...imp, resolvedPath: resolved }
39
+ })
40
+ }
41
+
42
+ private isRelativeImport(source: string): boolean {
43
+ if (!source || source.trim() === '') return false
44
+
45
+ const trimmed = source.trim()
46
+
47
+ switch (this.language) {
48
+ case 'python':
49
+ return trimmed.startsWith('.') || trimmed.startsWith('/')
50
+ case 'java':
51
+ // For Java, both relative (.) and package imports can be resolved
52
+ return true
53
+ case 'c':
54
+ case 'cpp':
55
+ return trimmed.startsWith('"') || trimmed.startsWith('<')
56
+ case 'csharp':
57
+ return trimmed.startsWith('.')
58
+ case 'rust':
59
+ return trimmed.startsWith('crate::') || trimmed.startsWith('super::') || trimmed.startsWith('self::')
60
+ case 'php':
61
+ return trimmed.startsWith('\\') || trimmed.startsWith('..')
62
+ case 'ruby':
63
+ return trimmed.startsWith('./') || trimmed.startsWith('../')
64
+ case 'go':
65
+ return trimmed.startsWith('.') || trimmed.startsWith('/')
66
+ default:
67
+ return trimmed.startsWith('.') || trimmed.startsWith('/')
68
+ }
69
+ }
70
+
71
+ private resolvePath(source: string, fromFile: string, fileSet: Set<string> | null): string {
72
+ const normalizedSource = this.normalizeSource(source)
73
+ const normalizedFrom = fromFile.replace(/\\/g, '/')
74
+ const baseDir = path.dirname(normalizedFrom)
75
+
76
+ let resolved: string
77
+ if (normalizedSource.startsWith('/')) {
78
+ resolved = normalizedSource.slice(1)
79
+ } else {
80
+ resolved = path.posix.normalize(path.posix.join(baseDir, normalizedSource))
81
+ }
82
+
83
+ return this.probeExtensions(resolved, fileSet) ?? ''
84
+ }
85
+
86
+ private normalizeSource(source: string): string {
87
+ let normalized = source.trim()
88
+
89
+ if (!normalized) return ''
90
+
91
+ if (normalized.startsWith('"') && normalized.endsWith('"')) {
92
+ normalized = normalized.slice(1, -1)
93
+ }
94
+ if (normalized.startsWith('<') && normalized.endsWith('>')) {
95
+ normalized = normalized.slice(1, -1)
96
+ }
97
+
98
+ if (normalized.startsWith('\\')) {
99
+ normalized = normalized.slice(1)
100
+ }
101
+
102
+ normalized = normalized.replace(/^\.+/, '.')
103
+
104
+ if (normalized.startsWith('crate::')) {
105
+ normalized = normalized.replace('crate::', 'src/')
106
+ }
107
+
108
+ if (normalized.startsWith('super::')) {
109
+ normalized = normalized.replace('super::', '')
110
+ }
111
+
112
+ if (normalized.startsWith('self::')) {
113
+ normalized = normalized.replace('self::', '')
114
+ }
115
+
116
+ normalized = normalized.replace(/\\/g, '/')
117
+
118
+ normalized = this.handleLanguageSpecificImports(normalized)
119
+
120
+ return normalized
121
+ }
122
+
123
+ private handleLanguageSpecificImports(normalized: string): string {
124
+ switch (this.language) {
125
+ case 'python':
126
+ // Handle relative imports: ./module, ../package, .
127
+ normalized = normalized.replace(/\./g, '/')
128
+
129
+ // Handle "from package import module" - module is the last part
130
+ // Convert to: package/module
131
+ if (normalized.includes('/')) {
132
+ const parts = normalized.split('/')
133
+ const last = parts[parts.length - 1]
134
+ // If last part doesn't look like a module, add __init__
135
+ if (last && !last.includes('.') && !last.includes('__init__')) {
136
+ // Check if it's a file pattern or directory
137
+ parts[parts.length - 1] = last
138
+ }
139
+ normalized = parts.join('/')
140
+ }
141
+
142
+ // Handle case where import is just a module name (no ./)
143
+ // e.g., "from os import path" -> os/path
144
+ break
145
+
146
+ case 'java':
147
+ // Convert package dots to path separators
148
+ // e.g., "java.util.List" -> "java/util/List"
149
+ normalized = normalized.replace(/\./g, '/')
150
+ break
151
+
152
+ case 'ruby':
153
+ normalized = normalized.replace(/::/g, '/')
154
+ if (normalized.startsWith('./')) {
155
+ normalized = normalized.slice(2)
156
+ }
157
+ if (normalized.startsWith('../')) {
158
+ normalized = normalized.replace('../', '')
159
+ }
160
+ break
161
+
162
+ case 'php':
163
+ normalized = normalized.replace(/\\/g, '/')
164
+ normalized = normalized.replace(/^App\//, '')
165
+ break
166
+
167
+ default:
168
+ break
169
+ }
170
+
171
+ return normalized
172
+ }
173
+
174
+ private probeExtensions(resolved: string, fileSet: Set<string> | null): string | null {
175
+ const extensions = this.getProbeExtensions()
176
+
177
+ const directMatches = new Set<string>()
178
+
179
+ for (const ext of extensions) {
180
+ directMatches.add(resolved + ext)
181
+
182
+ const parts = resolved.split('/')
183
+ const lastIdx = parts.length - 1
184
+ const lastPart = parts[lastIdx]
185
+
186
+ if (lastPart && !lastPart.includes('.')) {
187
+ parts[lastIdx] = '__init__'
188
+ directMatches.add(parts.join('/') + ext)
189
+ }
190
+
191
+ if (!resolved.includes('/index') && !resolved.endsWith('/')) {
192
+ directMatches.add(resolved + '/index' + ext)
193
+ }
194
+ }
195
+
196
+ if (fileSet) {
197
+ for (const candidate of directMatches) {
198
+ if (fileSet.has(candidate)) {
199
+ return candidate
200
+ }
201
+ }
202
+
203
+ const resolvedLower = resolved.toLowerCase()
204
+ for (const file of fileSet) {
205
+ if (file.toLowerCase() === resolvedLower + '.py' ||
206
+ file.toLowerCase() === resolvedLower + '.js' ||
207
+ file.toLowerCase() === resolvedLower + '.ts' ||
208
+ file.toLowerCase() === resolvedLower + '.java' ||
209
+ file.toLowerCase() === resolvedLower + '.go' ||
210
+ file.toLowerCase() === resolvedLower + '.rs' ||
211
+ file.toLowerCase() === resolvedLower + '.cs' ||
212
+ file.toLowerCase() === resolvedLower + '.c' ||
213
+ file.toLowerCase() === resolvedLower + '.cpp' ||
214
+ file.toLowerCase() === resolvedLower + '.php' ||
215
+ file.toLowerCase() === resolvedLower + '.rb') {
216
+ return file
217
+ }
218
+ }
219
+
220
+ const resolvedEndsWith = resolved.split('/').pop()?.toLowerCase() || ''
221
+ for (const file of fileSet) {
222
+ const fileName = file.split('/').pop()?.toLowerCase() || ''
223
+ if (fileName.startsWith(resolvedEndsWith) ||
224
+ resolvedEndsWith.includes(fileName.replace(/\.[^.]+$/, ''))) {
225
+ return file
226
+ }
227
+ }
228
+ }
229
+
230
+ if (extensions.length > 0) {
231
+ return resolved + extensions[0]
232
+ }
233
+
234
+ return null
235
+ }
236
+
237
+ private getProbeExtensions(): string[] {
238
+ switch (this.language) {
239
+ case 'python':
240
+ return ['.py', '/__init__.py']
241
+ case 'java':
242
+ return ['.java']
243
+ case 'c':
244
+ return ['.c', '.h']
245
+ case 'cpp':
246
+ return ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hh']
247
+ case 'csharp':
248
+ return ['.cs']
249
+ case 'rust':
250
+ return ['.rs', '/lib.rs', '/mod.rs']
251
+ case 'php':
252
+ return ['.php']
253
+ case 'ruby':
254
+ return ['.rb', '.rbw']
255
+ case 'go':
256
+ return ['.go']
257
+ default:
258
+ return ['']
259
+ }
260
+ }
261
+ }
@@ -97,8 +97,22 @@ export class BM25Index {
97
97
  const tfNorm = (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (doc.length / this.avgDocLength)))
98
98
  let termScore = idf * tfNorm
99
99
 
100
- // Bonus for direct name match in the ID
101
- if (doc.id.toLowerCase().includes(term.toLowerCase())) {
100
+ // Extract function name from ID for better matching
101
+ // ID format: fn:path:functionName
102
+ const fnNameInId = doc.id.includes(':')
103
+ ? doc.id.split(':').pop()?.toLowerCase() ?? doc.id.toLowerCase()
104
+ : doc.id.toLowerCase()
105
+
106
+ // Strong bonus for name prefix match (login matches loginUser)
107
+ if (fnNameInId.startsWith(term.toLowerCase())) {
108
+ termScore += 2.0
109
+ }
110
+ // Bonus for name contains match
111
+ else if (fnNameInId.includes(term.toLowerCase())) {
112
+ termScore += 1.0
113
+ }
114
+ // Fallback: any ID match
115
+ else if (doc.id.toLowerCase().includes(term.toLowerCase())) {
102
116
  termScore += 0.5
103
117
  }
104
118
 
package/src/utils/fs.ts CHANGED
@@ -275,7 +275,7 @@ function inferContextFileType(filePath: string): ContextFileType {
275
275
  }
276
276
 
277
277
  /** Recognised project language */
278
- export type ProjectLanguage = 'typescript' | 'javascript' | 'python' | 'go' | 'rust' | 'java' | 'ruby' | 'php' | 'csharp' | 'unknown'
278
+ export type ProjectLanguage = 'typescript' | 'javascript' | 'python' | 'go' | 'rust' | 'java' | 'ruby' | 'php' | 'csharp' | 'c' | 'cpp' | 'unknown'
279
279
 
280
280
  /** Auto-detect the project's primary language from manifest files */
281
281
  export async function detectProjectLanguage(projectRoot: string): Promise<ProjectLanguage> {
@@ -295,6 +295,8 @@ export async function detectProjectLanguage(projectRoot: string): Promise<Projec
295
295
  if (await exists('pom.xml') || await exists('build.gradle') || await exists('build.gradle.kts')) return 'java'
296
296
  if (await exists('composer.json')) return 'php'
297
297
  if (await hasGlob('*.csproj') || await hasGlob('*.sln')) return 'csharp'
298
+ if (await hasGlob('CMakeLists.txt') || await hasGlob('**/*.cmake') || await hasGlob('*.cpp')) return 'cpp'
299
+ if (await hasGlob('*.c') || await hasGlob('*.h')) return 'c'
298
300
  if (await exists('package.json')) return 'javascript'
299
301
  return 'unknown'
300
302
  }
@@ -350,6 +352,16 @@ export function getDiscoveryPatterns(language: ProjectLanguage): { patterns: str
350
352
  patterns: ['**/*.cs'],
351
353
  ignore: [...commonIgnore, '**/bin/**', '**/obj/**'],
352
354
  }
355
+ case 'cpp':
356
+ return {
357
+ patterns: ['**/*.cpp', '**/*.cc', '**/*.cxx', '**/*.hpp', '**/*.hxx', '**/*.h'],
358
+ ignore: [...commonIgnore, '**/build/**', '**/cmake-build-*/**'],
359
+ }
360
+ case 'c':
361
+ return {
362
+ patterns: ['**/*.c', '**/*.h'],
363
+ ignore: [...commonIgnore, '**/build/**'],
364
+ }
353
365
  default:
354
366
  // Fallback: discover JS/TS (most common)
355
367
  return {
@@ -568,6 +580,24 @@ const LANGUAGE_IGNORE_TEMPLATES: Record<ProjectLanguage, string[]> = {
568
580
  'obj/',
569
581
  '',
570
582
  ],
583
+ c: [
584
+ '# Build artifacts',
585
+ 'build/',
586
+ 'obj/',
587
+ '*.o',
588
+ '*.a',
589
+ '',
590
+ ],
591
+ cpp: [
592
+ '# Build artifacts',
593
+ 'build/',
594
+ 'cmake-build-*/',
595
+ '*.o',
596
+ '*.a',
597
+ '*.so',
598
+ '*.dll',
599
+ '',
600
+ ],
571
601
  unknown: [
572
602
  '# Test files (add your patterns here)',
573
603
  'tests/',
@@ -0,0 +1,36 @@
1
+ """Test module for Python parsing."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import List, Optional
6
+
7
+
8
+ class UserRepository:
9
+ """Handles user data operations."""
10
+
11
+ def __init__(self, db_path: str):
12
+ self.db_path = db_path
13
+
14
+ def find_by_id(self, user_id: int) -> Optional[dict]:
15
+ """Find user by ID."""
16
+ pass
17
+
18
+ def find_all(self) -> List[dict]:
19
+ """Get all users."""
20
+ pass
21
+
22
+
23
+ def authenticate(username: str, password: str) -> bool:
24
+ """Authenticate user credentials."""
25
+ return True
26
+
27
+
28
+ def get_user_profile(user_id: int) -> dict:
29
+ """Get user profile data."""
30
+ repo = UserRepository("users.db")
31
+ return repo.find_by_id(user_id)
32
+
33
+
34
+ # Private function - should NOT be exported
35
+ def _internal_helper():
36
+ pass
@@ -118,7 +118,8 @@ describe('ImpactAnalyzer', () => {
118
118
  ])
119
119
  const analyzer = new ImpactAnalyzer(graph)
120
120
  const result = analyzer.analyze(['fn:src/b.ts:b'])
121
- expect(result.confidence).toBeGreaterThanOrEqual(0.8)
121
+ // With the fix: small paths (2 hops) should have high confidence
122
+ expect(result.confidence).toBeGreaterThanOrEqual(0.7)
122
123
  })
123
124
 
124
125
  it('does not include changed nodes in impacted', () => {