@codragraph/cli 2.1.0 → 2.1.4

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 (156) hide show
  1. package/README.md +62 -21
  2. package/dist/_shared/cgdb/schema-constants.d.ts +2 -2
  3. package/dist/_shared/cgdb/schema-constants.d.ts.map +1 -1
  4. package/dist/_shared/cgdb/schema-constants.js +3 -0
  5. package/dist/_shared/cgdb/schema-constants.js.map +1 -1
  6. package/dist/_shared/feature-clusters.d.ts +99 -0
  7. package/dist/_shared/feature-clusters.d.ts.map +1 -0
  8. package/dist/_shared/feature-clusters.js +2 -0
  9. package/dist/_shared/feature-clusters.js.map +1 -0
  10. package/dist/_shared/graph/types.d.ts +16 -2
  11. package/dist/_shared/graph/types.d.ts.map +1 -1
  12. package/dist/_shared/index.d.ts +1 -0
  13. package/dist/_shared/index.d.ts.map +1 -1
  14. package/dist/_shared/index.js.map +1 -1
  15. package/dist/_shared/pipeline.d.ts +1 -1
  16. package/dist/_shared/pipeline.d.ts.map +1 -1
  17. package/dist/cli/ai-context.js +4 -0
  18. package/dist/cli/analyze.js +46 -26
  19. package/dist/cli/index.js +39 -1
  20. package/dist/cli/serve.d.ts +1 -0
  21. package/dist/cli/serve.js +3 -1
  22. package/dist/cli/setup.js +42 -21
  23. package/dist/cli/status.d.ts +13 -0
  24. package/dist/cli/status.js +99 -0
  25. package/dist/cli/tool.d.ts +25 -0
  26. package/dist/cli/tool.js +74 -0
  27. package/dist/config/ignore-service.js +2 -0
  28. package/dist/config/supported-languages.d.ts +3 -3
  29. package/dist/config/supported-languages.js +3 -3
  30. package/dist/core/cgdb/cgdb-adapter.js +19 -3
  31. package/dist/core/cgdb/csv-generator.js +33 -2
  32. package/dist/core/cgdb/schema.d.ts +2 -1
  33. package/dist/core/cgdb/schema.js +55 -0
  34. package/dist/core/embeddings/embedder.js +4 -2
  35. package/dist/core/graphstore/cgdb-row-source.js +3 -2
  36. package/dist/core/graphstore/index.d.ts +1 -1
  37. package/dist/core/graphstore/index.js +1 -1
  38. package/dist/core/group/bridge-db.js +42 -10
  39. package/dist/core/group/service.d.ts +16 -0
  40. package/dist/core/group/service.js +360 -0
  41. package/dist/core/ingestion/emit-references.d.ts +1 -1
  42. package/dist/core/ingestion/emit-references.js +1 -1
  43. package/dist/core/ingestion/feature-cluster-processor.d.ts +62 -0
  44. package/dist/core/ingestion/feature-cluster-processor.js +626 -0
  45. package/dist/core/ingestion/finalize-orchestrator.js +1 -1
  46. package/dist/core/ingestion/model/registration-table.js +1 -0
  47. package/dist/core/ingestion/model/resolve.d.ts +2 -2
  48. package/dist/core/ingestion/model/resolve.js +3 -3
  49. package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
  50. package/dist/core/ingestion/model/semantic-model.js +1 -1
  51. package/dist/core/ingestion/model/symbol-table.d.ts +1 -1
  52. package/dist/core/ingestion/model/symbol-table.js +1 -1
  53. package/dist/core/ingestion/pipeline-phases/feature-clusters.d.ts +17 -0
  54. package/dist/core/ingestion/pipeline-phases/feature-clusters.js +88 -0
  55. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  56. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  57. package/dist/core/ingestion/pipeline.d.ts +4 -0
  58. package/dist/core/ingestion/pipeline.js +9 -5
  59. package/dist/core/run-analyze.d.ts +21 -0
  60. package/dist/core/run-analyze.js +213 -6
  61. package/dist/core/search/hybrid-search.js +11 -3
  62. package/dist/mcp/core/embedder.js +5 -2
  63. package/dist/mcp/local/local-backend.d.ts +12 -0
  64. package/dist/mcp/local/local-backend.js +381 -3
  65. package/dist/mcp/resources.js +139 -0
  66. package/dist/mcp/tools.js +174 -2
  67. package/dist/server/api.d.ts +14 -2
  68. package/dist/server/api.js +206 -7
  69. package/dist/server/mcp-http.d.ts +22 -0
  70. package/dist/server/mcp-http.js +21 -2
  71. package/dist/server/web-dashboard.d.ts +28 -0
  72. package/dist/server/web-dashboard.js +61 -0
  73. package/dist/storage/repo-manager.d.ts +6 -1
  74. package/dist/storage/repo-manager.js +5 -1
  75. package/dist/types/pipeline.d.ts +2 -0
  76. package/dist/web/assets/agent-D5lb0zXz.js +1089 -0
  77. package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +36 -0
  78. package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +132 -0
  79. package/dist/web/assets/c4Diagram-DFAF54RM-C4Hl3J2U.js +10 -0
  80. package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +231 -0
  81. package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +1 -0
  82. package/dist/web/assets/chunk-7RZVMHOQ-BitYcNVR.js +338 -0
  83. package/dist/web/assets/chunk-AEOMTBSW-BgTIXPsY.js +1 -0
  84. package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +13 -0
  85. package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +1 -0
  86. package/dist/web/assets/chunk-KSICW3F5-BYzvDLNI.js +15 -0
  87. package/dist/web/assets/chunk-O5ABG6QK-dHwHzA6n.js +1 -0
  88. package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +206 -0
  89. package/dist/web/assets/chunk-RWUO3TPN-BgRTY0_k.js +1 -0
  90. package/dist/web/assets/chunk-TBF5ZNIQ-DL5stGM1.js +1 -0
  91. package/dist/web/assets/chunk-TU3PZOEN-RLyvLcv-.js +1 -0
  92. package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +1 -0
  93. package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +1 -0
  94. package/dist/web/assets/context-builder-22jU3V56.js +16 -0
  95. package/dist/web/assets/cose-bilkent-PNC4W37J-DVhePRYg.js +1 -0
  96. package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +4 -0
  97. package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +43 -0
  98. package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +24 -0
  99. package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +10 -0
  100. package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +24 -0
  101. package/dist/web/assets/erDiagram-GCSMX5X6-C3dhDFA8.js +85 -0
  102. package/dist/web/assets/flowDiagram-OTCZ4VVT-CWSFWmhr.js +162 -0
  103. package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +292 -0
  104. package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +106 -0
  105. package/dist/web/assets/index-BgeqpYgd.js +1415 -0
  106. package/dist/web/assets/index-CT0GtFLZ.css +1 -0
  107. package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +2 -0
  108. package/dist/web/assets/ishikawaDiagram-YMYX4NHK-DUoJvNP2.js +70 -0
  109. package/dist/web/assets/journeyDiagram-SO5T7YLQ-RMFPNNqz.js +139 -0
  110. package/dist/web/assets/kanban-definition-LJHFXRCJ-BzpDs1K9.js +89 -0
  111. package/dist/web/assets/katex-GD7MH7QM-DBQvrix-.js +261 -0
  112. package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +96 -0
  113. package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +30 -0
  114. package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +7 -0
  115. package/dist/web/assets/requirementDiagram-M5DCFWZL-DLHOVTSv.js +84 -0
  116. package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +10 -0
  117. package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +157 -0
  118. package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +1 -0
  119. package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +1 -0
  120. package/dist/web/assets/timeline-definition-5SPVSISX-TRSDRgPw.js +120 -0
  121. package/dist/web/assets/vennDiagram-IE5QUKF5-DNy7HRBM.js +34 -0
  122. package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +161 -0
  123. package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +20 -0
  124. package/dist/web/assets/xychartDiagram-ZHJ5623Y-Dr9r7a35.js +7 -0
  125. package/dist/web/codragraph-logo-512.png +0 -0
  126. package/dist/web/codragraph-logo.png +0 -0
  127. package/dist/web/favicon.png +0 -0
  128. package/dist/web/index.html +36 -0
  129. package/hooks/claude/codragraph-hook.cjs +24 -9
  130. package/hooks/claude/pre-tool-use.sh +6 -1
  131. package/package.json +15 -4
  132. package/scripts/build.js +75 -16
  133. package/scripts/patch-tree-sitter-swift.cjs +0 -1
  134. package/skills/codragraph-cli.md +17 -1
  135. package/skills/codragraph-guide.md +6 -2
  136. package/skills/codragraph-onboarding.md +2 -2
  137. package/vendor/leiden/index.cjs +272 -285
  138. package/vendor/leiden/utils.cjs +264 -274
  139. package/dist/_shared/lbug/schema-constants.d.ts +0 -16
  140. package/dist/_shared/lbug/schema-constants.d.ts.map +0 -1
  141. package/dist/_shared/lbug/schema-constants.js +0 -67
  142. package/dist/_shared/lbug/schema-constants.js.map +0 -1
  143. package/dist/core/graphstore/lbug-row-source.d.ts +0 -19
  144. package/dist/core/graphstore/lbug-row-source.js +0 -141
  145. package/dist/core/lbug/content-read.d.ts +0 -46
  146. package/dist/core/lbug/content-read.js +0 -64
  147. package/dist/core/lbug/csv-generator.d.ts +0 -29
  148. package/dist/core/lbug/csv-generator.js +0 -492
  149. package/dist/core/lbug/lbug-adapter.d.ts +0 -176
  150. package/dist/core/lbug/lbug-adapter.js +0 -1320
  151. package/dist/core/lbug/pool-adapter.d.ts +0 -93
  152. package/dist/core/lbug/pool-adapter.js +0 -550
  153. package/dist/core/lbug/schema.d.ts +0 -62
  154. package/dist/core/lbug/schema.js +0 -502
  155. package/dist/mcp/core/lbug-adapter.d.ts +0 -5
  156. package/dist/mcp/core/lbug-adapter.js +0 -5
