@codragraph/cli 2.1.0 → 2.1.1

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 (67) hide show
  1. package/README.md +58 -20
  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 +27 -24
  19. package/dist/cli/index.js +37 -0
  20. package/dist/cli/setup.js +9 -5
  21. package/dist/cli/tool.d.ts +25 -0
  22. package/dist/cli/tool.js +74 -0
  23. package/dist/config/supported-languages.d.ts +3 -3
  24. package/dist/config/supported-languages.js +3 -3
  25. package/dist/core/cgdb/cgdb-adapter.js +19 -3
  26. package/dist/core/cgdb/csv-generator.js +33 -2
  27. package/dist/core/cgdb/schema.d.ts +2 -1
  28. package/dist/core/cgdb/schema.js +55 -0
  29. package/dist/core/embeddings/embedder.js +4 -2
  30. package/dist/core/graphstore/index.d.ts +1 -1
  31. package/dist/core/graphstore/index.js +1 -1
  32. package/dist/core/group/service.d.ts +16 -0
  33. package/dist/core/group/service.js +360 -0
  34. package/dist/core/ingestion/emit-references.d.ts +1 -1
  35. package/dist/core/ingestion/emit-references.js +1 -1
  36. package/dist/core/ingestion/feature-cluster-processor.d.ts +62 -0
  37. package/dist/core/ingestion/feature-cluster-processor.js +626 -0
  38. package/dist/core/ingestion/finalize-orchestrator.js +1 -1
  39. package/dist/core/ingestion/model/registration-table.js +1 -0
  40. package/dist/core/ingestion/model/resolve.d.ts +2 -2
  41. package/dist/core/ingestion/model/resolve.js +3 -3
  42. package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
  43. package/dist/core/ingestion/model/semantic-model.js +1 -1
  44. package/dist/core/ingestion/model/symbol-table.d.ts +1 -1
  45. package/dist/core/ingestion/model/symbol-table.js +1 -1
  46. package/dist/core/ingestion/pipeline-phases/feature-clusters.d.ts +17 -0
  47. package/dist/core/ingestion/pipeline-phases/feature-clusters.js +88 -0
  48. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  49. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  50. package/dist/core/ingestion/pipeline.d.ts +4 -0
  51. package/dist/core/ingestion/pipeline.js +9 -5
  52. package/dist/core/run-analyze.d.ts +1 -0
  53. package/dist/core/run-analyze.js +12 -6
  54. package/dist/mcp/core/embedder.js +5 -2
  55. package/dist/mcp/local/local-backend.d.ts +12 -0
  56. package/dist/mcp/local/local-backend.js +381 -3
  57. package/dist/mcp/resources.js +139 -0
  58. package/dist/mcp/tools.js +174 -2
  59. package/dist/server/api.js +116 -0
  60. package/dist/storage/repo-manager.d.ts +6 -1
  61. package/dist/storage/repo-manager.js +5 -1
  62. package/dist/types/pipeline.d.ts +2 -0
  63. package/package.json +13 -4
  64. package/scripts/build.js +13 -12
  65. package/skills/codragraph-cli.md +17 -1
  66. package/skills/codragraph-guide.md +6 -2
  67. package/skills/codragraph-onboarding.md +2 -2
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * ## Dependency direction
11
11
  *
12
- * codragraph-shared (NodeLabel) — leaf
12
+ * @codragraph/shared (NodeLabel) — leaf
13
13
  * ↑
14
14
  * symbol-table.ts — pure file/callable index
15
15
  * ↑
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * Dependency direction (strictly enforced):
19
19
  *
20
- * codragraph-shared (NodeLabel) — leaf type
20
+ * @codragraph/shared (NodeLabel) — leaf type
21
21
  * ↑
22
22
  * symbol-table.ts — THIS FILE (pure storage)
23
23
  * ↑
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * Dependency direction (strictly enforced):
19
19
  *
20
- * codragraph-shared (NodeLabel) — leaf type
20
+ * @codragraph/shared (NodeLabel) — leaf type
21
21
  * ↑
22
22
  * symbol-table.ts — THIS FILE (pure storage)
