gitnexus 1.4.9 → 1.5.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 (186) hide show
  1. package/README.md +6 -5
  2. package/dist/cli/ai-context.d.ts +4 -1
  3. package/dist/cli/ai-context.js +19 -11
  4. package/dist/cli/analyze.d.ts +6 -0
  5. package/dist/cli/analyze.js +105 -251
  6. package/dist/cli/eval-server.js +20 -11
  7. package/dist/cli/index-repo.js +20 -22
  8. package/dist/cli/index.js +8 -7
  9. package/dist/cli/mcp.js +1 -1
  10. package/dist/cli/serve.js +29 -1
  11. package/dist/cli/setup.js +9 -9
  12. package/dist/cli/skill-gen.js +15 -9
  13. package/dist/cli/wiki.d.ts +2 -0
  14. package/dist/cli/wiki.js +141 -26
  15. package/dist/config/ignore-service.js +102 -22
  16. package/dist/config/supported-languages.d.ts +8 -42
  17. package/dist/config/supported-languages.js +8 -43
  18. package/dist/core/augmentation/engine.js +19 -7
  19. package/dist/core/embeddings/embedder.js +19 -15
  20. package/dist/core/embeddings/embedding-pipeline.js +6 -6
  21. package/dist/core/embeddings/http-client.js +3 -3
  22. package/dist/core/embeddings/text-generator.js +9 -24
  23. package/dist/core/embeddings/types.d.ts +1 -1
  24. package/dist/core/embeddings/types.js +1 -7
  25. package/dist/core/graph/graph.js +6 -2
  26. package/dist/core/graph/types.d.ts +9 -59
  27. package/dist/core/ingestion/ast-cache.js +3 -3
  28. package/dist/core/ingestion/call-processor.d.ts +20 -2
  29. package/dist/core/ingestion/call-processor.js +347 -144
  30. package/dist/core/ingestion/call-routing.js +10 -4
  31. package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +10 -0
  32. package/dist/core/ingestion/call-sites/extract-language-call-site.js +22 -0
  33. package/dist/core/ingestion/call-sites/java.d.ts +9 -0
  34. package/dist/core/ingestion/call-sites/java.js +30 -0
  35. package/dist/core/ingestion/cluster-enricher.js +6 -8
  36. package/dist/core/ingestion/cobol/cobol-copy-expander.js +10 -3
  37. package/dist/core/ingestion/cobol/cobol-preprocessor.js +287 -81
  38. package/dist/core/ingestion/cobol/jcl-parser.js +1 -1
  39. package/dist/core/ingestion/cobol/jcl-processor.js +1 -1
  40. package/dist/core/ingestion/cobol-processor.js +102 -56
  41. package/dist/core/ingestion/community-processor.js +21 -15
  42. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -1
  43. package/dist/core/ingestion/entry-point-scoring.js +5 -6
  44. package/dist/core/ingestion/export-detection.js +32 -9
  45. package/dist/core/ingestion/field-extractor.d.ts +1 -1
  46. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +8 -12
  47. package/dist/core/ingestion/field-extractors/configs/csharp.js +45 -2
  48. package/dist/core/ingestion/field-extractors/configs/dart.js +5 -3
  49. package/dist/core/ingestion/field-extractors/configs/go.js +3 -7
  50. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -0
  51. package/dist/core/ingestion/field-extractors/configs/helpers.js +14 -0
  52. package/dist/core/ingestion/field-extractors/configs/jvm.js +7 -7
  53. package/dist/core/ingestion/field-extractors/configs/php.js +9 -11
  54. package/dist/core/ingestion/field-extractors/configs/python.js +1 -1
  55. package/dist/core/ingestion/field-extractors/configs/ruby.js +4 -3
  56. package/dist/core/ingestion/field-extractors/configs/rust.js +2 -5
  57. package/dist/core/ingestion/field-extractors/configs/swift.js +9 -7
  58. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +2 -6
  59. package/dist/core/ingestion/field-extractors/generic.d.ts +5 -2
  60. package/dist/core/ingestion/field-extractors/generic.js +6 -0
  61. package/dist/core/ingestion/field-extractors/typescript.d.ts +1 -1
  62. package/dist/core/ingestion/field-extractors/typescript.js +1 -1
  63. package/dist/core/ingestion/field-types.d.ts +4 -2
  64. package/dist/core/ingestion/filesystem-walker.js +3 -3
  65. package/dist/core/ingestion/framework-detection.d.ts +1 -1
  66. package/dist/core/ingestion/framework-detection.js +355 -85
  67. package/dist/core/ingestion/heritage-processor.d.ts +24 -0
  68. package/dist/core/ingestion/heritage-processor.js +99 -8
  69. package/dist/core/ingestion/import-processor.js +44 -15
  70. package/dist/core/ingestion/import-resolvers/csharp.js +7 -3
  71. package/dist/core/ingestion/import-resolvers/dart.js +1 -1
  72. package/dist/core/ingestion/import-resolvers/go.js +4 -2
  73. package/dist/core/ingestion/import-resolvers/jvm.js +4 -4
  74. package/dist/core/ingestion/import-resolvers/php.js +4 -4
  75. package/dist/core/ingestion/import-resolvers/python.js +1 -1
  76. package/dist/core/ingestion/import-resolvers/rust.js +9 -3
  77. package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
  78. package/dist/core/ingestion/import-resolvers/standard.js +6 -5
  79. package/dist/core/ingestion/import-resolvers/swift.js +2 -1
  80. package/dist/core/ingestion/import-resolvers/utils.js +26 -7
  81. package/dist/core/ingestion/language-config.js +5 -4
  82. package/dist/core/ingestion/language-provider.d.ts +7 -2
  83. package/dist/core/ingestion/languages/c-cpp.js +106 -21
  84. package/dist/core/ingestion/languages/cobol.js +1 -1
  85. package/dist/core/ingestion/languages/csharp.js +96 -19
  86. package/dist/core/ingestion/languages/dart.js +23 -7
  87. package/dist/core/ingestion/languages/go.js +1 -1
  88. package/dist/core/ingestion/languages/index.d.ts +1 -1
  89. package/dist/core/ingestion/languages/index.js +2 -3
  90. package/dist/core/ingestion/languages/java.js +4 -1
  91. package/dist/core/ingestion/languages/kotlin.js +60 -13
  92. package/dist/core/ingestion/languages/php.js +102 -25
  93. package/dist/core/ingestion/languages/python.js +28 -5
  94. package/dist/core/ingestion/languages/ruby.js +56 -14
  95. package/dist/core/ingestion/languages/rust.js +55 -11
  96. package/dist/core/ingestion/languages/swift.js +112 -27
  97. package/dist/core/ingestion/languages/typescript.js +95 -19
  98. package/dist/core/ingestion/markdown-processor.js +5 -5
  99. package/dist/core/ingestion/method-extractors/configs/csharp.d.ts +2 -0
  100. package/dist/core/ingestion/method-extractors/configs/csharp.js +283 -0
  101. package/dist/core/ingestion/method-extractors/configs/jvm.d.ts +3 -0
  102. package/dist/core/ingestion/method-extractors/configs/jvm.js +326 -0
  103. package/dist/core/ingestion/method-extractors/generic.d.ts +5 -0
  104. package/dist/core/ingestion/method-extractors/generic.js +137 -0
  105. package/dist/core/ingestion/method-types.d.ts +61 -0
  106. package/dist/core/ingestion/method-types.js +2 -0
  107. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  108. package/dist/core/ingestion/mro-processor.js +12 -8
  109. package/dist/core/ingestion/named-binding-processor.js +2 -2
  110. package/dist/core/ingestion/named-bindings/rust.js +3 -1
  111. package/dist/core/ingestion/parsing-processor.js +74 -24
  112. package/dist/core/ingestion/pipeline.d.ts +2 -1
  113. package/dist/core/ingestion/pipeline.js +208 -102
  114. package/dist/core/ingestion/process-processor.js +12 -10
  115. package/dist/core/ingestion/resolution-context.js +3 -3
  116. package/dist/core/ingestion/route-extractors/middleware.js +31 -7
  117. package/dist/core/ingestion/route-extractors/php.js +2 -1
  118. package/dist/core/ingestion/route-extractors/response-shapes.js +8 -4
  119. package/dist/core/ingestion/structure-processor.d.ts +1 -1
  120. package/dist/core/ingestion/structure-processor.js +4 -4
  121. package/dist/core/ingestion/symbol-table.d.ts +1 -1
  122. package/dist/core/ingestion/symbol-table.js +22 -6
  123. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  124. package/dist/core/ingestion/tree-sitter-queries.js +1 -1
  125. package/dist/core/ingestion/type-env.d.ts +2 -2
  126. package/dist/core/ingestion/type-env.js +75 -50
  127. package/dist/core/ingestion/type-extractors/c-cpp.js +33 -30
  128. package/dist/core/ingestion/type-extractors/csharp.js +24 -14
  129. package/dist/core/ingestion/type-extractors/dart.js +6 -8
  130. package/dist/core/ingestion/type-extractors/go.js +7 -6
  131. package/dist/core/ingestion/type-extractors/jvm.js +10 -21
  132. package/dist/core/ingestion/type-extractors/php.js +26 -13
  133. package/dist/core/ingestion/type-extractors/python.js +11 -15
  134. package/dist/core/ingestion/type-extractors/ruby.js +8 -3
  135. package/dist/core/ingestion/type-extractors/rust.js +6 -8
  136. package/dist/core/ingestion/type-extractors/shared.js +134 -50
  137. package/dist/core/ingestion/type-extractors/swift.js +16 -13
  138. package/dist/core/ingestion/type-extractors/typescript.js +23 -15
  139. package/dist/core/ingestion/utils/ast-helpers.d.ts +8 -8
  140. package/dist/core/ingestion/utils/ast-helpers.js +72 -35
  141. package/dist/core/ingestion/utils/call-analysis.d.ts +2 -0
  142. package/dist/core/ingestion/utils/call-analysis.js +96 -49
  143. package/dist/core/ingestion/utils/event-loop.js +1 -1
  144. package/dist/core/ingestion/workers/parse-worker.d.ts +7 -2
  145. package/dist/core/ingestion/workers/parse-worker.js +364 -84
  146. package/dist/core/ingestion/workers/worker-pool.js +5 -10
  147. package/dist/core/lbug/csv-generator.js +54 -15
  148. package/dist/core/lbug/lbug-adapter.d.ts +5 -0
  149. package/dist/core/lbug/lbug-adapter.js +86 -23
  150. package/dist/core/lbug/schema.d.ts +3 -6
  151. package/dist/core/lbug/schema.js +6 -30
  152. package/dist/core/run-analyze.d.ts +49 -0
  153. package/dist/core/run-analyze.js +257 -0
  154. package/dist/core/tree-sitter/parser-loader.d.ts +1 -1
  155. package/dist/core/tree-sitter/parser-loader.js +1 -1
  156. package/dist/core/wiki/cursor-client.js +2 -7
  157. package/dist/core/wiki/generator.js +38 -23
  158. package/dist/core/wiki/graph-queries.js +10 -10
  159. package/dist/core/wiki/html-viewer.js +7 -3
  160. package/dist/core/wiki/llm-client.d.ts +23 -2
  161. package/dist/core/wiki/llm-client.js +96 -26
  162. package/dist/core/wiki/prompts.js +7 -6
  163. package/dist/mcp/core/embedder.js +1 -1
  164. package/dist/mcp/core/lbug-adapter.d.ts +4 -1
  165. package/dist/mcp/core/lbug-adapter.js +17 -7
  166. package/dist/mcp/local/local-backend.js +247 -95
  167. package/dist/mcp/resources.js +14 -6
  168. package/dist/mcp/server.js +13 -5
  169. package/dist/mcp/staleness.js +5 -1
  170. package/dist/mcp/tools.js +100 -23
  171. package/dist/server/analyze-job.d.ts +53 -0
  172. package/dist/server/analyze-job.js +146 -0
  173. package/dist/server/analyze-worker.d.ts +13 -0
  174. package/dist/server/analyze-worker.js +59 -0
  175. package/dist/server/api.js +795 -44
  176. package/dist/server/git-clone.d.ts +25 -0
  177. package/dist/server/git-clone.js +91 -0
  178. package/dist/storage/git.js +1 -3
  179. package/dist/storage/repo-manager.d.ts +5 -2
  180. package/dist/storage/repo-manager.js +4 -4
  181. package/dist/types/pipeline.d.ts +1 -21
  182. package/dist/types/pipeline.js +1 -18
  183. package/hooks/claude/gitnexus-hook.cjs +52 -22
  184. package/package.json +13 -13
  185. package/dist/core/ingestion/utils/language-detection.d.ts +0 -9
  186. package/dist/core/ingestion/utils/language-detection.js +0 -70
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
- import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, isWriteQuery } from '../core/lbug-adapter.js';
10
+ import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, isWriteQuery, } from '../core/lbug-adapter.js';
11
11
  export { isWriteQuery };
