@optave/codegraph 3.1.4 → 3.2.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.
- package/README.md +29 -72
- package/package.json +10 -8
- package/src/ast-analysis/engine.js +260 -246
- package/src/ast-analysis/shared.js +2 -14
- package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
- package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
- package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
- package/src/cli/commands/ast.js +4 -7
- package/src/cli/commands/audit.js +11 -11
- package/src/cli/commands/batch.js +6 -5
- package/src/cli/commands/branch-compare.js +1 -1
- package/src/cli/commands/brief.js +12 -0
- package/src/cli/commands/build.js +1 -1
- package/src/cli/commands/cfg.js +5 -8
- package/src/cli/commands/check.js +28 -36
- package/src/cli/commands/children.js +9 -7
- package/src/cli/commands/co-change.js +5 -3
- package/src/cli/commands/communities.js +2 -6
- package/src/cli/commands/complexity.js +5 -3
- package/src/cli/commands/context.js +9 -8
- package/src/cli/commands/cycles.js +12 -8
- package/src/cli/commands/dataflow.js +5 -8
- package/src/cli/commands/deps.js +9 -8
- package/src/cli/commands/diff-impact.js +2 -6
- package/src/cli/commands/embed.js +1 -1
- package/src/cli/commands/export.js +34 -31
- package/src/cli/commands/exports.js +2 -6
- package/src/cli/commands/flow.js +5 -8
- package/src/cli/commands/fn-impact.js +9 -8
- package/src/cli/commands/impact.js +2 -6
- package/src/cli/commands/info.js +2 -2
- package/src/cli/commands/map.js +1 -1
- package/src/cli/commands/mcp.js +1 -1
- package/src/cli/commands/models.js +1 -1
- package/src/cli/commands/owners.js +5 -3
- package/src/cli/commands/path.js +2 -2
- package/src/cli/commands/plot.js +40 -31
- package/src/cli/commands/query.js +9 -8
- package/src/cli/commands/registry.js +2 -2
- package/src/cli/commands/roles.js +5 -8
- package/src/cli/commands/search.js +9 -3
- package/src/cli/commands/sequence.js +5 -8
- package/src/cli/commands/snapshot.js +6 -1
- package/src/cli/commands/stats.js +1 -1
- package/src/cli/commands/structure.js +5 -4
- package/src/cli/commands/triage.js +41 -30
- package/src/cli/commands/watch.js +1 -1
- package/src/cli/commands/where.js +2 -6
- package/src/cli/index.js +11 -5
- package/src/cli/shared/open-graph.js +13 -0
- package/src/cli/shared/options.js +22 -2
- package/src/cli.js +1 -1
- package/src/db/connection.js +140 -11
- package/src/{db.js → db/index.js} +12 -5
- package/src/db/migrations.js +42 -65
- package/src/db/query-builder.js +72 -9
- package/src/db/repository/base.js +1 -1
- package/src/db/repository/graph-read.js +3 -3
- package/src/db/repository/in-memory-repository.js +30 -28
- package/src/db/repository/nodes.js +10 -17
- package/src/domain/analysis/brief.js +155 -0
- package/src/domain/analysis/context.js +392 -0
- package/src/domain/analysis/dependencies.js +395 -0
- package/src/{analysis → domain/analysis}/exports.js +11 -6
- package/src/domain/analysis/impact.js +581 -0
- package/src/domain/analysis/module-map.js +348 -0
- package/src/{analysis → domain/analysis}/roles.js +12 -9
- package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
- package/src/{builder → domain/graph/builder}/helpers.js +4 -4
- package/src/{builder → domain/graph/builder}/incremental.js +119 -93
- package/src/domain/graph/builder/pipeline.js +156 -0
- package/src/domain/graph/builder/stages/build-edges.js +376 -0
- package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
- package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
- package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
- package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
- package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
- package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
- package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
- package/src/{cycles.js → domain/graph/cycles.js} +4 -4
- package/src/{journal.js → domain/graph/journal.js} +1 -1
- package/src/{resolve.js → domain/graph/resolve.js} +2 -2
- package/src/{watcher.js → domain/graph/watcher.js} +7 -7
- package/src/{parser.js → domain/parser.js} +24 -15
- package/src/{queries.js → domain/queries.js} +17 -16
- package/src/{embeddings → domain/search}/generator.js +3 -3
- package/src/{embeddings → domain/search}/models.js +2 -2
- package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
- package/src/{embeddings → domain/search}/search/filters.js +9 -5
- package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
- package/src/{embeddings → domain/search}/search/keyword.js +13 -6
- package/src/{embeddings → domain/search}/search/prepare.js +15 -7
- package/src/{embeddings → domain/search}/search/semantic.js +1 -1
- package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
- package/src/extractors/csharp.js +224 -207
- package/src/extractors/go.js +176 -172
- package/src/extractors/hcl.js +94 -78
- package/src/extractors/java.js +213 -207
- package/src/extractors/javascript.js +275 -305
- package/src/extractors/php.js +234 -221
- package/src/extractors/python.js +252 -250
- package/src/extractors/ruby.js +192 -185
- package/src/extractors/rust.js +182 -167
- package/src/{ast.js → features/ast.js} +13 -11
- package/src/{audit.js → features/audit.js} +20 -46
- package/src/{batch.js → features/batch.js} +5 -5
- package/src/{boundaries.js → features/boundaries.js} +100 -85
- package/src/{branch-compare.js → features/branch-compare.js} +3 -3
- package/src/{cfg.js → features/cfg.js} +141 -150
- package/src/{check.js → features/check.js} +13 -30
- package/src/{cochange.js → features/cochange.js} +5 -5
- package/src/{communities.js → features/communities.js} +72 -57
- package/src/{complexity.js → features/complexity.js} +154 -143
- package/src/{dataflow.js → features/dataflow.js} +155 -158
- package/src/{export.js → features/export.js} +6 -6
- package/src/{flow.js → features/flow.js} +4 -4
- package/src/{viewer.js → features/graph-enrichment.js} +8 -8
- package/src/{manifesto.js → features/manifesto.js} +15 -12
- package/src/{owners.js → features/owners.js} +6 -5
- package/src/features/sequence.js +300 -0
- package/src/features/shared/find-nodes.js +31 -0
- package/src/{snapshot.js → features/snapshot.js} +3 -3
- package/src/{structure.js → features/structure.js} +139 -108
- package/src/features/triage.js +141 -0
- package/src/graph/builders/dependency.js +33 -14
- package/src/graph/classifiers/risk.js +3 -2
- package/src/graph/classifiers/roles.js +6 -3
- package/src/index.cjs +16 -0
- package/src/index.js +40 -39
- package/src/{native.js → infrastructure/native.js} +1 -1
- package/src/mcp/middleware.js +1 -1
- package/src/mcp/server.js +68 -59
- package/src/mcp/tool-registry.js +15 -2
- package/src/mcp/tools/ast-query.js +1 -1
- package/src/mcp/tools/audit.js +1 -1
- package/src/mcp/tools/batch-query.js +1 -1
- package/src/mcp/tools/branch-compare.js +3 -1
- package/src/mcp/tools/brief.js +8 -0
- package/src/mcp/tools/cfg.js +1 -1
- package/src/mcp/tools/check.js +3 -3
- package/src/mcp/tools/co-changes.js +1 -1
- package/src/mcp/tools/code-owners.js +1 -1
- package/src/mcp/tools/communities.js +1 -1
- package/src/mcp/tools/complexity.js +1 -1
- package/src/mcp/tools/dataflow.js +2 -2
- package/src/mcp/tools/execution-flow.js +2 -2
- package/src/mcp/tools/export-graph.js +2 -2
- package/src/mcp/tools/find-cycles.js +2 -2
- package/src/mcp/tools/index.js +2 -0
- package/src/mcp/tools/list-repos.js +1 -1
- package/src/mcp/tools/sequence.js +1 -1
- package/src/mcp/tools/structure.js +1 -1
- package/src/mcp/tools/triage.js +2 -2
- package/src/{commands → presentation}/audit.js +2 -2
- package/src/{commands → presentation}/batch.js +1 -1
- package/src/{commands → presentation}/branch-compare.js +2 -2
- package/src/presentation/brief.js +51 -0
- package/src/{commands → presentation}/cfg.js +1 -1
- package/src/{commands → presentation}/check.js +2 -2
- package/src/{commands → presentation}/communities.js +1 -1
- package/src/{commands → presentation}/complexity.js +1 -1
- package/src/{commands → presentation}/dataflow.js +1 -1
- package/src/{commands → presentation}/flow.js +2 -2
- package/src/{commands → presentation}/manifesto.js +1 -1
- package/src/{commands → presentation}/owners.js +1 -1
- package/src/presentation/queries-cli/exports.js +53 -0
- package/src/presentation/queries-cli/impact.js +214 -0
- package/src/presentation/queries-cli/index.js +5 -0
- package/src/presentation/queries-cli/inspect.js +329 -0
- package/src/presentation/queries-cli/overview.js +196 -0
- package/src/presentation/queries-cli/path.js +65 -0
- package/src/presentation/queries-cli.js +27 -0
- package/src/{commands → presentation}/query.js +1 -1
- package/src/presentation/result-formatter.js +126 -3
- package/src/{commands → presentation}/sequence.js +2 -2
- package/src/{commands → presentation}/structure.js +1 -1
- package/src/presentation/table.js +0 -8
- package/src/{commands → presentation}/triage.js +1 -1
- package/src/{constants.js → shared/constants.js} +1 -1
- package/src/shared/file-utils.js +2 -2
- package/src/shared/generators.js +9 -5
- package/src/shared/hierarchy.js +1 -1
- package/src/{kinds.js → shared/kinds.js} +1 -1
- package/src/analysis/context.js +0 -408
- package/src/analysis/dependencies.js +0 -341
- package/src/analysis/impact.js +0 -463
- package/src/analysis/module-map.js +0 -322
- package/src/builder/pipeline.js +0 -130
- package/src/builder/stages/build-edges.js +0 -297
- package/src/builder/stages/insert-nodes.js +0 -195
- package/src/mcp.js +0 -2
- package/src/queries-cli.js +0 -866
- package/src/sequence.js +0 -289
- package/src/triage.js +0 -126
- /package/src/{builder → domain/graph/builder}/context.js +0 -0
- /package/src/{builder.js → domain/graph/builder.js} +0 -0
- /package/src/{embeddings → domain/search}/index.js +0 -0
- /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
- /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
- /package/src/{config.js → infrastructure/config.js} +0 -0
- /package/src/{logger.js → infrastructure/logger.js} +0 -0
- /package/src/{registry.js → infrastructure/registry.js} +0 -0
- /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
- /package/src/{commands → presentation}/cochange.js +0 -0
- /package/src/{errors.js → shared/errors.js} +0 -0
- /package/src/{paginate.js → shared/paginate.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node role classification — pure logic, no DB.
|
|
3
3
|
*
|
|
4
|
-
* Roles: entry, core, utility, adapter, leaf, dead
|
|
4
|
+
* Roles: entry, core, utility, adapter, leaf, dead, test-only
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export const FRAMEWORK_ENTRY_PREFIXES = ['route:', 'event:', 'command:'];
|
|
@@ -15,7 +15,7 @@ function median(sorted) {
|
|
|
15
15
|
/**
|
|
16
16
|
* Classify nodes into architectural roles based on fan-in/fan-out metrics.
|
|
17
17
|
*
|
|
18
|
-
* @param {{ id: string, name: string, fanIn: number, fanOut: number, isExported: boolean }[]} nodes
|
|
18
|
+
* @param {{ id: string, name: string, fanIn: number, fanOut: number, isExported: boolean, testOnlyFanIn?: number }[]} nodes
|
|
19
19
|
* @returns {Map<string, string>} nodeId → role
|
|
20
20
|
*/
|
|
21
21
|
export function classifyRoles(nodes) {
|
|
@@ -38,15 +38,18 @@ export function classifyRoles(nodes) {
|
|
|
38
38
|
for (const node of nodes) {
|
|
39
39
|
const highIn = node.fanIn >= medFanIn && node.fanIn > 0;
|
|
40
40
|
const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
|
|
41
|
+
const hasProdFanIn = typeof node.productionFanIn === 'number';
|
|
41
42
|
|
|
42
43
|
let role;
|
|
43
44
|
const isFrameworkEntry = FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p));
|
|
44
45
|
if (isFrameworkEntry) {
|
|
45
46
|
role = 'entry';
|
|
46
47
|
} else if (node.fanIn === 0 && !node.isExported) {
|
|
47
|
-
role = 'dead';
|
|
48
|
+
role = node.testOnlyFanIn > 0 ? 'test-only' : 'dead';
|
|
48
49
|
} else if (node.fanIn === 0 && node.isExported) {
|
|
49
50
|
role = 'entry';
|
|
51
|
+
} else if (hasProdFanIn && node.fanIn > 0 && node.productionFanIn === 0) {
|
|
52
|
+
role = 'test-only';
|
|
50
53
|
} else if (highIn && !highOut) {
|
|
51
54
|
role = 'core';
|
|
52
55
|
} else if (highIn && highOut) {
|
package/src/index.cjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CJS compatibility wrapper — delegates to ESM via dynamic import().
|
|
3
|
+
*
|
|
4
|
+
* This wrapper always returns a Promise on every Node version, because
|
|
5
|
+
* import() is unconditionally async. You must always await the result:
|
|
6
|
+
*
|
|
7
|
+
* const codegraph = await require('@optave/codegraph');
|
|
8
|
+
*
|
|
9
|
+
* // Named destructuring at require-time does NOT work — always await the full result first.
|
|
10
|
+
* // BAD: const { buildGraph } = require('@optave/codegraph'); // buildGraph is undefined
|
|
11
|
+
* // GOOD: const { buildGraph } = await require('@optave/codegraph');
|
|
12
|
+
*/
|
|
13
|
+
// Note: if import() rejects (e.g. missing dependency), the rejected Promise is cached
|
|
14
|
+
// by the CJS module system and every subsequent require() call will re-surface the same
|
|
15
|
+
// rejection without re-attempting the load.
|
|
16
|
+
module.exports = import('./index.js');
|
package/src/index.js
CHANGED
|
@@ -9,42 +9,10 @@
|
|
|
9
9
|
* import { buildGraph, queryNameData, findCycles, exportDOT } from '@optave/codegraph';
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export { batchData } from './batch.js';
|
|
15
|
-
export { branchCompareData } from './branch-compare.js';
|
|
16
|
-
export { buildGraph } from './builder.js';
|
|
17
|
-
export { cfgData } from './cfg.js';
|
|
18
|
-
export { checkData } from './check.js';
|
|
19
|
-
export { coChangeData } from './cochange.js';
|
|
20
|
-
export { communitiesData } from './communities.js';
|
|
21
|
-
export { complexityData } from './complexity.js';
|
|
22
|
-
export { loadConfig } from './config.js';
|
|
23
|
-
export { EXTENSIONS, IGNORE_DIRS } from './constants.js';
|
|
24
|
-
export { findCycles } from './cycles.js';
|
|
25
|
-
export { dataflowData } from './dataflow.js';
|
|
26
|
-
export {
|
|
27
|
-
buildEmbeddings,
|
|
28
|
-
hybridSearchData,
|
|
29
|
-
multiSearchData,
|
|
30
|
-
searchData,
|
|
31
|
-
} from './embeddings/index.js';
|
|
32
|
-
export {
|
|
33
|
-
AnalysisError,
|
|
34
|
-
BoundaryError,
|
|
35
|
-
CodegraphError,
|
|
36
|
-
ConfigError,
|
|
37
|
-
DbError,
|
|
38
|
-
EngineError,
|
|
39
|
-
ParseError,
|
|
40
|
-
ResolutionError,
|
|
41
|
-
} from './errors.js';
|
|
42
|
-
export { exportDOT, exportJSON, exportMermaid } from './export.js';
|
|
43
|
-
export { flowData, listEntryPointsData } from './flow.js';
|
|
44
|
-
export { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND } from './kinds.js';
|
|
45
|
-
export { manifestoData } from './manifesto.js';
|
|
46
|
-
export { ownersData } from './owners.js';
|
|
12
|
+
export { buildGraph } from './domain/graph/builder.js';
|
|
13
|
+
export { findCycles } from './domain/graph/cycles.js';
|
|
47
14
|
export {
|
|
15
|
+
briefData,
|
|
48
16
|
childrenData,
|
|
49
17
|
contextData,
|
|
50
18
|
diffImpactData,
|
|
@@ -60,7 +28,40 @@ export {
|
|
|
60
28
|
rolesData,
|
|
61
29
|
statsData,
|
|
62
30
|
whereData,
|
|
63
|
-
} from './queries.js';
|
|
64
|
-
export {
|
|
65
|
-
|
|
66
|
-
|
|
31
|
+
} from './domain/queries.js';
|
|
32
|
+
export {
|
|
33
|
+
buildEmbeddings,
|
|
34
|
+
hybridSearchData,
|
|
35
|
+
multiSearchData,
|
|
36
|
+
searchData,
|
|
37
|
+
} from './domain/search/index.js';
|
|
38
|
+
export { astQueryData } from './features/ast.js';
|
|
39
|
+
export { auditData } from './features/audit.js';
|
|
40
|
+
export { batchData } from './features/batch.js';
|
|
41
|
+
export { branchCompareData } from './features/branch-compare.js';
|
|
42
|
+
export { cfgData } from './features/cfg.js';
|
|
43
|
+
export { checkData } from './features/check.js';
|
|
44
|
+
export { coChangeData } from './features/cochange.js';
|
|
45
|
+
export { communitiesData } from './features/communities.js';
|
|
46
|
+
export { complexityData } from './features/complexity.js';
|
|
47
|
+
export { dataflowData } from './features/dataflow.js';
|
|
48
|
+
export { exportDOT, exportJSON, exportMermaid } from './features/export.js';
|
|
49
|
+
export { flowData, listEntryPointsData } from './features/flow.js';
|
|
50
|
+
export { manifestoData } from './features/manifesto.js';
|
|
51
|
+
export { ownersData } from './features/owners.js';
|
|
52
|
+
export { sequenceData } from './features/sequence.js';
|
|
53
|
+
export { hotspotsData, moduleBoundariesData, structureData } from './features/structure.js';
|
|
54
|
+
export { triageData } from './features/triage.js';
|
|
55
|
+
export { loadConfig } from './infrastructure/config.js';
|
|
56
|
+
export { EXTENSIONS, IGNORE_DIRS } from './shared/constants.js';
|
|
57
|
+
export {
|
|
58
|
+
AnalysisError,
|
|
59
|
+
BoundaryError,
|
|
60
|
+
CodegraphError,
|
|
61
|
+
ConfigError,
|
|
62
|
+
DbError,
|
|
63
|
+
EngineError,
|
|
64
|
+
ParseError,
|
|
65
|
+
ResolutionError,
|
|
66
|
+
} from './shared/errors.js';
|
|
67
|
+
export { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND } from './shared/kinds.js';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { createRequire } from 'node:module';
|
|
10
10
|
import os from 'node:os';
|
|
11
|
-
import { EngineError } from '
|
|
11
|
+
import { EngineError } from '../shared/errors.js';
|
|
12
12
|
|
|
13
13
|
let _cached; // undefined = not yet tried, null = failed, object = module
|
|
14
14
|
let _loadError = null;
|
package/src/mcp/middleware.js
CHANGED
package/src/mcp/server.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
|
-
import { findDbPath } from '../db.js';
|
|
10
|
-
import { CodegraphError, ConfigError } from '../errors.js';
|
|
11
|
-
import { MCP_MAX_LIMIT } from '../paginate.js';
|
|
9
|
+
import { findDbPath } from '../db/index.js';
|
|
10
|
+
import { CodegraphError, ConfigError } from '../shared/errors.js';
|
|
11
|
+
import { MCP_MAX_LIMIT } from '../shared/paginate.js';
|
|
12
12
|
import { buildToolList } from './tool-registry.js';
|
|
13
13
|
import { TOOL_HANDLERS } from './tools/index.js';
|
|
14
14
|
|
|
@@ -21,45 +21,84 @@ import { TOOL_HANDLERS } from './tools/index.js';
|
|
|
21
21
|
* @param {boolean} [options.multiRepo] - Enable multi-repo access (default: false)
|
|
22
22
|
* @param {string[]} [options.allowedRepos] - Restrict access to these repo names only
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
const { allowedRepos } = options;
|
|
26
|
-
const multiRepo = options.multiRepo || !!allowedRepos;
|
|
27
|
-
let Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema;
|
|
24
|
+
async function loadMCPSdk() {
|
|
28
25
|
try {
|
|
29
26
|
const sdk = await import('@modelcontextprotocol/sdk/server/index.js');
|
|
30
|
-
Server = sdk.Server;
|
|
31
27
|
const transport = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
32
|
-
StdioServerTransport = transport.StdioServerTransport;
|
|
33
28
|
const types = await import('@modelcontextprotocol/sdk/types.js');
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
return {
|
|
30
|
+
Server: sdk.Server,
|
|
31
|
+
StdioServerTransport: transport.StdioServerTransport,
|
|
32
|
+
ListToolsRequestSchema: types.ListToolsRequestSchema,
|
|
33
|
+
CallToolRequestSchema: types.CallToolRequestSchema,
|
|
34
|
+
};
|
|
36
35
|
} catch {
|
|
37
36
|
throw new ConfigError(
|
|
38
37
|
'MCP server requires @modelcontextprotocol/sdk.\nInstall it with: npm install @modelcontextprotocol/sdk',
|
|
39
38
|
);
|
|
40
39
|
}
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
44
|
-
// are still loading. These are lazy-loaded on the first tool call
|
|
45
|
-
// and cached for subsequent calls.
|
|
42
|
+
function createLazyLoaders() {
|
|
46
43
|
let _queries;
|
|
47
44
|
let _Database;
|
|
45
|
+
return {
|
|
46
|
+
async getQueries() {
|
|
47
|
+
if (!_queries) _queries = await import('../domain/queries.js');
|
|
48
|
+
return _queries;
|
|
49
|
+
},
|
|
50
|
+
getDatabase() {
|
|
51
|
+
if (!_Database) {
|
|
52
|
+
const require = createRequire(import.meta.url);
|
|
53
|
+
_Database = require('better-sqlite3');
|
|
54
|
+
}
|
|
55
|
+
return _Database;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
async function resolveDbPath(customDbPath, args, allowedRepos) {
|
|
61
|
+
let dbPath = customDbPath || undefined;
|
|
62
|
+
if (args.repo) {
|
|
63
|
+
if (allowedRepos && !allowedRepos.includes(args.repo)) {
|
|
64
|
+
throw new ConfigError(`Repository "${args.repo}" is not in the allowed repos list.`);
|
|
52
65
|
}
|
|
53
|
-
|
|
66
|
+
const { resolveRepoDbPath } = await import('../infrastructure/registry.js');
|
|
67
|
+
const resolved = resolveRepoDbPath(args.repo);
|
|
68
|
+
if (!resolved)
|
|
69
|
+
throw new ConfigError(
|
|
70
|
+
`Repository "${args.repo}" not found in registry or its database is missing.`,
|
|
71
|
+
);
|
|
72
|
+
dbPath = resolved;
|
|
54
73
|
}
|
|
74
|
+
return dbPath;
|
|
75
|
+
}
|
|
55
76
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
function validateMultiRepoAccess(multiRepo, name, args) {
|
|
78
|
+
if (!multiRepo && args.repo) {
|
|
79
|
+
throw new ConfigError(
|
|
80
|
+
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (!multiRepo && name === 'list_repos') {
|
|
84
|
+
throw new ConfigError(
|
|
85
|
+
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.',
|
|
86
|
+
);
|
|
62
87
|
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function startMCPServer(customDbPath, options = {}) {
|
|
91
|
+
const { allowedRepos } = options;
|
|
92
|
+
const multiRepo = options.multiRepo || !!allowedRepos;
|
|
93
|
+
|
|
94
|
+
const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
|
|
95
|
+
await loadMCPSdk();
|
|
96
|
+
|
|
97
|
+
// Connect transport FIRST so the server can receive the client's
|
|
98
|
+
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
99
|
+
// are still loading. These are lazy-loaded on the first tool call
|
|
100
|
+
// and cached for subsequent calls.
|
|
101
|
+
const { getQueries, getDatabase } = createLazyLoaders();
|
|
63
102
|
|
|
64
103
|
const server = new Server(
|
|
65
104
|
{ name: 'codegraph', version: '1.0.0' },
|
|
@@ -73,47 +112,17 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
73
112
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
74
113
|
const { name, arguments: args } = request.params;
|
|
75
114
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
if (!multiRepo && name === 'list_repos') {
|
|
82
|
-
throw new ConfigError(
|
|
83
|
-
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.',
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let dbPath = customDbPath || undefined;
|
|
88
|
-
if (args.repo) {
|
|
89
|
-
if (allowedRepos && !allowedRepos.includes(args.repo)) {
|
|
90
|
-
throw new ConfigError(`Repository "${args.repo}" is not in the allowed repos list.`);
|
|
91
|
-
}
|
|
92
|
-
const { resolveRepoDbPath } = await import('../registry.js');
|
|
93
|
-
const resolved = resolveRepoDbPath(args.repo);
|
|
94
|
-
if (!resolved)
|
|
95
|
-
throw new ConfigError(
|
|
96
|
-
`Repository "${args.repo}" not found in registry or its database is missing.`,
|
|
97
|
-
);
|
|
98
|
-
dbPath = resolved;
|
|
99
|
-
}
|
|
115
|
+
validateMultiRepoAccess(multiRepo, name, args);
|
|
116
|
+
const dbPath = await resolveDbPath(customDbPath, args, allowedRepos);
|
|
100
117
|
|
|
101
118
|
const toolEntry = TOOL_HANDLERS.get(name);
|
|
102
119
|
if (!toolEntry) {
|
|
103
120
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
104
121
|
}
|
|
105
122
|
|
|
106
|
-
const ctx = {
|
|
107
|
-
dbPath,
|
|
108
|
-
getQueries,
|
|
109
|
-
getDatabase,
|
|
110
|
-
findDbPath,
|
|
111
|
-
allowedRepos,
|
|
112
|
-
MCP_MAX_LIMIT,
|
|
113
|
-
};
|
|
114
|
-
|
|
123
|
+
const ctx = { dbPath, getQueries, getDatabase, findDbPath, allowedRepos, MCP_MAX_LIMIT };
|
|
115
124
|
const result = await toolEntry.handler(args, ctx);
|
|
116
|
-
if (result?.content) return result;
|
|
125
|
+
if (result?.content) return result;
|
|
117
126
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
118
127
|
} catch (err) {
|
|
119
128
|
const code = err instanceof CodegraphError ? err.code : 'UNKNOWN_ERROR';
|
package/src/mcp/tool-registry.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Owns BASE_TOOLS, LIST_REPOS_TOOL, buildToolList(), and the backward-compatible TOOLS export.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND, VALID_ROLES } from '../domain/queries.js';
|
|
8
|
+
import { AST_NODE_KINDS } from '../features/ast.js';
|
|
9
9
|
|
|
10
10
|
const REPO_PROP = {
|
|
11
11
|
repo: {
|
|
@@ -99,6 +99,19 @@ const BASE_TOOLS = [
|
|
|
99
99
|
required: ['file'],
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
|
+
{
|
|
103
|
+
name: 'brief',
|
|
104
|
+
description:
|
|
105
|
+
'Token-efficient file summary: symbols with roles and transitive caller counts, importer counts, and file risk tier (high/medium/low). Designed for context injection.',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
file: { type: 'string', description: 'File path (partial match supported)' },
|
|
110
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
111
|
+
},
|
|
112
|
+
required: ['file'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
102
115
|
{
|
|
103
116
|
name: 'file_exports',
|
|
104
117
|
description:
|
|
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
|
|
|
3
3
|
export const name = 'ast_query';
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
|
-
const { astQueryData } = await import('../../ast.js');
|
|
6
|
+
const { astQueryData } = await import('../../features/ast.js');
|
|
7
7
|
return astQueryData(args.pattern, ctx.dbPath, {
|
|
8
8
|
kind: args.kind,
|
|
9
9
|
file: args.file,
|
package/src/mcp/tools/audit.js
CHANGED
|
@@ -11,7 +11,7 @@ export async function handler(args, ctx) {
|
|
|
11
11
|
offset: effectiveOffset(args),
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
-
const { auditData } = await import('../../audit.js');
|
|
14
|
+
const { auditData } = await import('../../features/audit.js');
|
|
15
15
|
return auditData(args.target, ctx.dbPath, {
|
|
16
16
|
depth: args.depth,
|
|
17
17
|
file: args.file,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const name = 'batch_query';
|
|
2
2
|
|
|
3
3
|
export async function handler(args, ctx) {
|
|
4
|
-
const { batchData } = await import('../../batch.js');
|
|
4
|
+
const { batchData } = await import('../../features/batch.js');
|
|
5
5
|
return batchData(args.command, args.targets, ctx.dbPath, {
|
|
6
6
|
depth: args.depth,
|
|
7
7
|
file: args.file,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export const name = 'branch_compare';
|
|
2
2
|
|
|
3
3
|
export async function handler(args, _ctx) {
|
|
4
|
-
const { branchCompareData, branchCompareMermaid } = await import(
|
|
4
|
+
const { branchCompareData, branchCompareMermaid } = await import(
|
|
5
|
+
'../../features/branch-compare.js'
|
|
6
|
+
);
|
|
5
7
|
const bcData = await branchCompareData(args.base, args.target, {
|
|
6
8
|
depth: args.depth,
|
|
7
9
|
noTests: args.no_tests,
|
package/src/mcp/tools/cfg.js
CHANGED
|
@@ -3,7 +3,7 @@ import { effectiveOffset, MCP_DEFAULTS } from '../middleware.js';
|
|
|
3
3
|
export const name = 'cfg';
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
|
-
const { cfgData, cfgToDOT, cfgToMermaid } = await import('../../cfg.js');
|
|
6
|
+
const { cfgData, cfgToDOT, cfgToMermaid } = await import('../../features/cfg.js');
|
|
7
7
|
const cfgResult = cfgData(args.name, ctx.dbPath, {
|
|
8
8
|
file: args.file,
|
|
9
9
|
kind: args.kind,
|
package/src/mcp/tools/check.js
CHANGED
|
@@ -6,7 +6,7 @@ export async function handler(args, ctx) {
|
|
|
6
6
|
const isDiffMode = args.ref || args.staged;
|
|
7
7
|
|
|
8
8
|
if (!isDiffMode && !args.rules) {
|
|
9
|
-
const { manifestoData } = await import('../../manifesto.js');
|
|
9
|
+
const { manifestoData } = await import('../../features/manifesto.js');
|
|
10
10
|
return manifestoData(ctx.dbPath, {
|
|
11
11
|
file: args.file,
|
|
12
12
|
noTests: args.no_tests,
|
|
@@ -16,7 +16,7 @@ export async function handler(args, ctx) {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { checkData } = await import('../../check.js');
|
|
19
|
+
const { checkData } = await import('../../features/check.js');
|
|
20
20
|
const checkResult = checkData(ctx.dbPath, {
|
|
21
21
|
ref: args.ref,
|
|
22
22
|
staged: args.staged,
|
|
@@ -29,7 +29,7 @@ export async function handler(args, ctx) {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
if (args.rules) {
|
|
32
|
-
const { manifestoData } = await import('../../manifesto.js');
|
|
32
|
+
const { manifestoData } = await import('../../features/manifesto.js');
|
|
33
33
|
const manifestoResult = manifestoData(ctx.dbPath, {
|
|
34
34
|
file: args.file,
|
|
35
35
|
noTests: args.no_tests,
|
|
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
|
|
|
3
3
|
export const name = 'co_changes';
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
|
-
const { coChangeData, coChangeTopData } = await import('../../cochange.js');
|
|
6
|
+
const { coChangeData, coChangeTopData } = await import('../../features/cochange.js');
|
|
7
7
|
return args.file
|
|
8
8
|
? coChangeData(args.file, ctx.dbPath, {
|
|
9
9
|
limit: effectiveLimit(args, name),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const name = 'code_owners';
|
|
2
2
|
|
|
3
3
|
export async function handler(args, ctx) {
|
|
4
|
-
const { ownersData } = await import('../../owners.js');
|
|
4
|
+
const { ownersData } = await import('../../features/owners.js');
|
|
5
5
|
return ownersData(ctx.dbPath, {
|
|
6
6
|
file: args.file,
|
|
7
7
|
owner: args.owner,
|
|
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
|
|
|
3
3
|
export const name = 'communities';
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
|
-
const { communitiesData } = await import('../../communities.js');
|
|
6
|
+
const { communitiesData } = await import('../../features/communities.js');
|
|
7
7
|
return communitiesData(ctx.dbPath, {
|
|
8
8
|
functions: args.functions,
|
|
9
9
|
resolution: args.resolution,
|
|
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
|
|
|
3
3
|
export const name = 'complexity';
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
|
-
const { complexityData } = await import('../../complexity.js');
|
|
6
|
+
const { complexityData } = await import('../../features/complexity.js');
|
|
7
7
|
return complexityData(ctx.dbPath, {
|
|
8
8
|
target: args.name,
|
|
9
9
|
file: args.file,
|
|
@@ -5,7 +5,7 @@ export const name = 'dataflow';
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
6
|
const dfMode = args.mode || 'edges';
|
|
7
7
|
if (dfMode === 'impact') {
|
|
8
|
-
const { dataflowImpactData } = await import('../../dataflow.js');
|
|
8
|
+
const { dataflowImpactData } = await import('../../features/dataflow.js');
|
|
9
9
|
return dataflowImpactData(args.name, ctx.dbPath, {
|
|
10
10
|
depth: args.depth,
|
|
11
11
|
file: args.file,
|
|
@@ -15,7 +15,7 @@ export async function handler(args, ctx) {
|
|
|
15
15
|
offset: effectiveOffset(args),
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
const { dataflowData } = await import('../../dataflow.js');
|
|
18
|
+
const { dataflowData } = await import('../../features/dataflow.js');
|
|
19
19
|
return dataflowData(args.name, ctx.dbPath, {
|
|
20
20
|
file: args.file,
|
|
21
21
|
kind: args.kind,
|
|
@@ -4,7 +4,7 @@ export const name = 'execution_flow';
|
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
6
|
if (args.list) {
|
|
7
|
-
const { listEntryPointsData } = await import('../../flow.js');
|
|
7
|
+
const { listEntryPointsData } = await import('../../features/flow.js');
|
|
8
8
|
return listEntryPointsData(ctx.dbPath, {
|
|
9
9
|
noTests: args.no_tests,
|
|
10
10
|
limit: effectiveLimit(args, name),
|
|
@@ -14,7 +14,7 @@ export async function handler(args, ctx) {
|
|
|
14
14
|
if (!args.name) {
|
|
15
15
|
return { error: 'Provide a name or set list=true' };
|
|
16
16
|
}
|
|
17
|
-
const { flowData } = await import('../../flow.js');
|
|
17
|
+
const { flowData } = await import('../../features/flow.js');
|
|
18
18
|
return flowData(args.name, ctx.dbPath, {
|
|
19
19
|
depth: args.depth,
|
|
20
20
|
file: args.file,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { findDbPath } from '../../db.js';
|
|
1
|
+
import { findDbPath } from '../../db/index.js';
|
|
2
2
|
import { effectiveOffset, MCP_DEFAULTS, MCP_MAX_LIMIT } from '../middleware.js';
|
|
3
3
|
|
|
4
4
|
export const name = 'export_graph';
|
|
5
5
|
|
|
6
6
|
export async function handler(args, ctx) {
|
|
7
7
|
const { exportDOT, exportGraphML, exportGraphSON, exportJSON, exportMermaid, exportNeo4jCSV } =
|
|
8
|
-
await import('../../export.js');
|
|
8
|
+
await import('../../features/export.js');
|
|
9
9
|
const Database = ctx.getDatabase();
|
|
10
10
|
const db = new Database(findDbPath(ctx.dbPath), { readonly: true });
|
|
11
11
|
const fileLevel = args.file_level !== false;
|
package/src/mcp/tools/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as astQuery from './ast-query.js';
|
|
|
6
6
|
import * as audit from './audit.js';
|
|
7
7
|
import * as batchQuery from './batch-query.js';
|
|
8
8
|
import * as branchCompare from './branch-compare.js';
|
|
9
|
+
import * as brief from './brief.js';
|
|
9
10
|
import * as cfg from './cfg.js';
|
|
10
11
|
import * as check from './check.js';
|
|
11
12
|
import * as coChanges from './co-changes.js';
|
|
@@ -67,5 +68,6 @@ export const TOOL_HANDLERS = new Map([
|
|
|
67
68
|
[dataflow.name, dataflow],
|
|
68
69
|
[check.name, check],
|
|
69
70
|
[astQuery.name, astQuery],
|
|
71
|
+
[brief.name, brief],
|
|
70
72
|
[listRepos.name, listRepos],
|
|
71
73
|
]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const name = 'list_repos';
|
|
2
2
|
|
|
3
3
|
export async function handler(_args, ctx) {
|
|
4
|
-
const { listRepos, pruneRegistry } = await import('../../registry.js');
|
|
4
|
+
const { listRepos, pruneRegistry } = await import('../../infrastructure/registry.js');
|
|
5
5
|
pruneRegistry();
|
|
6
6
|
let repos = listRepos();
|
|
7
7
|
if (ctx.allowedRepos) {
|
|
@@ -3,7 +3,7 @@ import { effectiveOffset, MCP_DEFAULTS } from '../middleware.js';
|
|
|
3
3
|
export const name = 'sequence';
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
|
-
const { sequenceData, sequenceToMermaid } = await import('../../sequence.js');
|
|
6
|
+
const { sequenceData, sequenceToMermaid } = await import('../../features/sequence.js');
|
|
7
7
|
const seqResult = sequenceData(args.name, ctx.dbPath, {
|
|
8
8
|
depth: args.depth,
|
|
9
9
|
file: args.file,
|
|
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
|
|
|
3
3
|
export const name = 'structure';
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
|
-
const { structureData } = await import('../../structure.js');
|
|
6
|
+
const { structureData } = await import('../../features/structure.js');
|
|
7
7
|
return structureData(ctx.dbPath, {
|
|
8
8
|
directory: args.directory,
|
|
9
9
|
depth: args.depth,
|
package/src/mcp/tools/triage.js
CHANGED
|
@@ -4,7 +4,7 @@ export const name = 'triage';
|
|
|
4
4
|
|
|
5
5
|
export async function handler(args, ctx) {
|
|
6
6
|
if (args.level === 'file' || args.level === 'directory') {
|
|
7
|
-
const { hotspotsData } = await import('../../structure.js');
|
|
7
|
+
const { hotspotsData } = await import('../../features/structure.js');
|
|
8
8
|
const TRIAGE_TO_HOTSPOT = {
|
|
9
9
|
risk: 'fan-in',
|
|
10
10
|
complexity: 'density',
|
|
@@ -20,7 +20,7 @@ export async function handler(args, ctx) {
|
|
|
20
20
|
noTests: args.no_tests,
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
const { triageData } = await import('../../triage.js');
|
|
23
|
+
const { triageData } = await import('../../features/triage.js');
|
|
24
24
|
return triageData(ctx.dbPath, {
|
|
25
25
|
sort: args.sort,
|
|
26
26
|
minScore: args.min_score,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { kindIcon } from '../domain/queries.js';
|
|
2
|
+
import { auditData } from '../features/audit.js';
|
|
2
3
|
import { outputResult } from '../infrastructure/result-formatter.js';
|
|
3
|
-
import { kindIcon } from '../queries.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* CLI formatter for the audit command.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { kindIcon } from '../domain/queries.js';
|
|
2
|
+
import { branchCompareData, branchCompareMermaid } from '../features/branch-compare.js';
|
|
2
3
|
import { outputResult } from '../infrastructure/result-formatter.js';
|
|
3
|
-
import { kindIcon } from '../queries.js';
|
|
4
4
|
|
|
5
5
|
// ─── Text Formatting ────────────────────────────────────────────────────
|
|
6
6
|
|