23
23
  * ↑
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Phase: featureClusters
3
+ *
4
+ * Creates human-facing FeatureCluster nodes above algorithmic Community nodes.
5
+ * This is the layer agents query for product/domain areas such as Settings,
6
+ * AI, Auth, MCP, or Ingestion before drilling into exact symbols.
7
+ *
8
+ * @deps processes, structure
9
+ * @reads graph (all nodes and relationships)
10
+ * @writes graph (FeatureCluster nodes, FEATURE_MEMBER_OF, FEATURE_DEPENDS_ON)
11
+ */
12
+ import type { PipelinePhase } from './types.js';
13
+ import { type FeatureClusterDetectionResult } from '../feature-cluster-processor.js';
14
+ export interface FeatureClustersOutput {
15
+ featureClusterResult: FeatureClusterDetectionResult;
16
+ }
17
+ export declare const featureClustersPhase: PipelinePhase<FeatureClustersOutput>;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Phase: featureClusters
3
+ *
4
+ * Creates human-facing FeatureCluster nodes above algorithmic Community nodes.
5
+ * This is the layer agents query for product/domain areas such as Settings,
6
+ * AI, Auth, MCP, or Ingestion before drilling into exact symbols.
7
+ *
8
+ * @deps processes, structure
9
+ * @reads graph (all nodes and relationships)
10
+ * @writes graph (FeatureCluster nodes, FEATURE_MEMBER_OF, FEATURE_DEPENDS_ON)
11
+ */
12
+ import { getPhaseOutput } from './types.js';
13
+ import { processFeatureClusters, } from '../feature-cluster-processor.js';
14
+ import { generateId } from '../../../lib/utils.js';
15
+ import { isDev } from '../utils/env.js';
16
+ export const featureClustersPhase = {
17
+ name: 'featureClusters',
18
+ deps: ['processes', 'structure'],
19
+ async execute(ctx, deps) {
20
+ const { totalFiles } = getPhaseOutput(deps, 'structure');
21
+ ctx.onProgress({
22
+ phase: 'feature_clusters',
23
+ percent: 99,
24
+ message: 'Building feature clusters...',
25
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: ctx.graph.nodeCount },
26
+ });
27
+ const featureClusterResult = await processFeatureClusters(ctx.graph, (message, progress) => {
28
+ ctx.onProgress({
29
+ phase: 'feature_clusters',
30
+ percent: Math.round(99 + progress * 0.009),
31
+ message,
32
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: ctx.graph.nodeCount },
33
+ });
34
+ }, {
35
+ repo: ctx.options?.featureClusterRepo,
36
+ lastIndexedCommit: ctx.options?.lastIndexedCommit,
37
+ });
38
+ if (isDev) {
39
+ console.log(`Feature clustering: ${featureClusterResult.stats.totalClusters} clusters, ${featureClusterResult.stats.totalMemberships} memberships`);
40
+ }
41
+ featureClusterResult.clusters.forEach((cluster) => {
42
+ ctx.graph.addNode({
43
+ id: cluster.id,
44
+ label: 'FeatureCluster',
45
+ properties: {
46
+ name: cluster.name,
47
+ filePath: '',
48
+ slug: cluster.slug,
49
+ featureKind: cluster.featureKind,
50
+ summary: cluster.summary,
51
+ description: cluster.description,
52
+ repo: cluster.repo,
53
+ service: cluster.service,
54
+ signals: cluster.signals,
55
+ memberCount: cluster.memberCount,
56
+ entryPointIds: cluster.entryPointIds,
57
+ routes: cluster.routes,
58
+ tools: cluster.tools,
59
+ testCoverageHints: cluster.testCoverageHints,
60
+ lastIndexedCommit: cluster.lastIndexedCommit,
61
+ confidence: cluster.confidence,
62
+ source: 'heuristic',
63
+ },
64
+ });
65
+ });
66
+ featureClusterResult.memberships.forEach((membership) => {
67
+ ctx.graph.addRelationship({
68
+ id: generateId('FEATURE_MEMBER_OF', `${membership.nodeId}->${membership.clusterId}`),
69
+ sourceId: membership.nodeId,
70
+ targetId: membership.clusterId,
71
+ type: 'FEATURE_MEMBER_OF',
72
+ confidence: membership.confidence,
73
+ reason: membership.signals.join('|'),
74
+ });
75
+ });
76
+ featureClusterResult.dependencies.forEach((dependency) => {
77
+ ctx.graph.addRelationship({
78
+ id: generateId('FEATURE_DEPENDS_ON', `${dependency.sourceClusterId}->${dependency.targetClusterId}`),
79
+ sourceId: dependency.sourceClusterId,
80
+ targetId: dependency.targetClusterId,
81
+ type: 'FEATURE_DEPENDS_ON',
82
+ confidence: dependency.confidence,
83
+ reason: `member-dependency|edges:${dependency.edgeCount}|types:${dependency.relationshipTypes.join(',')}`,
84
+ });
85
+ });
86
+ return { featureClusterResult };
87
+ },
88
+ };
@@ -17,6 +17,7 @@ export { scopeResolutionPhase, type ScopeResolutionOutput, } from '../scope-reso
17
17
  export { mroPhase, type MROOutput } from './mro.js';