12
12
  // Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
13
13
  // at MCP server startup — crashes on unsupported Node ABI versions (#89)
@@ -22,25 +22,70 @@ import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-ma
22
22
  */
23
23
  export function isTestFilePath(filePath) {
24
24
  const p = filePath.toLowerCase().replace(/\\/g, '/');
25
- return (p.includes('.test.') || p.includes('.spec.') ||
26
- p.includes('__tests__/') || p.includes('__mocks__/') ||
27
- p.includes('/test/') || p.includes('/tests/') ||
28
- p.includes('/testing/') || p.includes('/fixtures/') ||
29
- p.endsWith('_test.go') || p.endsWith('_test.py') ||
30
- p.endsWith('_spec.rb') || p.endsWith('_test.rb') || p.includes('/spec/') ||
31
- p.includes('/test_') || p.includes('/conftest.'));
25
+ return (p.includes('.test.') ||
26
+ p.includes('.spec.') ||
27
+ p.includes('__tests__/') ||
28
+ p.includes('__mocks__/') ||
29
+ p.includes('/test/') ||
30
+ p.includes('/tests/') ||
31
+ p.includes('/testing/') ||
32
+ p.includes('/fixtures/') ||
33
+ p.endsWith('_test.go') ||
34
+ p.endsWith('_test.py') ||
35
+ p.endsWith('_spec.rb') ||
36
+ p.endsWith('_test.rb') ||
37
+ p.includes('/spec/') ||
38
+ p.includes('/test_') ||
39
+ p.includes('/conftest.'));
32
40
  }