@@ -36,6 +36,116 @@ function filterQueryByServicePrefix(queryResult, servicePrefix) {
36
36
  const processes = (queryResult.processes || []).filter((p) => allowed.has(String(p.id)));
37
37
  return { processes, process_symbols: symbols };
38
38
  }
39
+ function normalizeClusterToken(value) {
40
+ return String(value ?? '')
41
+ .trim()
42
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
43
+ .toLowerCase()
44
+ .replace(/[^a-z0-9]+/g, '-')
45
+ .replace(/^-+|-+$/g, '');
46
+ }
47
+ function featureClusterKey(cluster) {
48
+ return normalizeClusterToken(cluster.slug || cluster.name || cluster.id || 'unknown');
49
+ }
50
+ function normalizeStringList(value) {
51
+ if (Array.isArray(value)) {
52
+ return value
53
+ .filter((item) => item !== null && item !== undefined)
54
+ .map((item) => String(item).trim())
55
+ .filter(Boolean);
56
+ }
57
+ if (typeof value !== 'string' || value.trim() === '')
58
+ return [];
59
+ return value
60
+ .replace(/^\[|\]$/g, '')
61
+ .split(/,(?=(?:[^']*'[^']*')*[^']*$)/)
62
+ .map((item) => item
63
+ .trim()
64
+ .replace(/^['"]|['"]$/g, '')
65
+ .replace(/\\,/g, ','))
66
+ .filter(Boolean);
67
+ }
68
+ function clusterOwnsEndpoint(cluster, endpoint) {
69
+ const entryPointIds = normalizeStringList(cluster.entryPointIds);
70
+ if (entryPointIds.includes(endpoint.symbolUid))
71
+ return true;
72
+ const key = featureClusterKey(cluster);
73
+ if (!key || key === 'unknown')
74
+ return false;
75
+ const filePath = normalizeClusterToken(endpoint.symbolRef.filePath);
76
+ const symbolName = normalizeClusterToken(endpoint.symbolRef.name);
77
+ return filePath.includes(key) || symbolName.includes(key);
78
+ }
79
+ function resolveEndpointCluster(endpoint, clusters) {
80
+ const sameRepo = clusters.filter((cluster) => cluster.repoPath === endpoint.repo);
81
+ return sameRepo.find((cluster) => clusterOwnsEndpoint(cluster, endpoint));
82
+ }
83
+ function buildCrossRepoClusterLinks(registry, clusters) {
84
+ if (!registry)
85
+ return [];
86
+ const links = [];
87
+ for (const link of registry.crossLinks) {
88
+ const sourceCluster = resolveEndpointCluster(link.from, clusters);
89
+ const targetCluster = resolveEndpointCluster(link.to, clusters);
90
+ if (!sourceCluster || !targetCluster)
91
+ continue;
92
+ links.push({
93
+ sourceRepo: link.from.repo,
94
+ sourceService: link.from.service,
95
+ sourceClusterId: sourceCluster.id,
96
+ sourceClusterName: String(sourceCluster.name || sourceCluster.slug || sourceCluster.id || ''),
97
+ targetRepo: link.to.repo,
98
+ targetService: link.to.service,
99
+ targetClusterId: targetCluster.id,
100
+ targetClusterName: String(targetCluster.name || targetCluster.slug || targetCluster.id || ''),
101
+ contractName: link.contractId,
102
+ relationship: 'shared-contract',
103
+ confidence: link.confidence,
104
+ evidence: [
105
+ `${link.type}:${link.contractId}`,
106
+ `${link.from.symbolRef.filePath} -> ${link.to.symbolRef.filePath}`,
107
+ `match:${link.matchType}`,
108
+ ],
109
+ });
110
+ }
111
+ return links;
112
+ }
113
+ function aggregateCrossRepoFeatureClusters(clusters, links) {
114
+ const byKey = new Map();
115
+ for (const cluster of clusters) {
116
+ const key = featureClusterKey(cluster);
117
+ const list = byKey.get(key) ?? [];
118
+ list.push(cluster);
119
+ byKey.set(key, list);
120
+ }
121
+ return [...byKey.entries()]
122
+ .map(([key, group]) => {
123
+ const clusterIds = new Set(group.map((cluster) => String(cluster.id || '')).filter(Boolean));
124
+ const crossRepoLinks = links.filter((link) => (link.sourceClusterId && clusterIds.has(link.sourceClusterId)) ||
125
+ (link.targetClusterId && clusterIds.has(link.targetClusterId)));
126
+ const routes = new Set();
127
+ const tools = new Set();
128
+ for (const cluster of group) {
129
+ normalizeStringList(cluster.routes).forEach((route) => routes.add(route));
130
+ normalizeStringList(cluster.tools).forEach((tool) => tools.add(tool));
131
+ }
132
+ return {
133
+ key,
134
+ name: String(group[0]?.name || group[0]?.slug || key),
135
+ repos: group.map((cluster) => ({
136
+ repoPath: cluster.repoPath,
137
+ registryName: cluster.registryName,
138
+ cluster,
139
+ })),
140
+ repoCount: new Set(group.map((cluster) => cluster.repoPath)).size,
141
+ memberCount: group.reduce((sum, cluster) => sum + Number(cluster.memberCount ?? 0), 0),
142
+ routes: [...routes].sort(),
143
+ tools: [...tools].sort(),
144
+ crossRepoLinks,
145
+ };
146
+ })
147
+ .sort((a, b) => Number(b.memberCount) - Number(a.memberCount));
148
+ }
39
149
  function isCrossLink(raw) {
40
150
  if (!raw || typeof raw !== 'object')
41
151
  return false;
@@ -346,6 +456,256 @@ export class GroupService {
346
456
  per_repo: perRepo.map((r) => ({ repo: r.repo, count: r.processes.length })),
347
457
  };
348
458
  }
459
+ async groupFeatureClusters(params) {
460
+ const name = String(params.name ?? '').trim();
461
+ if (!name)
462
+ return { error: 'name is required' };
463
+ const featureClusters = this.port.featureClusters;
464
+ if (!featureClusters)
465
+ return { error: 'feature cluster query is unavailable' };
466
+ const query = typeof params.query === 'string' ? params.query.trim() : '';
467
+ const limit = typeof params.limit === 'number' && params.limit > 0 ? params.limit : 100;
468
+ const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
469
+ const subgroupExact = params.subgroupExact === true;
470
+ const groupDir = getGroupDir(getDefaultCodragraphDir(), name);
471
+ let config;
472
+ try {
473
+ config = await loadGroupConfig(groupDir);
474
+ }
475
+ catch (err) {
476
+ if (err instanceof GroupNotFoundError)
477
+ return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
478
+ throw err;
479
+ }
480
+ const memberEntries = Object.entries(config.repos).filter(([repoPath]) => repoInSubgroup(repoPath, subgroup, subgroupExact));
481
+ const registryResult = await loadContractRegistryResilient(groupDir);
482
+ const registry = registryResult.ok ? registryResult.registry : null;
483
+ const perRepo = await Promise.all(memberEntries.map(async ([repoPath, registryName]) => {
484
+ try {
485
+ const repoObj = await this.port.resolveRepo(registryName);
486
+ const payload = (await featureClusters(repoObj, {
487
+ query,
488
+ limit,
489
+ }));
490
+ const clusters = (payload.clusters ?? []).map((cluster) => ({
491
+ ...cluster,
492
+ repoPath,
493
+ registryName,
494
+ }));
495
+ return { repo: repoPath, registryName, clusters };
496
+ }
497
+ catch (e) {
498
+ return {
499
+ repo: repoPath,
500
+ registryName,
501
+ clusters: [],
502
+ error: e instanceof Error ? e.message : String(e),
503
+ };
504
+ }
505
+ }));
506
+ const clusters = perRepo
507
+ .flatMap((entry) => entry.clusters)
508
+ .sort((a, b) => Number(b.memberCount ?? 0) - Number(a.memberCount ?? 0))
509
+ .slice(0, limit);
510
+ const allClusters = perRepo.flatMap((entry) => entry.clusters);
511
+ const crossRepoLinks = buildCrossRepoClusterLinks(registry, allClusters);
512
+ const crossRepoClusters = aggregateCrossRepoFeatureClusters(allClusters, crossRepoLinks);
513
+ return {
514
+ group: name,
515
+ query,
516
+ clusters,
517
+ cross_repo_clusters: crossRepoClusters,
518
+ cross_repo_links: crossRepoLinks,
519
+ ...(registryResult.ok === true && registryResult.skippedCorrupt > 0
520
+ ? { skippedCorruptContracts: registryResult.skippedCorrupt }
521
+ : {}),
522
+ per_repo: perRepo.map((entry) => ({
523
+ repo: entry.repo,
524
+ registryName: entry.registryName,
525
+ count: entry.clusters.length,
526
+ ...(entry.error ? { error: entry.error } : {}),
527
+ })),
528
+ };
529
+ }
530
+ async groupFeatureContext(params) {
531
+ const name = String(params.name ?? '').trim();
532
+ const clusterName = String(params.cluster ?? params.target ?? params.feature ?? '').trim();
533
+ if (!name || !clusterName)
534
+ return { error: 'name and cluster are required' };
535
+ const featureContext = this.port.featureContext;
536
+ if (!featureContext)
537
+ return { error: 'feature cluster context is unavailable' };
538
+ const limit = typeof params.limit === 'number' && params.limit > 0 ? params.limit : 100;
539
+ const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
540
+ const subgroupExact = params.subgroupExact === true;
541
+ const groupDir = getGroupDir(getDefaultCodragraphDir(), name);
542
+ let config;
543
+ try {
544
+ config = await loadGroupConfig(groupDir);
545
+ }
546
+ catch (err) {
547
+ if (err instanceof GroupNotFoundError)
548
+ return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
549
+ throw err;
550
+ }
551
+ const memberEntries = Object.entries(config.repos).filter(([repoPath]) => repoInSubgroup(repoPath, subgroup, subgroupExact));
552
+ const registryResult = await loadContractRegistryResilient(groupDir);
553
+ const registry = registryResult.ok ? registryResult.registry : null;
554
+ const results = await Promise.all(memberEntries.map(async ([repoPath, registryName]) => {
555
+ try {
556
+ const repoObj = await this.port.resolveRepo(registryName);
557
+ const payload = await featureContext(repoObj, {
558
+ name: clusterName,
559
+ limit,
560
+ });
561
+ return { repoPath, registryName, payload };
562
+ }
563
+ catch (e) {
564
+ return {
565
+ repoPath,
566
+ registryName,
567
+ payload: { error: e instanceof Error ? e.message : String(e) },
568
+ };
569
+ }
570
+ }));
571
+ const contexts = results.filter((result) => !result.payload?.error);
572
+ const memberIdsByRepo = new Map();
573
+ for (const result of contexts) {
574
+ const payload = result.payload;
575
+ memberIdsByRepo.set(result.repoPath, new Set((payload.members ?? []).map((member) => String(member.id || '')).filter(Boolean)));
576
+ }
577
+ const crossRepoLinks = (registry?.crossLinks ?? [])
578
+ .filter((link) => {
579
+ const fromIds = memberIdsByRepo.get(link.from.repo);
580
+ const toIds = memberIdsByRepo.get(link.to.repo);
581
+ return fromIds?.has(link.from.symbolUid) || toIds?.has(link.to.symbolUid);
582
+ })
583
+ .map((link) => ({
584
+ sourceRepo: link.from.repo,
585
+ sourceService: link.from.service,
586
+ targetRepo: link.to.repo,
587
+ targetService: link.to.service,
588
+ contractName: link.contractId,
589
+ relationship: 'shared-contract',
590
+ confidence: link.confidence,
591
+ evidence: [
592
+ `${link.type}:${link.contractId}`,
593
+ `${link.from.symbolRef.filePath} -> ${link.to.symbolRef.filePath}`,
594
+ `match:${link.matchType}`,
595
+ ],
596
+ }));
597
+ return {
598
+ group: name,
599
+ cluster: clusterName,
600
+ results: contexts,
601
+ cross_repo_links: crossRepoLinks,
602
+ ...(registryResult.ok === true && registryResult.skippedCorrupt > 0
603
+ ? { skippedCorruptContracts: registryResult.skippedCorrupt }
604
+ : {}),
605
+ errors: results
606
+ .filter((result) => result.payload?.error)
607
+ .map((result) => ({
608
+ repoPath: result.repoPath,
609
+ registryName: result.registryName,
610
+ error: result.payload.error,
611
+ })),
612
+ };
613
+ }
614
+ async groupFeatureImpact(params) {
615
+ const name = String(params.name ?? '').trim();
616
+ const clusterName = String(params.cluster ?? params.target ?? params.feature ?? '').trim();
617
+ if (!name || !clusterName)
618
+ return { error: 'name and cluster are required' };
619
+ const featureImpact = this.port.featureImpact;
620
+ if (!featureImpact)
621
+ return { error: 'feature cluster impact is unavailable' };
622
+ const direction = params.direction === 'downstream' || params.direction === 'both'
623
+ ? params.direction
624
+ : 'upstream';
625
+ const limit = typeof params.limit === 'number' && params.limit > 0 ? params.limit : 100;
626
+ const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
627
+ const subgroupExact = params.subgroupExact === true;
628
+ const groupDir = getGroupDir(getDefaultCodragraphDir(), name);
629
+ let config;
630
+ try {
631
+ config = await loadGroupConfig(groupDir);
632
+ }
633
+ catch (err) {
634
+ if (err instanceof GroupNotFoundError)
635
+ return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
636
+ throw err;
637
+ }
638
+ const memberEntries = Object.entries(config.repos).filter(([repoPath]) => repoInSubgroup(repoPath, subgroup, subgroupExact));
639
+ const registryResult = await loadContractRegistryResilient(groupDir);
640
+ const registry = registryResult.ok ? registryResult.registry : null;
641
+ const results = await Promise.all(memberEntries.map(async ([repoPath, registryName]) => {
642
+ try {
643
+ const repoObj = await this.port.resolveRepo(registryName);
644
+ const payload = await featureImpact(repoObj, {
645
+ name: clusterName,
646
+ direction,
647
+ limit,
648
+ });
649
+ return { repoPath, registryName, payload };
650
+ }
651
+ catch (e) {
652
+ return {
653
+ repoPath,
654
+ registryName,
655
+ payload: { error: e instanceof Error ? e.message : String(e) },
656
+ };
657
+ }
658
+ }));
659
+ const successfulResults = results.filter((result) => !result.payload?.error);
660
+ const memberIdsByRepo = new Map();
661
+ for (const result of successfulResults) {
662
+ const payload = result.payload;
663
+ memberIdsByRepo.set(result.repoPath, new Set((payload.contextPack?.members ?? [])
664
+ .map((member) => String(member.id || ''))
665
+ .filter(Boolean)));
666
+ }
667
+ const crossRepoLinks = (registry?.crossLinks ?? [])
668
+ .filter((link) => {
669
+ const fromIds = memberIdsByRepo.get(link.from.repo);
670
+ const toIds = memberIdsByRepo.get(link.to.repo);
671
+ return fromIds?.has(link.from.symbolUid) || toIds?.has(link.to.symbolUid);
672
+ })
673
+ .map((link) => ({
674
+ sourceRepo: link.from.repo,
675
+ sourceService: link.from.service,
676
+ targetRepo: link.to.repo,
677
+ targetService: link.to.service,
678
+ contractName: link.contractId,
679
+ relationship: 'shared-contract',
680
+ confidence: link.confidence,
681
+ evidence: [
682
+ `${link.type}:${link.contractId}`,
683
+ `${link.from.symbolRef.filePath} -> ${link.to.symbolRef.filePath}`,
684
+ `match:${link.matchType}`,
685
+ ],
686
+ }));
687
+ return {
688
+ group: name,
689
+ cluster: clusterName,
690
+ direction,
691
+ results: successfulResults,
692
+ cross_repo_links: crossRepoLinks,
693
+ summary: {
694
+ repos: successfulResults.length,
695
+ crossRepoLinks: crossRepoLinks.length,
696
+ },
697
+ ...(registryResult.ok === true && registryResult.skippedCorrupt > 0
698
+ ? { skippedCorruptContracts: registryResult.skippedCorrupt }
699
+ : {}),
700
+ errors: results
701
+ .filter((result) => result.payload?.error)
702
+ .map((result) => ({
703
+ repoPath: result.repoPath,
704
+ registryName: result.registryName,
705
+ error: result.payload.error,
706
+ })),
707
+ };
708
+ }
349
709
  async groupStatus(params) {
350
710
  const name = String(params.name ?? '').trim();
351
711
  if (!name)
@@ -15,7 +15,7 @@
15
15
  * - `confidence`: the pre-computed confidence from the Reference record.
16
16
  * - `reason`: human-readable summary (`"scope-resolution: call | confidence 0.75"`).
17
17
  * - `evidence`: the full `ResolutionEvidence[]` trace — additive graph
18
- * property (see `GraphRelationship.evidence` in codragraph-shared),
18
+ * property (see `GraphRelationship.evidence` in @codragraph/shared),
19
19
  * so queries that don't know about it are unaffected.
20
20
  * - `step`: carries the reference's access-kind discriminant when
21
21
  * available (`1` for read, `2` for write) so `ACCESSES` edges retain
@@ -15,7 +15,7 @@
15
15
  * - `confidence`: the pre-computed confidence from the Reference record.
16
16
  * - `reason`: human-readable summary (`"scope-resolution: call | confidence 0.75"`).
17
17
  * - `evidence`: the full `ResolutionEvidence[]` trace — additive graph
18
- * property (see `GraphRelationship.evidence` in codragraph-shared),
18
+ * property (see `GraphRelationship.evidence` in @codragraph/shared),
19
19
  * so queries that don't know about it are unaffected.
20
20
  * - `step`: carries the reference's access-kind discriminant when
21
21
  * available (`1` for read, `2` for write) so `ACCESSES` edges retain
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Feature Cluster Processor
3
+ *
4
+ * Builds a stable, human-facing feature layer over the raw code graph.
5
+ * Communities are graph-algorithm clusters; FeatureCluster nodes are
6
+ * product/domain clusters such as Settings, AI, Auth, Billing, MCP, or
7
+ * Ingestion. Agents can query this layer first to land directly on the
8
+ * files, symbols, and line ranges that matter for a task.
9
+ */
10
+ import type { FeatureClusterKind } from '../../_shared/index.js';
11
+ import { KnowledgeGraph } from '../graph/types.js';
12
+ export interface FeatureClusterNode {
13
+ id: string;
14
+ name: string;
15
+ slug: string;
16
+ featureKind: FeatureClusterKind;
17
+ summary: string;
18
+ description: string;
19
+ repo?: string;
20
+ service?: string;
21
+ signals: string[];
22
+ memberCount: number;
23
+ entryPointIds: string[];
24
+ routes: string[];
25
+ tools: string[];
26
+ testCoverageHints: string[];
27
+ lastIndexedCommit?: string;
28
+ confidence: number;
29
+ memberIds: string[];
30
+ }
31
+ export interface FeatureClusterMembership {
32
+ nodeId: string;
33
+ clusterId: string;
34
+ confidence: number;
35
+ signals: string[];
36
+ }
37
+ export interface FeatureClusterDependency {
38
+ sourceClusterId: string;
39
+ targetClusterId: string;
40
+ edgeCount: number;
41
+ relationshipTypes: string[];
42
+ confidence: number;
43
+ }
44
+ export interface FeatureClusterDetectionResult {
45
+ clusters: FeatureClusterNode[];
46
+ memberships: FeatureClusterMembership[];
47
+ dependencies: FeatureClusterDependency[];
48
+ stats: {
49
+ totalClusters: number;
50
+ totalMemberships: number;
51
+ totalDependencies: number;
52
+ nodesProcessed: number;
53
+ };
54
+ }
55
+ export interface FeatureClusterConfig {
56
+ minMembers: number;
57
+ maxClusters: number;
58
+ repo?: string;
59
+ service?: string;
60
+ lastIndexedCommit?: string;
61
+ }
62
+ export declare const processFeatureClusters: (knowledgeGraph: KnowledgeGraph, onProgress?: (message: string, progress: number) => void, config?: Partial<FeatureClusterConfig>) => Promise<FeatureClusterDetectionResult>;