18
18
  export { communitiesPhase, type CommunitiesOutput } from './communities.js';
19
19
  export { processesPhase, type ProcessesOutput } from './processes.js';
20
+ export { featureClustersPhase, type FeatureClustersOutput } from './feature-clusters.js';
20
21
  export { runPipeline } from './runner.js';
21
22
  export type { PipelinePhase, PipelineContext, PhaseResult } from './types.js';
22
23
  export { getPhaseOutput } from './types.js';
@@ -18,6 +18,7 @@ export { scopeResolutionPhase, } from '../scope-resolution/pipeline/phase.js';
18
18
  export { mroPhase } from './mro.js';
19
19
  export { communitiesPhase } from './communities.js';
20
20
  export { processesPhase } from './processes.js';
21
+ export { featureClustersPhase } from './feature-clusters.js';
21
22
  // ── Infrastructure ─────────────────────────────────────────────────────────
22
23
  export { runPipeline } from './runner.js';
23
24
  export { getPhaseOutput } from './types.js';
@@ -21,6 +21,10 @@ export interface PipelineOptions {
21
21
  skipGraphPhases?: boolean;
22
22
  /** Force sequential parsing (no worker pool). Useful for testing the sequential path. */
23
23
  skipWorkers?: boolean;
24
+ /** Repo label written onto FeatureCluster metadata. */
25
+ featureClusterRepo?: string;
26
+ /** Indexed source commit written onto FeatureCluster metadata. */
27
+ lastIndexedCommit?: string;
24
28
  /**
25
29
  * @internal Test-only override for worker-pool gating thresholds.
26
30
  * When unset, production defaults apply (15 files OR 512 KB total bytes).
@@ -15,15 +15,16 @@
15
15
  * See ARCHITECTURE.md for the full phase dependency diagram.
16
16
  */
17
17
  import { createKnowledgeGraph } from '../graph/graph.js';
18
- import { runPipeline, getPhaseOutput, scanPhase, structurePhase, markdownPhase, cobolPhase, parsePhase, routesPhase, toolsPhase, ormPhase, crossFilePhase, scopeResolutionPhase, mroPhase, communitiesPhase, processesPhase, } from './pipeline-phases/index.js';
18
+ import { runPipeline, getPhaseOutput, scanPhase, structurePhase, markdownPhase, cobolPhase, parsePhase, routesPhase, toolsPhase, ormPhase, crossFilePhase, scopeResolutionPhase, mroPhase, communitiesPhase, processesPhase, featureClustersPhase, } from './pipeline-phases/index.js';
19
19
  // ── Phase registry ─────────────────────────────────────────────────────────
20
20
  /**
21
21
  * All pipeline phases with their dependency relationships.
22
22
  *
23
23
  * Phase dependency graph:
24
24
  *
25
- * scan structure [markdown, cobol] parse [routes, tools, orm]
26
- * crossFile mro communities processes
25
+ * scan -> structure -> [markdown, cobol] -> parse -> [routes, tools, orm]
26
+ * -> crossFile -> scopeResolution -> mro -> communities -> processes
27
+ * -> featureClusters
27
28
  *
28
29
  * To add a new phase: create a file in pipeline-phases/, export the phase
29
30
  * object, and add it to the appropriate position in this array.
@@ -42,7 +43,7 @@ function buildPhaseList(options) {
42
43
  scopeResolutionPhase,
43
44
  ];
44
45
  if (!options?.skipGraphPhases) {
45
- phases.push(mroPhase, communitiesPhase, processesPhase);
46
+ phases.push(mroPhase, communitiesPhase, processesPhase, featureClustersPhase);
46
47
  }
47
48
  return phases;
48
49
  }
@@ -62,15 +63,17 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
62
63
  const { totalFiles, usedWorkerPool } = getPhaseOutput(results, 'parse');
63
64
  let communityResult;
64
65
  let processResult;
66
+ let featureClusterResult;
65
67
  if (!options?.skipGraphPhases) {
66
68
  communityResult = getPhaseOutput(results, 'communities').communityResult;
67
69
  processResult = getPhaseOutput(results, 'processes').processResult;
70
+ featureClusterResult = getPhaseOutput(results, 'featureClusters').featureClusterResult;
68
71
  }
69
72
  onProgress({
70
73
  phase: 'complete',
71
74
  percent: 100,
72
75
  message: communityResult && processResult
73
- ? `Graph complete! ${communityResult.stats.totalCommunities} communities, ${processResult.stats.totalProcesses} processes detected.`
76
+ ? `Graph complete! ${communityResult.stats.totalCommunities} communities, ${processResult.stats.totalProcesses} processes, ${featureClusterResult?.stats.totalClusters ?? 0} feature clusters detected.`
74
77
  : 'Graph complete! (graph phases skipped)',
75
78
  stats: {
76
79
  filesProcessed: totalFiles,
@@ -84,6 +87,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
84
87
  totalFileCount: totalFiles,
85
88
  communityResult,
86
89
  processResult,
90
+ featureClusterResult,
87
91
  usedWorkerPool,
88
92
  };
89
93
  };
@@ -63,6 +63,7 @@ export interface AnalyzeResult {
63
63
  nodes?: number;
64
64
  edges?: number;
65
65
  communities?: number;
66
+ featureClusters?: number;
66
67
  processes?: number;
67
68
  embeddings?: number;
68
69
  };
@@ -31,6 +31,7 @@ export const PHASE_LABELS = {
31
31
  heritage: 'Extracting inheritance',
32
32
  communities: 'Detecting communities',
33
33
  processes: 'Detecting processes',
34
+ feature_clusters: 'Building feature clusters',
34
35
  complete: 'Pipeline complete',
35
36
  cgdb: 'Loading into LadybugDB',
36
37
  fts: 'Creating search indexes',
@@ -129,10 +130,10 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
129
130
  // ── Early-return: already up to date ──────────────────────────────
130
131
  // Schema-version mismatch forces a full re-analyze regardless of commit
131
132
  // equality: existing 1.7.x indexes have no `schemaVersion` field at all,
132
- // and 1.8+ readers expect every node table to carry a `contentEncoding`
133
- // column (RFC 0001 Phase 2). LadybugDB ALTER on existing tables is not
134
- // validated end-to-end yet, so the supported migration path is
135
- // re-analyze → fresh CREATE NODE TABLE.
133
+ // and current readers expect contentEncoding plus rich FeatureCluster
134
+ // context-pack columns. LadybugDB ALTER on existing tables is not validated
135
+ // end-to-end yet, so the supported migration path is re-analyze via a fresh
136
+ // CREATE NODE TABLE.
136
137
  const schemaUpToDate = !!existingMeta && (existingMeta.schemaVersion ?? 0) >= INDEX_SCHEMA_VERSION;
137
138
  if (existingMeta &&
138
139
  schemaUpToDate &&
@@ -150,7 +151,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
150
151
  }
151
152
  if (existingMeta && !schemaUpToDate) {
152
153
  log(`Index schema version ${existingMeta.schemaVersion ?? '<missing>'} is older than ` +
153
- `${INDEX_SCHEMA_VERSION} (RFC 0001 Phase 2 — adds contentEncoding column). ` +
154
+ `${INDEX_SCHEMA_VERSION} (FeatureCluster context-pack schema). ` +
154
155
  `Re-analyzing.`);
155
156
  }
156
157
  // ── Cache embeddings from existing index before rebuild ────────────
@@ -175,10 +176,14 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
175
176
  }
176
177
  }
177
178
  // ── Phase 1: Full Pipeline (0–60%) ────────────────────────────────
179
+ const repoNameForFeatureClusters = options.registryName ?? getInferredRepoName(repoPath) ?? path.basename(repoPath);
178
180
  const pipelineResult = await runPipelineFromRepo(repoPath, (p) => {
179
181
  const phaseLabel = PHASE_LABELS[p.phase] || p.phase;
180
182
  const scaled = Math.round(p.percent * 0.6);
181
183
  progress(p.phase, scaled, phaseLabel);
184
+ }, {
185
+ featureClusterRepo: repoNameForFeatureClusters,
186
+ lastIndexedCommit: currentCommit || undefined,
182
187
  });
183
188
  // ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────
184
189
  progress('cgdb', 60, 'Loading into LadybugDB...');
@@ -334,6 +339,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
334
339
  nodes: stats.nodes,
335
340
  edges: stats.edges,
336
341
  communities: pipelineResult.communityResult?.stats.totalCommunities,
342
+ featureClusters: pipelineResult.featureClusterResult?.stats.totalClusters,
337
343
  processes: pipelineResult.processResult?.stats.totalProcesses,
338
344
  embeddings: embeddingCount,
339
345
  },
@@ -372,7 +378,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
372
378
  nodes: stats.nodes,
373
379
  edges: stats.edges,
374
380
  communities: pipelineResult.communityResult?.stats.totalCommunities,
375
- clusters: aggregatedClusterCount,
381
+ clusters: pipelineResult.featureClusterResult?.stats.totalClusters ?? aggregatedClusterCount,
376
382
  processes: pipelineResult.processResult?.stats.totalProcesses,
377
383
  }, undefined, { skipAgentsMd: options.skipAgentsMd, noStats: options.noStats });
378
384
  }
@@ -6,6 +6,8 @@
6
6
  */
7
7
  import { pipeline, env } from '@huggingface/transformers';
8
8
  import { isHttpMode, getHttpDimensions, httpEmbedQuery, } from '../../core/embeddings/http-client.js';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
9
11
  import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/cgdb/pool-adapter.js';
10
12
  // Model config
11
13
  const MODEL_ID = 'Snowflake/snowflake-arctic-embed-xs';
@@ -33,8 +35,9 @@ export const initEmbedder = async () => {
33
35
  // Default cache to user-writable location. transformers.js defaults to
34
36
  // ./node_modules/.cache inside its own install dir, which is unwritable
35
37
  // when codragraph is installed globally (e.g. /usr/lib/node_modules/).
36
- // Respect HF_HOME if set, otherwise fall back to ~/.cache/huggingface.
37
- env.cacheDir = process.env.HF_HOME ?? `${process.env.HOME}/.cache/huggingface`;
38
+ // Respect HF_HOME if set, otherwise fall back to a user-writable cache
39
+ // path using Node's OS-aware home directory resolution.
40
+ env.cacheDir = process.env.HF_HOME ?? join(homedir(), '.cache', 'huggingface');
38
41
  console.error('CodraGraph: Loading embedding model (first search may take a moment)...');
39
42
  // Try GPU first (DirectML on Windows, CUDA on Linux), fall back to CPU
40
43
  const isWindows = process.platform === 'win32';
@@ -323,6 +323,18 @@ export declare class LocalBackend {
323
323
  * Query clusters (communities) directly from graph.
324
324
  * Used by getClustersResource — avoids legacy overview() dispatch.
325
325
  */
326
+ /**
327
+ * Query feature clusters directly from graph.
328
+ * FeatureCluster is the human-facing project area layer above Communities.
329
+ */
330
+ queryFeatureClusters(repoName?: string, limit?: number, query?: string): Promise<{
331
+ clusters: any[];
332
+ }>;
333
+ /**
334
+ * Query one feature cluster with members, dependencies, and process links.
335
+ */
336
+ queryFeatureContext(name: string, repoName?: string, limit?: number): Promise<any>;
337
+ queryFeatureImpact(name: string, repoName?: string, direction?: 'upstream' | 'downstream' | 'both', limit?: number): Promise<any>;
326
338
  queryClusters(repoName?: string, limit?: number): Promise<{
327
339
  clusters: any[];
328
340
  }>;