33
41
  /** Valid LadybugDB node labels for safe Cypher query construction */
34
42
  export const VALID_NODE_LABELS = new Set([
35
- 'File', 'Folder', 'Function', 'Class', 'Interface', 'Method', 'CodeElement',
36
- 'Community', 'Process', 'Struct', 'Enum', 'Macro', 'Typedef', 'Union',
37
- 'Namespace', 'Trait', 'Impl', 'TypeAlias', 'Const', 'Static', 'Property',
38
- 'Record', 'Delegate', 'Annotation', 'Constructor', 'Template', 'Module',
43
+ 'File',
44
+ 'Folder',
45
+ 'Function',
46
+ 'Class',
47
+ 'Interface',
48
+ 'Method',
49
+ 'CodeElement',
50
+ 'Community',
51
+ 'Process',
52
+ 'Struct',
53
+ 'Enum',
54
+ 'Macro',
55
+ 'Typedef',
56
+ 'Union',
57
+ 'Namespace',
58
+ 'Trait',
59
+ 'Impl',
60
+ 'TypeAlias',
61
+ 'Const',
62
+ 'Static',
63
+ 'Property',
64
+ 'Record',
65
+ 'Delegate',
66
+ 'Annotation',
67
+ 'Constructor',
68
+ 'Template',
69
+ 'Module',
39
70
  'Route',
40
71
  'Tool',
41
72
  ]);
42
73
  /** Valid relation types for impact analysis filtering */
43
- export const VALID_RELATION_TYPES = new Set(['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'OVERRIDES', 'ACCESSES', 'HANDLES_ROUTE', 'FETCHES', 'HANDLES_TOOL', 'ENTRY_POINT_OF', 'WRAPS']);
74
+ export const VALID_RELATION_TYPES = new Set([
75
+ 'CALLS',
76
+ 'IMPORTS',
77
+ 'EXTENDS',
78
+ 'IMPLEMENTS',
79
+ 'HAS_METHOD',
80
+ 'HAS_PROPERTY',
81
+ 'OVERRIDES',
82
+ 'ACCESSES',
83
+ 'HANDLES_ROUTE',
84
+ 'FETCHES',
85
+ 'HANDLES_TOOL',
86
+ 'ENTRY_POINT_OF',
87
+ 'WRAPS',
88
+ ]);
44
89
  /**
45
90
  * Per-relation-type confidence floor for impact analysis.
46
91
  *
@@ -187,10 +232,10 @@ export class LocalBackend {
187
232
  throw new Error('No indexed repositories. Run: gitnexus analyze');
188
233
  }
189
234
  if (repoParam) {
190
- const names = [...this.repos.values()].map(h => h.name);
235
+ const names = [...this.repos.values()].map((h) => h.name);
191
236
  throw new Error(`Repository "${repoParam}" not found. Available: ${names.join(', ')}`);
192
237
  }
193
- const names = [...this.repos.values()].map(h => h.name);
238
+ const names = [...this.repos.values()].map((h) => h.name);
194
239
  throw new Error(`Multiple repositories indexed. Specify which one with the "repo" parameter. Available: ${names.join(', ')}`);
195
240
  }
196
241
  /**
@@ -306,7 +351,7 @@ export class LocalBackend {
306
351
  */
307
352
  async listRepos() {
308
353
  await this.refreshRepos();
309
- return [...this.repos.values()].map(h => ({
354
+ return [...this.repos.values()].map((h) => ({
310
355
  name: h.name,
311
356
  path: h.repoPath,
312
357
  indexedAt: h.indexedAt,
@@ -516,14 +561,14 @@ export class LocalBackend {
516
561
  }
517
562
  // Step 3: Rank processes by aggregate score + internal cohesion boost
518
563
  const rankedProcesses = Array.from(processMap.values())
519
- .map(p => ({
564
+ .map((p) => ({
520
565
  ...p,
521
- priority: p.totalScore + (p.cohesionBoost * 0.1), // cohesion as subtle ranking signal
566
+ priority: p.totalScore + p.cohesionBoost * 0.1, // cohesion as subtle ranking signal
522
567
  }))
523
568
  .sort((a, b) => b.priority - a.priority)
524
569
  .slice(0, processLimit);
525
570
  // Step 4: Build response
526
- const processes = rankedProcesses.map(p => ({
571
+ const processes = rankedProcesses.map((p) => ({
527
572
  id: p.id,
528
573
  summary: p.heuristicLabel || p.label,
529
574
  priority: Math.round(p.priority * 1000) / 1000,
@@ -531,13 +576,13 @@ export class LocalBackend {
531
576
  process_type: p.processType,
532
577
  step_count: p.stepCount,
533
578
  }));
534
- const processSymbols = rankedProcesses.flatMap(p => p.symbols.slice(0, maxSymbolsPerProcess).map(s => ({
579
+ const processSymbols = rankedProcesses.flatMap((p) => p.symbols.slice(0, maxSymbolsPerProcess).map((s) => ({
535
580
  ...s,
536
581
  // remove internal fields
537
582
  })));
538
583
  // Deduplicate process_symbols by id
539
584
  const seen = new Set();
540
- const dedupedSymbols = processSymbols.filter(s => {
585
+ const dedupedSymbols = processSymbols.filter((s) => {
541
586
  if (seen.has(s.id))
542
587
  return false;
543
588
  seen.add(s.id);
@@ -547,7 +592,9 @@ export class LocalBackend {
547
592
  processes,
548
593
  process_symbols: dedupedSymbols,
549
594
  definitions: definitions.slice(0, 20), // cap standalone definitions
550
- ...(!ftsUsed && { warning: 'FTS extension unavailable - keyword search degraded. Run: gitnexus analyze --force to rebuild indexes.' }),
595
+ ...(!ftsUsed && {
596
+ warning: 'FTS extension unavailable - keyword search degraded. Run: gitnexus analyze --force to rebuild indexes.',
597
+ }),
551
598
  };
552
599
  }
553
600
  /**
@@ -563,7 +610,7 @@ export class LocalBackend {
563
610
  console.error('GitNexus: BM25/FTS search failed (FTS indexes may not exist) -', err.message);
564
611
  return { results: [], ftsUsed: false };
565
612
  }
566
- const ftsUsed = bm25Results.length === 0 || (bm25Results[0]?.ftsUsed !== false);
613
+ const ftsUsed = bm25Results.length === 0 || bm25Results[0]?.ftsUsed !== false;
567
614
  const results = [];
568
615
  for (const bm25Result of bm25Results) {
569
616
  const fullPath = bm25Result.filePath;
@@ -681,7 +728,9 @@ export class LocalBackend {
681
728
  }
682
729
  // Block write operations (defense-in-depth — DB is already read-only)
683
730
  if (isWriteQuery(params.query)) {
684
- return { error: 'Write operations (CREATE, DELETE, SET, MERGE, REMOVE, DROP, ALTER, COPY, DETACH) are not allowed. The knowledge graph is read-only.' };
731
+ return {
732
+ error: 'Write operations (CREATE, DELETE, SET, MERGE, REMOVE, DROP, ALTER, COPY, DETACH) are not allowed. The knowledge graph is read-only.',
733
+ };
685
734
  }
686
735
  try {
687
736
  const result = await executeQuery(repo.id, params.query);
@@ -706,14 +755,18 @@ export class LocalBackend {
706
755
  return result;
707
756
  const header = '| ' + keys.join(' | ') + ' |';
708
757
  const separator = '| ' + keys.map(() => '---').join(' | ') + ' |';
709
- const dataRows = result.map((row) => '| ' + keys.map(k => {
710
- const v = row[k];
711
- if (v === null || v === undefined)
712
- return '';
713
- if (typeof v === 'object')
714
- return JSON.stringify(v);
715
- return String(v);
716
- }).join(' | ') + ' |');
758
+ const dataRows = result.map((row) => '| ' +
759
+ keys
760
+ .map((k) => {
761
+ const v = row[k];
762
+ if (v === null || v === undefined)
763
+ return '';
764
+ if (typeof v === 'object')
765
+ return JSON.stringify(v);
766
+ return String(v);
767
+ })
768
+ .join(' | ') +
769
+ ' |');
717
770
  return {
718
771
  markdown: [header, separator, ...dataRows].join('\n'),
719
772
  row_count: result.length,
@@ -732,7 +785,12 @@ export class LocalBackend {
732
785
  const cohesion = c.cohesion || 0;
733
786
  const existing = groups.get(label);
734
787
  if (!existing) {
735
- groups.set(label, { ids: [c.id], totalSymbols: symbols, weightedCohesion: cohesion * symbols, largest: c });
788
+ groups.set(label, {
789
+ ids: [c.id],
790
+ totalSymbols: symbols,
791
+ weightedCohesion: cohesion * symbols,
792
+ largest: c,
793
+ });
736
794
  }
737
795
  else {
738
796
  existing.ids.push(c.id);
@@ -752,7 +810,7 @@ export class LocalBackend {
752
810
  cohesion: g.totalSymbols > 0 ? g.weightedCohesion / g.totalSymbols : 0,
753
811
  subCommunities: g.ids.length,
754
812
  }))
755
- .filter(c => c.symbolCount >= 5)
813
+ .filter((c) => c.symbolCount >= 5)
756
814
  .sort((a, b) => b.symbolCount - a.symbolCount);
757
815
  }
758
816
  async overview(repo, params) {
@@ -914,7 +972,7 @@ export class LocalBackend {
914
972
  const sym = symbols[0];
915
973
  const symId = sym.id || sym[0];
916
974
  // Categorized incoming refs
917
- let incomingRows = await executeParameterized(repo.id, `
975
+ const incomingRows = await executeParameterized(repo.id, `
918
976
  MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
919
977
  WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'OVERRIDES', 'ACCESSES']
920
978
  RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
@@ -940,7 +998,9 @@ export class LocalBackend {
940
998
  `, { symId });
941
999
  isClassLike = typeCheck.length > 0;
942
1000
  }
943
- catch { /* not a Class/Interface node */ }
1001
+ catch {
1002
+ /* not a Class/Interface node */
1003
+ }
944
1004
  }
945
1005
  else if (!isClassLike) {
946
1006
  isClassLike = symRawType === 'Class' || symRawType === 'Interface';
@@ -1022,7 +1082,7 @@ export class LocalBackend {
1022
1082
  symbol: {
1023
1083
  uid: sym.id || sym[0],
1024
1084
  name: sym.name || sym[1],
1025
- kind: isClassLike ? (resolvedLabel || 'Class') : (sym.type || sym[2]),
1085
+ kind: isClassLike ? resolvedLabel || 'Class' : sym.type || sym[2],
1026
1086
  filePath: sym.filePath || sym[3],
1027
1087
  startLine: sym.startLine || sym[4],
1028
1088
  endLine: sym.endLine || sym[5],
@@ -1057,8 +1117,11 @@ export class LocalBackend {
1057
1117
  if (clusters.length === 0)
1058
1118
  return { error: `Cluster '${name}' not found` };
1059
1119
  const rawClusters = clusters.map((c) => ({
1060
- id: c.id || c[0], label: c.label || c[1], heuristicLabel: c.heuristicLabel || c[2],
1061
- cohesion: c.cohesion || c[3], symbolCount: c.symbolCount || c[4],
1120
+ id: c.id || c[0],
1121
+ label: c.label || c[1],
1122
+ heuristicLabel: c.heuristicLabel || c[2],
1123
+ cohesion: c.cohesion || c[3],
1124
+ symbolCount: c.symbolCount || c[4],
1062
1125
  }));
1063
1126
  let totalSymbols = 0, weightedCohesion = 0;
1064
1127
  for (const c of rawClusters) {
@@ -1082,7 +1145,9 @@ export class LocalBackend {
1082
1145
  subCommunities: rawClusters.length,
1083
1146
  },
1084
1147
  members: members.map((m) => ({
1085
- name: m.name || m[0], type: m.type || m[1], filePath: m.filePath || m[2],
1148
+ name: m.name || m[0],
1149
+ type: m.type || m[1],
1150
+ filePath: m.filePath || m[2],
1086
1151
  })),
1087
1152
  };
1088
1153
  }
@@ -1104,11 +1169,17 @@ export class LocalBackend {
1104
1169
  `, { procId });
1105
1170
  return {
1106
1171
  process: {
1107
- id: procId, label: proc.label || proc[1], heuristicLabel: proc.heuristicLabel || proc[2],
1108
- processType: proc.processType || proc[3], stepCount: proc.stepCount || proc[4],
1172
+ id: procId,
1173
+ label: proc.label || proc[1],
1174
+ heuristicLabel: proc.heuristicLabel || proc[2],
1175
+ processType: proc.processType || proc[3],
1176
+ stepCount: proc.stepCount || proc[4],
1109
1177
  },
1110
1178
  steps: steps.map((s) => ({
1111
- step: s.step || s[3], name: s.name || s[0], type: s.type || s[1], filePath: s.filePath || s[2],
1179
+ step: s.step || s[3],
1180
+ name: s.name || s[0],
1181
+ type: s.type || s[1],
1182
+ filePath: s.filePath || s[2],
1112
1183
  })),
1113
1184
  };
1114
1185
  }
@@ -1144,14 +1215,22 @@ export class LocalBackend {
1144
1215
  let changedFiles;
1145
1216
  try {
1146
1217
  const output = execFileSync('git', diffArgs, { cwd: repo.repoPath, encoding: 'utf-8' });
1147
- changedFiles = output.trim().split('\n').filter(f => f.length > 0);
1218
+ changedFiles = output
1219
+ .trim()
1220
+ .split('\n')
1221
+ .filter((f) => f.length > 0);
1148
1222
  }
1149
1223
  catch (err) {
1150
1224
  return { error: `Git diff failed: ${err.message}` };
1151
1225
  }
1152
1226
  if (changedFiles.length === 0) {
1153
1227
  return {
1154
- summary: { changed_count: 0, affected_count: 0, risk_level: 'none', message: 'No changes detected.' },
1228
+ summary: {
1229
+ changed_count: 0,
1230
+ affected_count: 0,
1231
+ risk_level: 'none',
1232
+ message: 'No changes detected.',
1233
+ },
1155
1234
  changed_symbols: [],
1156
1235
  affected_processes: [],
1157
1236
  };
@@ -1210,7 +1289,13 @@ export class LocalBackend {
1210
1289
  }
1211
1290
  }
1212
1291
  const processCount = affectedProcesses.size;
1213
- const risk = processCount === 0 ? 'low' : processCount <= 5 ? 'medium' : processCount <= 15 ? 'high' : 'critical';
1292
+ const risk = processCount === 0
1293
+ ? 'low'
1294
+ : processCount <= 5
1295
+ ? 'medium'
1296
+ : processCount <= 15
1297
+ ? 'high'
1298
+ : 'critical';
1214
1299
  return {
1215
1300
  summary: {
1216
1301
  changed_count: changedSymbols.length,
@@ -1298,7 +1383,9 @@ export class LocalBackend {
1298
1383
  const lines = content.split('\n');
1299
1384
  for (let i = 0; i < lines.length; i++) {
1300
1385
  if (lines[i].includes(oldName)) {
1301
- addEdit(ref.filePath, i + 1, lines[i].trim(), lines[i].replace(new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g'), new_name).trim(), 'graph');
1386
+ addEdit(ref.filePath, i + 1, lines[i].trim(), lines[i]
1387
+ .replace(new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g'), new_name)
1388
+ .trim(), 'graph');
1302
1389
  graphEdits++;
1303
1390
  break; // one edit per file from graph refs
1304
1391
  }
@@ -1310,19 +1397,28 @@ export class LocalBackend {
1310
1397
  }
1311
1398
  // Step 3: Text search for refs the graph might have missed
1312
1399
  let astSearchEdits = 0;
1313
- const graphFiles = new Set([sym.filePath, ...allIncoming.map(r => r.filePath)].filter(Boolean));
1400
+ const graphFiles = new Set([sym.filePath, ...allIncoming.map((r) => r.filePath)].filter(Boolean));
1314
1401
  // Simple text search across the repo for the old name (in files not already covered by graph)
1315
1402
  try {
1316
1403
  const { execFileSync } = await import('child_process');
1317
1404
  const rgArgs = [
1318
1405
  '-l',
1319
- '--type-add', 'code:*.{ts,tsx,js,jsx,py,go,rs,java,c,h,cpp,cc,cxx,hpp,hxx,hh,cs,php,swift}',
1320
- '-t', 'code',
1406
+ '--type-add',
1407
+ 'code:*.{ts,tsx,js,jsx,py,go,rs,java,c,h,cpp,cc,cxx,hpp,hxx,hh,cs,php,swift}',
1408
+ '-t',
1409
+ 'code',
1321
1410
  `\\b${oldName}\\b`,
1322
1411
  '.',
1323
1412
  ];
1324
- const output = execFileSync('rg', rgArgs, { cwd: repo.repoPath, encoding: 'utf-8', timeout: 5000 });
1325
- const files = output.trim().split('\n').filter(f => f.length > 0);
1413
+ const output = execFileSync('rg', rgArgs, {
1414
+ cwd: repo.repoPath,
1415
+ encoding: 'utf-8',
1416
+ timeout: 5000,
1417
+ });
1418
+ const files = output
1419
+ .trim()
1420
+ .split('\n')
1421
+ .filter((f) => f.length > 0);
1326
1422
  for (const file of files) {
1327
1423
  const normalizedFile = file.replace(/\\/g, '/').replace(/^\.\//, '');
1328
1424
  if (graphFiles.has(normalizedFile))
@@ -1399,12 +1495,12 @@ export class LocalBackend {
1399
1495
  const { target, direction } = params;
1400
1496
  const maxDepth = params.maxDepth || 3;
1401
1497
  const rawRelTypes = params.relationTypes && params.relationTypes.length > 0
1402
- ? params.relationTypes.filter(t => VALID_RELATION_TYPES.has(t))
1498
+ ? params.relationTypes.filter((t) => VALID_RELATION_TYPES.has(t))
1403
1499
  : ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS'];
1404
1500
  const relationTypes = rawRelTypes.length > 0 ? rawRelTypes : ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS'];
1405
1501
  const includeTests = params.includeTests ?? false;
1406
1502
  const minConfidence = params.minConfidence ?? 0;
1407
- const relTypeFilter = relationTypes.map(t => `'${t}'`).join(', ');
1503
+ const relTypeFilter = relationTypes.map((t) => `'${t}'`).join(', ');
1408
1504
  const confidenceFilter = minConfidence > 0 ? ` AND r.confidence >= ${minConfidence}` : '';
1409
1505
  // Resolve target by name, preferring Class/Interface over Constructor
1410
1506
  // (fix #480: Java class and constructor share the same name).
@@ -1438,7 +1534,9 @@ export class LocalBackend {
1438
1534
  symType = priorityToLabel[best.priority ?? best[3]] ?? '';
1439
1535
  }
1440
1536
  }
1441
- catch { /* fall through to unlabeled match */ }
1537
+ catch {
1538
+ /* fall through to unlabeled match */
1539
+ }
1442
1540
  // Fall back to unlabeled match for any other node type
1443
1541
  if (!sym) {
1444
1542
  const rows = await executeParameterized(repo.id, `
@@ -1503,7 +1601,7 @@ export class LocalBackend {
1503
1601
  for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
1504
1602
  const nextFrontier = [];
1505
1603
  // Batch frontier nodes into a single Cypher query per depth level
1506
- const idList = frontier.map(id => `'${id.replace(/'/g, "''")}'`).join(', ');
1604
+ const idList = frontier.map((id) => `'${id.replace(/'/g, "''")}'`).join(', ');
1507
1605
  const query = direction === 'upstream'
1508
1606
  ? `MATCH (caller)-[r:CodeRelation]->(n) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, caller.id AS id, caller.name AS name, labels(caller)[0] AS type, caller.filePath AS filePath, r.type AS relType, r.confidence AS confidence`
1509
1607
  : `MATCH (n)-[r:CodeRelation]->(callee) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, callee.id AS id, callee.name AS name, labels(callee)[0] AS type, callee.filePath AS filePath, r.type AS relType, r.confidence AS confidence`;
@@ -1572,7 +1670,7 @@ export class LocalBackend {
1572
1670
  let chunksProcessed = 0;
1573
1671
  for (let i = 0; i < impacted.length && chunksProcessed < MAX_CHUNKS; i += CHUNK_SIZE, chunksProcessed++) {
1574
1672
  const chunk = impacted.slice(i, i + CHUNK_SIZE);
1575
- const ids = chunk.map(item => String(item.id ?? ''));
1673
+ const ids = chunk.map((item) => String(item.id ?? ''));
1576
1674
  try {
1577
1675
  // Use parameterized list to avoid building long query strings
1578
1676
  const rows = await executeParameterized(repo.id, `
@@ -1593,14 +1691,16 @@ export class LocalBackend {
1593
1691
  // Normalize epName: prefer epName, fall back to other columns, and
1594
1692
  // ensure we don't keep an empty string (labels(...) can return "").
1595
1693
  const epNameRaw = row.epName ?? row[7] ?? row.name ?? row[1] ?? 'unknown';
1596
- const epName = (typeof epNameRaw === 'string' && epNameRaw.trim().length > 0) ? epNameRaw.trim() : 'unknown';
1694
+ const epName = typeof epNameRaw === 'string' && epNameRaw.trim().length > 0
1695
+ ? epNameRaw.trim()
1696
+ : 'unknown';
1597
1697
  // Normalize epType: labels(ep)[0] can return an empty string in
1598
1698
  // some DBs (LadybugDB). Using nullish coalescing (??) preserves
1599
1699
  // empty strings, which results in empty `type` values being
1600
1700
  // propagated. Treat empty-string labels as missing and fall back
1601
1701
  // to the next candidate or a sensible default.
1602
1702
  const epTypeRaw = row.epType ?? row[8] ?? '';
1603
- const epType = (typeof epTypeRaw === 'string' && epTypeRaw.trim().length > 0)
1703
+ const epType = typeof epTypeRaw === 'string' && epTypeRaw.trim().length > 0
1604
1704
  ? epTypeRaw.trim()
1605
1705
  : 'Function';
1606
1706
  const epFilePath = row.epFilePath ?? row[9] ?? '';
@@ -1638,7 +1738,7 @@ export class LocalBackend {
1638
1738
  if (processesMissingMinStep.size > 0) {
1639
1739
  try {
1640
1740
  const pIds = Array.from(processesMissingMinStep);
1641
- const allImpactedIds = impacted.map(it => String(it.id ?? ''));
1741
+ const allImpactedIds = impacted.map((it) => String(it.id ?? ''));
1642
1742
  const missingRows = await executeParameterized(repo.id, `
1643
1743
  MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
1644
1744
  WHERE p.id IN $pIds AND s.id IN $ids
@@ -1667,7 +1767,7 @@ export class LocalBackend {
1667
1767
  traversalComplete = false;
1668
1768
  }
1669
1769
  affectedProcesses = Array.from(entryPointMap.values())
1670
- .map(ep => ({
1770
+ .map((ep) => ({
1671
1771
  ...ep,
1672
1772
  earliest_broken_step: ep.earliest_broken_step === Infinity ? null : ep.earliest_broken_step,
1673
1773
  }))
@@ -1741,7 +1841,7 @@ export class LocalBackend {
1741
1841
  .map(([name, hits]) => ({ name, hits }))
1742
1842
  .sort((a, b) => b.hits - a.hits)
1743
1843
  .slice(0, 20);
1744
- const directModuleRows = Array.from(directModuleSet).map(name => ({ name }));
1844
+ const directModuleRows = Array.from(directModuleSet).map((name) => ({ name }));
1745
1845
  // Build affectedModules in the same shape as original implementation
1746
1846
  const directModuleNameSet = new Set(directModuleRows.map((r) => r.name || r[0]));
1747
1847
  affectedModules = moduleRows.map((r) => {
@@ -1761,7 +1861,10 @@ export class LocalBackend {
1761
1861
  if (directCount >= 30 || processCount >= 5 || moduleCount >= 5 || impacted.length >= 200) {
1762
1862
  risk = 'CRITICAL';
1763
1863
  }
1764
- else if (directCount >= 15 || processCount >= 3 || moduleCount >= 3 || impacted.length >= 100) {
1864
+ else if (directCount >= 15 ||
1865
+ processCount >= 3 ||
1866
+ moduleCount >= 3 ||
1867
+ impacted.length >= 100) {
1765
1868
  risk = 'HIGH';
1766
1869
  }
1767
1870
  else if (directCount >= 5 || impacted.length >= 30) {
@@ -1805,7 +1908,7 @@ export class LocalBackend {
1805
1908
  `, params);
1806
1909
  // Strip wrapping quotes from DB array elements — CSV COPY stores ['key'] which
1807
1910
  // LadybugDB may return as "'key'" rather than "key"
1808
- const stripQuotes = (keys) => keys ? keys.map(k => k.replace(/^['"]|['"]$/g, '')) : null;
1911
+ const stripQuotes = (keys) => keys ? keys.map((k) => k.replace(/^['"]|['"]$/g, '')) : null;
1809
1912
  const routeMap = new Map();
1810
1913
  for (const row of rows) {
1811
1914
  const id = row.routeId ?? row[0];
@@ -1818,7 +1921,15 @@ export class LocalBackend {
1818
1921
  const consumerFile = row.consumerFile ?? row[7];
1819
1922
  const fetchReason = row.fetchReason ?? row[8] ?? null;
1820
1923
  if (!routeMap.has(id)) {
1821
- routeMap.set(id, { id, name, filePath, responseKeys, errorKeys, middleware, consumers: [] });
1924
+ routeMap.set(id, {
1925
+ id,
1926
+ name,
1927
+ filePath,
1928
+ responseKeys,
1929
+ errorKeys,
1930
+ middleware,
1931
+ consumers: [],
1932
+ });
1822
1933
  }
1823
1934
  if (consumerName && consumerFile) {
1824
1935
  // Parse accessed keys from reason field: "fetch-url-match|keys:data,pagination|fetches:3"
@@ -1827,7 +1938,7 @@ export class LocalBackend {
1827
1938
  if (fetchReason) {
1828
1939
  const keysMatch = fetchReason.match(/\|keys:([^|]+)/);
1829
1940
  if (keysMatch) {
1830
- accessedKeys = keysMatch[1].split(',').filter(k => k.length > 0);
1941
+ accessedKeys = keysMatch[1].split(',').filter((k) => k.length > 0);
1831
1942
  }
1832
1943
  const fetchesMatch = fetchReason.match(/\|fetches:(\d+)/);
1833
1944
  if (fetchesMatch) {
@@ -1873,7 +1984,9 @@ export class LocalBackend {
1873
1984
  list.push(name);
1874
1985
  }
1875
1986
  }
1876
- catch { /* no ENTRY_POINT_OF edges yet */ }
1987
+ catch {
1988
+ /* no ENTRY_POINT_OF edges yet */
1989
+ }
1877
1990
  return result;
1878
1991
  }
1879
1992
  async routeMap(repo, params) {
@@ -1882,12 +1995,19 @@ export class LocalBackend {
1882
1995
  const queryParams = params.route ? { route: params.route } : {};
1883
1996
  const routes = await this.fetchRoutesWithConsumers(repo.id, routeFilter, queryParams);
1884
1997
  if (routes.length === 0) {
1885
- return { routes: [], total: 0, message: params.route ? `No routes matching "${params.route}"` : 'No routes found in this project.' };
1998
+ return {
1999
+ routes: [],
2000
+ total: 0,
2001
+ message: params.route
2002
+ ? `No routes matching "${params.route}"`
2003
+ : 'No routes found in this project.',
2004
+ };
1886
2005
  }
1887
- const flowMap = await this.fetchLinkedFlowsBatch(repo.id, routes.map(r => r.id));
2006
+ const flowMap = await this.fetchLinkedFlowsBatch(repo.id, routes.map((r) => r.id));
1888
2007
  return {
1889
- routes: routes.map(r => ({
1890
- route: r.name, handler: r.filePath,
2008
+ routes: routes.map((r) => ({
2009
+ route: r.name,
2010
+ handler: r.filePath,
1891
2011
  middleware: r.middleware || [],
1892
2012
  consumers: r.consumers,
1893
2013
  flows: flowMap.get(r.id) || [],
@@ -1901,8 +2021,10 @@ export class LocalBackend {
1901
2021
  const queryParams = params.route ? { route: params.route } : {};
1902
2022
  const allRoutes = await this.fetchRoutesWithConsumers(repo.id, routeFilter, queryParams);
1903
2023
  const results = allRoutes
1904
- .filter(r => ((r.responseKeys && r.responseKeys.length > 0) || (r.errorKeys && r.errorKeys.length > 0)) && r.consumers.length > 0)
1905
- .map(r => {
2024
+ .filter((r) => ((r.responseKeys && r.responseKeys.length > 0) ||
2025
+ (r.errorKeys && r.errorKeys.length > 0)) &&
2026
+ r.consumers.length > 0)
2027
+ .map((r) => {
1906
2028
  // Keys already normalized by fetchRoutesWithConsumers (quotes stripped)
1907
2029
  const responseKeys = r.responseKeys ?? [];
1908
2030
  const errorKeys = r.errorKeys ?? [];
@@ -1910,24 +2032,33 @@ export class LocalBackend {
1910
2032
  const allKnownKeys = new Set([...responseKeys, ...errorKeys]);
1911
2033
  // Check each consumer's accessed keys against the route's response shape
1912
2034
  const responseKeySet = new Set(responseKeys);
1913
- const consumers = r.consumers.map(c => {
2035
+ const consumers = r.consumers.map((c) => {
1914
2036
  if (!c.accessedKeys || c.accessedKeys.length === 0) {
1915
2037
  return { name: c.name, filePath: c.filePath };
1916
2038
  }
1917
- const mismatched = c.accessedKeys.filter(k => !allKnownKeys.has(k));
2039
+ const mismatched = c.accessedKeys.filter((k) => !allKnownKeys.has(k));
1918
2040
  // Keys in allKnownKeys but not in responseKeys — error-path access (e.g., .error from errorKeys)
1919
- const errorPathKeys = c.accessedKeys.filter(k => allKnownKeys.has(k) && !responseKeySet.has(k));
2041
+ const errorPathKeys = c.accessedKeys.filter((k) => allKnownKeys.has(k) && !responseKeySet.has(k));
1920
2042
  const isMultiFetch = (c.fetchCount ?? 1) > 1;
1921
2043
  return {
1922
2044
  name: c.name,
1923
2045
  filePath: c.filePath,
1924
2046
  accessedKeys: c.accessedKeys,
1925
- ...(mismatched.length > 0 ? { mismatched, mismatchConfidence: isMultiFetch ? 'low' : 'high' } : {}),
2047
+ ...(mismatched.length > 0
2048
+ ? {
2049
+ mismatched,
2050
+ mismatchConfidence: isMultiFetch ? 'low' : 'high',
2051
+ }
2052
+ : {}),
1926
2053
  ...(errorPathKeys.length > 0 ? { errorPathKeys } : {}),
1927
- ...(isMultiFetch ? { attributionNote: `This file fetches ${c.fetchCount} routes — accessed keys may belong to a different route.` } : {}),
2054
+ ...(isMultiFetch
2055
+ ? {
2056
+ attributionNote: `This file fetches ${c.fetchCount} routes — accessed keys may belong to a different route.`,
2057
+ }
2058
+ : {}),
1928
2059
  };
1929
2060
  });
1930
- const hasMismatches = consumers.some(c => 'mismatched' in c && c.mismatched.length > 0);
2061
+ const hasMismatches = consumers.some((c) => 'mismatched' in c && c.mismatched.length > 0);
1931
2062
  return {
1932
2063
  route: r.name,
1933
2064
  handler: r.filePath,
@@ -1937,7 +2068,7 @@ export class LocalBackend {
1937
2068
  ...(hasMismatches ? { status: 'MISMATCH' } : {}),
1938
2069
  };
1939
2070
  });
1940
- const mismatchCount = results.filter(r => r.status === 'MISMATCH').length;
2071
+ const mismatchCount = results.filter((r) => r.status === 'MISMATCH').length;
1941
2072
  return {
1942
2073
  routes: results,
1943
2074
  total: results.length,
@@ -1960,7 +2091,11 @@ export class LocalBackend {
1960
2091
  RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.description AS description
1961
2092
  `, queryParams);
1962
2093
  if (rows.length === 0) {
1963
- return { tools: [], total: 0, message: params.tool ? `No tools matching "${params.tool}"` : 'No tool definitions found.' };
2094
+ return {
2095
+ tools: [],
2096
+ total: 0,
2097
+ message: params.tool ? `No tools matching "${params.tool}"` : 'No tool definitions found.',
2098
+ };
1964
2099
  }
1965
2100
  const toolIds = rows.map((r) => r.id ?? r[0]);
1966
2101
  const flowMap = await this.fetchLinkedFlowsBatch(repo.id, toolIds);
@@ -1998,7 +2133,7 @@ export class LocalBackend {
1998
2133
  const target = params.route || params.file;
1999
2134
  return { error: `No routes found matching "${target}".` };
2000
2135
  }
2001
- const flowMap = await this.fetchLinkedFlowsBatch(repo.id, routes.map(r => r.id));
2136
+ const flowMap = await this.fetchLinkedFlowsBatch(repo.id, routes.map((r) => r.id));
2002
2137
  // Count how many routes share the same handler file (for middleware partial detection)
2003
2138
  const routeCountByHandler = new Map();
2004
2139
  for (const r of routes) {
@@ -2006,17 +2141,21 @@ export class LocalBackend {
2006
2141
  routeCountByHandler.set(r.filePath, (routeCountByHandler.get(r.filePath) ?? 0) + 1);
2007
2142
  }
2008
2143
  }
2009
- const results = routes.map(r => {
2144
+ const results = routes.map((r) => {
2010
2145
  // Keys already normalized by fetchRoutesWithConsumers (quotes stripped)
2011
2146
  const responseKeys = r.responseKeys ?? [];
2012
2147
  const errorKeys = r.errorKeys ?? [];
2013
2148
  const allKnownKeys = new Set([...responseKeys, ...errorKeys]);
2014
2149
  // Build consumer list with mismatch detection
2015
- const consumers = r.consumers.map(c => ({
2150
+ const consumers = r.consumers.map((c) => ({
2016
2151
  name: c.name,
2017
2152
  file: c.filePath,
2018
2153
  accesses: c.accessedKeys ?? [],
2019
- ...(c.fetchCount && c.fetchCount > 1 ? { attributionNote: `This file fetches ${c.fetchCount} routes — accessed keys may belong to a different route.` } : {}),
2154
+ ...(c.fetchCount && c.fetchCount > 1
2155
+ ? {
2156
+ attributionNote: `This file fetches ${c.fetchCount} routes — accessed keys may belong to a different route.`,
2157
+ }
2158
+ : {}),
2020
2159
  }));
2021
2160
  // Detect mismatches: consumer accesses keys not in response shape
2022
2161
  const mismatches = [];
@@ -2073,10 +2212,12 @@ export class LocalBackend {
2073
2212
  error: errorKeys,
2074
2213
  },
2075
2214
  middleware: middlewareArr,
2076
- ...(middlewarePartial ? {
2077
- middlewareDetection: 'partial',
2078
- middlewareNote: 'Middleware captured from first HTTP method export only — other methods in this handler may use different middleware chains.',
2079
- } : {}),
2215
+ ...(middlewarePartial
2216
+ ? {
2217
+ middlewareDetection: 'partial',
2218
+ middlewareNote: 'Middleware captured from first HTTP method export only — other methods in this handler may use different middleware chains.',
2219
+ }
2220
+ : {}),
2080
2221
  consumers,
2081
2222
  ...(mismatches.length > 0 ? { mismatches } : {}),
2082
2223
  executionFlows: flows,
@@ -2166,8 +2307,11 @@ export class LocalBackend {
2166
2307
  if (clusters.length === 0)
2167
2308
  return { error: `Cluster '${name}' not found` };
2168
2309
  const rawClusters = clusters.map((c) => ({
2169
- id: c.id || c[0], label: c.label || c[1], heuristicLabel: c.heuristicLabel || c[2],
2170
- cohesion: c.cohesion || c[3], symbolCount: c.symbolCount || c[4],
2310
+ id: c.id || c[0],
2311
+ label: c.label || c[1],
2312
+ heuristicLabel: c.heuristicLabel || c[2],
2313
+ cohesion: c.cohesion || c[3],
2314
+ symbolCount: c.symbolCount || c[4],
2171
2315
  }));
2172
2316
  let totalSymbols = 0, weightedCohesion = 0;
2173
2317
  for (const c of rawClusters) {
@@ -2191,7 +2335,9 @@ export class LocalBackend {
2191
2335
  subCommunities: rawClusters.length,
2192
2336
  },
2193
2337
  members: members.map((m) => ({
2194
- name: m.name || m[0], type: m.type || m[1], filePath: m.filePath || m[2],
2338
+ name: m.name || m[0],
2339
+ type: m.type || m[1],
2340
+ filePath: m.filePath || m[2],
2195
2341
  })),
2196
2342
  };
2197
2343
  }
@@ -2219,11 +2365,17 @@ export class LocalBackend {
2219
2365
  `, { procId });
2220
2366
  return {
2221
2367
  process: {
2222
- id: procId, label: proc.label || proc[1], heuristicLabel: proc.heuristicLabel || proc[2],
2223
- processType: proc.processType || proc[3], stepCount: proc.stepCount || proc[4],
2368
+ id: procId,
2369
+ label: proc.label || proc[1],
2370
+ heuristicLabel: proc.heuristicLabel || proc[2],
2371
+ processType: proc.processType || proc[3],
2372
+ stepCount: proc.stepCount || proc[4],
2224
2373
  },
2225
2374
  steps: steps.map((s) => ({
2226
- step: s.step || s[3], name: s.name || s[0], type: s.type || s[1], filePath: s.filePath || s[2],
2375
+ step: s.step || s[3],
2376
+ name: s.name || s[0],
2377
+ type: s.type || s[1],
2378
+ filePath: s.filePath || s[2],
2227
2379
  })),
2228
2380
  };
2229
2381
  }