@optave/codegraph 3.1.2 → 3.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.
- package/README.md +19 -21
- package/package.json +10 -7
- package/src/analysis/context.js +408 -0
- package/src/analysis/dependencies.js +341 -0
- package/src/analysis/exports.js +130 -0
- package/src/analysis/impact.js +463 -0
- package/src/analysis/module-map.js +322 -0
- package/src/analysis/roles.js +45 -0
- package/src/analysis/symbol-lookup.js +232 -0
- package/src/ast-analysis/shared.js +5 -4
- package/src/batch.js +2 -1
- package/src/builder/context.js +85 -0
- package/src/builder/helpers.js +218 -0
- package/src/builder/incremental.js +178 -0
- package/src/builder/pipeline.js +130 -0
- package/src/builder/stages/build-edges.js +297 -0
- package/src/builder/stages/build-structure.js +113 -0
- package/src/builder/stages/collect-files.js +44 -0
- package/src/builder/stages/detect-changes.js +413 -0
- package/src/builder/stages/finalize.js +139 -0
- package/src/builder/stages/insert-nodes.js +195 -0
- package/src/builder/stages/parse-files.js +28 -0
- package/src/builder/stages/resolve-imports.js +143 -0
- package/src/builder/stages/run-analyses.js +44 -0
- package/src/builder.js +10 -1472
- package/src/cfg.js +1 -2
- package/src/cli/commands/ast.js +26 -0
- package/src/cli/commands/audit.js +46 -0
- package/src/cli/commands/batch.js +68 -0
- package/src/cli/commands/branch-compare.js +21 -0
- package/src/cli/commands/build.js +26 -0
- package/src/cli/commands/cfg.js +30 -0
- package/src/cli/commands/check.js +79 -0
- package/src/cli/commands/children.js +31 -0
- package/src/cli/commands/co-change.js +65 -0
- package/src/cli/commands/communities.js +23 -0
- package/src/cli/commands/complexity.js +45 -0
- package/src/cli/commands/context.js +34 -0
- package/src/cli/commands/cycles.js +28 -0
- package/src/cli/commands/dataflow.js +32 -0
- package/src/cli/commands/deps.js +16 -0
- package/src/cli/commands/diff-impact.js +30 -0
- package/src/cli/commands/embed.js +30 -0
- package/src/cli/commands/export.js +75 -0
- package/src/cli/commands/exports.js +18 -0
- package/src/cli/commands/flow.js +36 -0
- package/src/cli/commands/fn-impact.js +30 -0
- package/src/cli/commands/impact.js +16 -0
- package/src/cli/commands/info.js +76 -0
- package/src/cli/commands/map.js +19 -0
- package/src/cli/commands/mcp.js +18 -0
- package/src/cli/commands/models.js +19 -0
- package/src/cli/commands/owners.js +25 -0
- package/src/cli/commands/path.js +36 -0
- package/src/cli/commands/plot.js +80 -0
- package/src/cli/commands/query.js +49 -0
- package/src/cli/commands/registry.js +100 -0
- package/src/cli/commands/roles.js +34 -0
- package/src/cli/commands/search.js +42 -0
- package/src/cli/commands/sequence.js +32 -0
- package/src/cli/commands/snapshot.js +61 -0
- package/src/cli/commands/stats.js +15 -0
- package/src/cli/commands/structure.js +32 -0
- package/src/cli/commands/triage.js +78 -0
- package/src/cli/commands/watch.js +12 -0
- package/src/cli/commands/where.js +24 -0
- package/src/cli/index.js +118 -0
- package/src/cli/shared/options.js +39 -0
- package/src/cli/shared/output.js +1 -0
- package/src/cli.js +11 -1514
- package/src/commands/check.js +5 -5
- package/src/commands/manifesto.js +3 -3
- package/src/commands/structure.js +1 -1
- package/src/communities.js +15 -87
- package/src/complexity.js +1 -1
- package/src/cycles.js +30 -85
- package/src/dataflow.js +1 -2
- package/src/db/connection.js +4 -4
- package/src/db/migrations.js +41 -0
- package/src/db/query-builder.js +6 -5
- package/src/db/repository/base.js +201 -0
- package/src/db/repository/cached-stmt.js +19 -0
- package/src/db/repository/cfg.js +27 -38
- package/src/db/repository/cochange.js +16 -3
- package/src/db/repository/complexity.js +11 -6
- package/src/db/repository/dataflow.js +6 -1
- package/src/db/repository/edges.js +120 -98
- package/src/db/repository/embeddings.js +14 -3
- package/src/db/repository/graph-read.js +32 -9
- package/src/db/repository/in-memory-repository.js +584 -0
- package/src/db/repository/index.js +6 -1
- package/src/db/repository/nodes.js +110 -40
- package/src/db/repository/sqlite-repository.js +219 -0
- package/src/db.js +5 -0
- package/src/embeddings/generator.js +163 -0
- package/src/embeddings/index.js +13 -0
- package/src/embeddings/models.js +218 -0
- package/src/embeddings/search/cli-formatter.js +151 -0
- package/src/embeddings/search/filters.js +46 -0
- package/src/embeddings/search/hybrid.js +121 -0
- package/src/embeddings/search/keyword.js +68 -0
- package/src/embeddings/search/prepare.js +66 -0
- package/src/embeddings/search/semantic.js +145 -0
- package/src/embeddings/stores/fts5.js +27 -0
- package/src/embeddings/stores/sqlite-blob.js +24 -0
- package/src/embeddings/strategies/source.js +14 -0
- package/src/embeddings/strategies/structured.js +43 -0
- package/src/embeddings/strategies/text-utils.js +43 -0
- package/src/errors.js +78 -0
- package/src/export.js +217 -520
- package/src/extractors/csharp.js +10 -2
- package/src/extractors/go.js +3 -1
- package/src/extractors/helpers.js +71 -0
- package/src/extractors/java.js +9 -2
- package/src/extractors/javascript.js +38 -1
- package/src/extractors/php.js +3 -1
- package/src/extractors/python.js +14 -3
- package/src/extractors/rust.js +3 -1
- package/src/graph/algorithms/bfs.js +49 -0
- package/src/graph/algorithms/centrality.js +16 -0
- package/src/graph/algorithms/index.js +5 -0
- package/src/graph/algorithms/louvain.js +26 -0
- package/src/graph/algorithms/shortest-path.js +41 -0
- package/src/graph/algorithms/tarjan.js +49 -0
- package/src/graph/builders/dependency.js +91 -0
- package/src/graph/builders/index.js +3 -0
- package/src/graph/builders/structure.js +40 -0
- package/src/graph/builders/temporal.js +33 -0
- package/src/graph/classifiers/index.js +2 -0
- package/src/graph/classifiers/risk.js +85 -0
- package/src/graph/classifiers/roles.js +64 -0
- package/src/graph/index.js +13 -0
- package/src/graph/model.js +230 -0
- package/src/index.js +33 -204
- package/src/infrastructure/result-formatter.js +2 -21
- package/src/mcp/index.js +2 -0
- package/src/mcp/middleware.js +26 -0
- package/src/mcp/server.js +128 -0
- package/src/mcp/tool-registry.js +801 -0
- package/src/mcp/tools/ast-query.js +14 -0
- package/src/mcp/tools/audit.js +21 -0
- package/src/mcp/tools/batch-query.js +11 -0
- package/src/mcp/tools/branch-compare.js +10 -0
- package/src/mcp/tools/cfg.js +21 -0
- package/src/mcp/tools/check.js +43 -0
- package/src/mcp/tools/co-changes.js +20 -0
- package/src/mcp/tools/code-owners.js +12 -0
- package/src/mcp/tools/communities.js +15 -0
- package/src/mcp/tools/complexity.js +18 -0
- package/src/mcp/tools/context.js +17 -0
- package/src/mcp/tools/dataflow.js +26 -0
- package/src/mcp/tools/diff-impact.js +24 -0
- package/src/mcp/tools/execution-flow.js +26 -0
- package/src/mcp/tools/export-graph.js +57 -0
- package/src/mcp/tools/file-deps.js +12 -0
- package/src/mcp/tools/file-exports.js +13 -0
- package/src/mcp/tools/find-cycles.js +15 -0
- package/src/mcp/tools/fn-impact.js +15 -0
- package/src/mcp/tools/impact-analysis.js +12 -0
- package/src/mcp/tools/index.js +71 -0
- package/src/mcp/tools/list-functions.js +14 -0
- package/src/mcp/tools/list-repos.js +11 -0
- package/src/mcp/tools/module-map.js +6 -0
- package/src/mcp/tools/node-roles.js +14 -0
- package/src/mcp/tools/path.js +12 -0
- package/src/mcp/tools/query.js +30 -0
- package/src/mcp/tools/semantic-search.js +65 -0
- package/src/mcp/tools/sequence.js +17 -0
- package/src/mcp/tools/structure.js +15 -0
- package/src/mcp/tools/symbol-children.js +14 -0
- package/src/mcp/tools/triage.js +35 -0
- package/src/mcp/tools/where.js +13 -0
- package/src/mcp.js +2 -1470
- package/src/native.js +34 -10
- package/src/parser.js +53 -2
- package/src/presentation/colors.js +44 -0
- package/src/presentation/export.js +444 -0
- package/src/presentation/result-formatter.js +21 -0
- package/src/presentation/sequence-renderer.js +43 -0
- package/src/presentation/table.js +47 -0
- package/src/presentation/viewer.js +634 -0
- package/src/queries.js +35 -2276
- package/src/resolve.js +1 -1
- package/src/sequence.js +2 -38
- package/src/shared/file-utils.js +153 -0
- package/src/shared/generators.js +125 -0
- package/src/shared/hierarchy.js +27 -0
- package/src/shared/normalize.js +59 -0
- package/src/snapshot.js +6 -5
- package/src/structure.js +15 -40
- package/src/triage.js +20 -72
- package/src/viewer.js +35 -656
- package/src/watcher.js +8 -148
- package/src/embedder.js +0 -1097
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { search } from '../../embeddings/index.js';
|
|
2
|
+
|
|
3
|
+
export const command = {
|
|
4
|
+
name: 'search <query>',
|
|
5
|
+
description: 'Semantic search: find functions by natural language description',
|
|
6
|
+
options: [
|
|
7
|
+
['-d, --db <path>', 'Path to graph.db'],
|
|
8
|
+
['-m, --model <name>', 'Override embedding model (auto-detects from DB)'],
|
|
9
|
+
['-n, --limit <number>', 'Max results', '15'],
|
|
10
|
+
['-T, --no-tests', 'Exclude test/spec files from results'],
|
|
11
|
+
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
12
|
+
['--min-score <score>', 'Minimum similarity threshold', '0.2'],
|
|
13
|
+
['-k, --kind <kind>', 'Filter by kind: function, method, class'],
|
|
14
|
+
['--file <pattern>', 'Filter by file path pattern'],
|
|
15
|
+
['--rrf-k <number>', 'RRF k parameter for multi-query ranking', '60'],
|
|
16
|
+
['--mode <mode>', 'Search mode: hybrid, semantic, keyword (default: hybrid)'],
|
|
17
|
+
['-j, --json', 'Output as JSON'],
|
|
18
|
+
['--offset <number>', 'Skip N results (default: 0)'],
|
|
19
|
+
['--ndjson', 'Newline-delimited JSON output'],
|
|
20
|
+
],
|
|
21
|
+
validate([_query], opts) {
|
|
22
|
+
const validModes = ['hybrid', 'semantic', 'keyword'];
|
|
23
|
+
if (opts.mode && !validModes.includes(opts.mode)) {
|
|
24
|
+
return `Invalid mode "${opts.mode}". Valid: ${validModes.join(', ')}`;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
async execute([query], opts, ctx) {
|
|
28
|
+
await search(query, opts.db, {
|
|
29
|
+
limit: parseInt(opts.limit, 10),
|
|
30
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
31
|
+
noTests: ctx.resolveNoTests(opts),
|
|
32
|
+
minScore: parseFloat(opts.minScore),
|
|
33
|
+
model: opts.model,
|
|
34
|
+
kind: opts.kind,
|
|
35
|
+
filePattern: opts.file,
|
|
36
|
+
rrfK: parseInt(opts.rrfK, 10),
|
|
37
|
+
mode: opts.mode,
|
|
38
|
+
json: opts.json,
|
|
39
|
+
ndjson: opts.ndjson,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EVERY_SYMBOL_KIND } from '../../queries.js';
|
|
2
|
+
|
|
3
|
+
export const command = {
|
|
4
|
+
name: 'sequence <name>',
|
|
5
|
+
description: 'Generate a Mermaid sequence diagram from call graph edges (participants = files)',
|
|
6
|
+
queryOpts: true,
|
|
7
|
+
options: [
|
|
8
|
+
['--depth <n>', 'Max forward traversal depth', '10'],
|
|
9
|
+
['--dataflow', 'Annotate with parameter names and return arrows from dataflow table'],
|
|
10
|
+
['-f, --file <path>', 'Scope to a specific file (partial match)'],
|
|
11
|
+
['-k, --kind <kind>', 'Filter by symbol kind'],
|
|
12
|
+
],
|
|
13
|
+
validate([_name], opts) {
|
|
14
|
+
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
15
|
+
return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
async execute([name], opts, ctx) {
|
|
19
|
+
const { sequence } = await import('../../commands/sequence.js');
|
|
20
|
+
sequence(name, opts.db, {
|
|
21
|
+
depth: parseInt(opts.depth, 10),
|
|
22
|
+
file: opts.file,
|
|
23
|
+
kind: opts.kind,
|
|
24
|
+
noTests: ctx.resolveNoTests(opts),
|
|
25
|
+
json: opts.json,
|
|
26
|
+
dataflow: opts.dataflow,
|
|
27
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
28
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
29
|
+
ndjson: opts.ndjson,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { snapshotDelete, snapshotList, snapshotRestore, snapshotSave } from '../../snapshot.js';
|
|
2
|
+
|
|
3
|
+
export const command = {
|
|
4
|
+
name: 'snapshot',
|
|
5
|
+
description: 'Save and restore graph database snapshots',
|
|
6
|
+
subcommands: [
|
|
7
|
+
{
|
|
8
|
+
name: 'save <name>',
|
|
9
|
+
description: 'Save a snapshot of the current graph database',
|
|
10
|
+
options: [
|
|
11
|
+
['-d, --db <path>', 'Path to graph.db'],
|
|
12
|
+
['--force', 'Overwrite existing snapshot'],
|
|
13
|
+
],
|
|
14
|
+
execute([name], opts, ctx) {
|
|
15
|
+
const result = snapshotSave(name, { dbPath: opts.db, force: opts.force });
|
|
16
|
+
console.log(`Snapshot saved: ${result.name} (${ctx.formatSize(result.size)})`);
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'restore <name>',
|
|
21
|
+
description: 'Restore a snapshot over the current graph database',
|
|
22
|
+
options: [['-d, --db <path>', 'Path to graph.db']],
|
|
23
|
+
execute([name], opts) {
|
|
24
|
+
snapshotRestore(name, { dbPath: opts.db });
|
|
25
|
+
console.log(`Snapshot "${name}" restored.`);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'list',
|
|
30
|
+
description: 'List all saved snapshots',
|
|
31
|
+
options: [
|
|
32
|
+
['-d, --db <path>', 'Path to graph.db'],
|
|
33
|
+
['-j, --json', 'Output as JSON'],
|
|
34
|
+
],
|
|
35
|
+
execute(_args, opts, ctx) {
|
|
36
|
+
const snapshots = snapshotList({ dbPath: opts.db });
|
|
37
|
+
if (opts.json) {
|
|
38
|
+
console.log(JSON.stringify(snapshots, null, 2));
|
|
39
|
+
} else if (snapshots.length === 0) {
|
|
40
|
+
console.log('No snapshots found.');
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`Snapshots (${snapshots.length}):\n`);
|
|
43
|
+
for (const s of snapshots) {
|
|
44
|
+
console.log(
|
|
45
|
+
` ${s.name.padEnd(30)} ${ctx.formatSize(s.size).padStart(10)} ${s.createdAt.toISOString()}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'delete <name>',
|
|
53
|
+
description: 'Delete a saved snapshot',
|
|
54
|
+
options: [['-d, --db <path>', 'Path to graph.db']],
|
|
55
|
+
execute([name], opts) {
|
|
56
|
+
snapshotDelete(name, { dbPath: opts.db });
|
|
57
|
+
console.log(`Snapshot "${name}" deleted.`);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { stats } from '../../queries-cli.js';
|
|
2
|
+
|
|
3
|
+
export const command = {
|
|
4
|
+
name: 'stats',
|
|
5
|
+
description: 'Show graph health overview: nodes, edges, languages, cycles, hotspots, embeddings',
|
|
6
|
+
options: [
|
|
7
|
+
['-d, --db <path>', 'Path to graph.db'],
|
|
8
|
+
['-T, --no-tests', 'Exclude test/spec files from results'],
|
|
9
|
+
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
10
|
+
['-j, --json', 'Output as JSON'],
|
|
11
|
+
],
|
|
12
|
+
async execute(_args, opts, ctx) {
|
|
13
|
+
await stats(opts.db, { noTests: ctx.resolveNoTests(opts), json: opts.json });
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const command = {
|
|
2
|
+
name: 'structure [dir]',
|
|
3
|
+
description:
|
|
4
|
+
'Show project directory structure with hierarchy, cohesion scores, and per-file metrics',
|
|
5
|
+
options: [
|
|
6
|
+
['-d, --db <path>', 'Path to graph.db'],
|
|
7
|
+
['--depth <n>', 'Max directory depth'],
|
|
8
|
+
['--sort <metric>', 'Sort by: cohesion | fan-in | fan-out | density | files', 'files'],
|
|
9
|
+
['--full', 'Show all files without limit'],
|
|
10
|
+
['-T, --no-tests', 'Exclude test/spec files'],
|
|
11
|
+
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
12
|
+
['-j, --json', 'Output as JSON'],
|
|
13
|
+
['--limit <number>', 'Max results to return'],
|
|
14
|
+
['--offset <number>', 'Skip N results (default: 0)'],
|
|
15
|
+
['--ndjson', 'Newline-delimited JSON output'],
|
|
16
|
+
],
|
|
17
|
+
async execute([dir], opts, ctx) {
|
|
18
|
+
const { structureData, formatStructure } = await import('../../commands/structure.js');
|
|
19
|
+
const data = structureData(opts.db, {
|
|
20
|
+
directory: dir,
|
|
21
|
+
depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
|
|
22
|
+
sort: opts.sort,
|
|
23
|
+
full: opts.full,
|
|
24
|
+
noTests: ctx.resolveNoTests(opts),
|
|
25
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
26
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
27
|
+
});
|
|
28
|
+
if (!ctx.outputResult(data, 'directories', opts)) {
|
|
29
|
+
console.log(formatStructure(data));
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ConfigError } from '../../errors.js';
|
|
2
|
+
import { EVERY_SYMBOL_KIND, VALID_ROLES } from '../../queries.js';
|
|
3
|
+
|
|
4
|
+
export const command = {
|
|
5
|
+
name: 'triage',
|
|
6
|
+
description:
|
|
7
|
+
'Ranked audit queue by composite risk score (connectivity + complexity + churn + role)',
|
|
8
|
+
options: [
|
|
9
|
+
['-d, --db <path>', 'Path to graph.db'],
|
|
10
|
+
['-n, --limit <number>', 'Max results to return', '20'],
|
|
11
|
+
[
|
|
12
|
+
'--level <level>',
|
|
13
|
+
'Granularity: function (default) | file | directory. File/directory level shows hotspots',
|
|
14
|
+
'function',
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
'--sort <metric>',
|
|
18
|
+
'Sort metric: risk | complexity | churn | fan-in | mi (function level); fan-in | fan-out | density | coupling (file/directory level)',
|
|
19
|
+
'risk',
|
|
20
|
+
],
|
|
21
|
+
['--min-score <score>', 'Only show symbols with risk score >= threshold'],
|
|
22
|
+
['--role <role>', 'Filter by role (entry, core, utility, adapter, leaf, dead)'],
|
|
23
|
+
['-f, --file <path>', 'Scope to a specific file (partial match)'],
|
|
24
|
+
['-k, --kind <kind>', 'Filter by symbol kind (function, method, class)'],
|
|
25
|
+
['-T, --no-tests', 'Exclude test/spec files from results'],
|
|
26
|
+
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
27
|
+
['-j, --json', 'Output as JSON'],
|
|
28
|
+
['--offset <number>', 'Skip N results (default: 0)'],
|
|
29
|
+
['--ndjson', 'Newline-delimited JSON output'],
|
|
30
|
+
['--weights <json>', 'Custom weights JSON (e.g. \'{"fanIn":1,"complexity":0}\')'],
|
|
31
|
+
],
|
|
32
|
+
async execute(_args, opts, ctx) {
|
|
33
|
+
if (opts.level === 'file' || opts.level === 'directory') {
|
|
34
|
+
const { hotspotsData, formatHotspots } = await import('../../commands/structure.js');
|
|
35
|
+
const metric = opts.sort === 'risk' ? 'fan-in' : opts.sort;
|
|
36
|
+
const data = hotspotsData(opts.db, {
|
|
37
|
+
metric,
|
|
38
|
+
level: opts.level,
|
|
39
|
+
limit: parseInt(opts.limit, 10),
|
|
40
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
41
|
+
noTests: ctx.resolveNoTests(opts),
|
|
42
|
+
});
|
|
43
|
+
if (!ctx.outputResult(data, 'hotspots', opts)) {
|
|
44
|
+
console.log(formatHotspots(data));
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
50
|
+
throw new ConfigError(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
53
|
+
throw new ConfigError(`Invalid role "${opts.role}". Valid: ${VALID_ROLES.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
let weights;
|
|
56
|
+
if (opts.weights) {
|
|
57
|
+
try {
|
|
58
|
+
weights = JSON.parse(opts.weights);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
throw new ConfigError('Invalid --weights JSON', { cause: err });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const { triage } = await import('../../commands/triage.js');
|
|
64
|
+
triage(opts.db, {
|
|
65
|
+
limit: parseInt(opts.limit, 10),
|
|
66
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
67
|
+
sort: opts.sort,
|
|
68
|
+
minScore: opts.minScore,
|
|
69
|
+
role: opts.role,
|
|
70
|
+
file: opts.file,
|
|
71
|
+
kind: opts.kind,
|
|
72
|
+
noTests: ctx.resolveNoTests(opts),
|
|
73
|
+
json: opts.json,
|
|
74
|
+
ndjson: opts.ndjson,
|
|
75
|
+
weights,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { watchProject } from '../../watcher.js';
|
|
3
|
+
|
|
4
|
+
export const command = {
|
|
5
|
+
name: 'watch [dir]',
|
|
6
|
+
description: 'Watch project for file changes and incrementally update the graph',
|
|
7
|
+
async execute([dir], _opts, ctx) {
|
|
8
|
+
const root = path.resolve(dir || '.');
|
|
9
|
+
const engine = ctx.program.opts().engine;
|
|
10
|
+
await watchProject(root, { engine });
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { where } from '../../queries-cli.js';
|
|
2
|
+
|
|
3
|
+
export const command = {
|
|
4
|
+
name: 'where [name]',
|
|
5
|
+
description: 'Find where a symbol is defined and used (minimal, fast lookup)',
|
|
6
|
+
queryOpts: true,
|
|
7
|
+
options: [['-f, --file <path>', 'File overview: list symbols, imports, exports']],
|
|
8
|
+
validate([name], opts) {
|
|
9
|
+
if (!name && !opts.file) {
|
|
10
|
+
return 'Provide a symbol name or use --file <path>';
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
execute([name], opts, ctx) {
|
|
14
|
+
const target = opts.file || name;
|
|
15
|
+
where(target, opts.db, {
|
|
16
|
+
file: !!opts.file,
|
|
17
|
+
noTests: ctx.resolveNoTests(opts),
|
|
18
|
+
json: opts.json,
|
|
19
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
20
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
21
|
+
ndjson: opts.ndjson,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
};
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { ConfigError } from '../errors.js';
|
|
6
|
+
import { setVerbose } from '../logger.js';
|
|
7
|
+
import { checkForUpdates, printUpdateNotification } from '../update-check.js';
|
|
8
|
+
import { applyQueryOpts, config, formatSize, resolveNoTests } from './shared/options.js';
|
|
9
|
+
import { outputResult } from './shared/output.js';
|
|
10
|
+
|
|
11
|
+
const __cliDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1'));
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__cliDir, '..', '..', 'package.json'), 'utf-8'));
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
program
|
|
16
|
+
.name('codegraph')
|
|
17
|
+
.description('Local code dependency graph tool')
|
|
18
|
+
.version(pkg.version)
|
|
19
|
+
.option('-v, --verbose', 'Enable verbose/debug output')
|
|
20
|
+
.option('--engine <engine>', 'Parser engine: native, wasm, or auto (default: auto)', 'auto')
|
|
21
|
+
.hook('preAction', (thisCommand) => {
|
|
22
|
+
const opts = thisCommand.opts();
|
|
23
|
+
if (opts.verbose) setVerbose(true);
|
|
24
|
+
})
|
|
25
|
+
.hook('postAction', async (_thisCommand, actionCommand) => {
|
|
26
|
+
const name = actionCommand.name();
|
|
27
|
+
if (name === 'mcp' || name === 'watch') return;
|
|
28
|
+
if (actionCommand.opts().json) return;
|
|
29
|
+
try {
|
|
30
|
+
const result = await checkForUpdates(pkg.version);
|
|
31
|
+
if (result) printUpdateNotification(result.current, result.latest);
|
|
32
|
+
} catch {
|
|
33
|
+
/* never break CLI */
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** Shared context passed to every command's execute(). */
|
|
38
|
+
const ctx = { config, resolveNoTests, formatSize, outputResult, program };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register a command definition onto a Commander parent.
|
|
42
|
+
*
|
|
43
|
+
* Command shape:
|
|
44
|
+
* { name, description, queryOpts?, options?, validate?, execute(args, opts, ctx), subcommands? }
|
|
45
|
+
*
|
|
46
|
+
* - `name` includes positional args, e.g. 'build [dir]' or 'path <from> <to>'
|
|
47
|
+
* - `queryOpts` (boolean) — if true, attaches shared query options
|
|
48
|
+
* - `options` — array of arrays passed to cmd.option(), e.g. [['--depth <n>', 'Max depth', '3']]
|
|
49
|
+
* - `validate(args, opts, ctx)` — return an error string to abort, or falsy to proceed
|
|
50
|
+
* - `execute(args, opts, ctx)` — the action handler
|
|
51
|
+
* - `subcommands` — nested command definitions (for groups like registry, snapshot)
|
|
52
|
+
*/
|
|
53
|
+
function registerCommand(parent, def) {
|
|
54
|
+
const cmd = parent.command(def.name).description(def.description);
|
|
55
|
+
|
|
56
|
+
if (def.queryOpts) applyQueryOpts(cmd);
|
|
57
|
+
|
|
58
|
+
for (const opt of def.options || []) {
|
|
59
|
+
cmd.option(...opt);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (def.execute) {
|
|
63
|
+
const argCount = (def.name.match(/<[^>]+>|\[[^\]]+\]/g) || []).length;
|
|
64
|
+
|
|
65
|
+
cmd.action((...actionArgs) => {
|
|
66
|
+
const args = actionArgs.slice(0, argCount);
|
|
67
|
+
const opts = actionArgs[argCount];
|
|
68
|
+
|
|
69
|
+
if (def.validate) {
|
|
70
|
+
const err = def.validate(args, opts, ctx);
|
|
71
|
+
if (err) {
|
|
72
|
+
throw new ConfigError(err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return def.execute(args, opts, ctx);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (def.subcommands) {
|
|
81
|
+
for (const sub of def.subcommands) {
|
|
82
|
+
registerCommand(cmd, sub);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return cmd;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Auto-discover and register all command modules from src/cli/commands/.
|
|
91
|
+
* Each module must export a `command` (single definition) or `commands` (array).
|
|
92
|
+
*/
|
|
93
|
+
async function discoverCommands() {
|
|
94
|
+
const commandsDir = path.join(__cliDir, 'commands');
|
|
95
|
+
const files = fs
|
|
96
|
+
.readdirSync(commandsDir)
|
|
97
|
+
.filter((f) => f.endsWith('.js'))
|
|
98
|
+
.sort();
|
|
99
|
+
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const mod = await import(pathToFileURL(path.join(commandsDir, file)).href);
|
|
102
|
+
if (mod.command) {
|
|
103
|
+
registerCommand(program, mod.command);
|
|
104
|
+
}
|
|
105
|
+
if (mod.commands) {
|
|
106
|
+
for (const def of mod.commands) {
|
|
107
|
+
registerCommand(program, def);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function run() {
|
|
114
|
+
await discoverCommands();
|
|
115
|
+
await program.parseAsync();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { ctx, program, registerCommand };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { loadConfig } from '../../config.js';
|
|
2
|
+
|
|
3
|
+
const config = loadConfig(process.cwd());
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Attach the common query options shared by most analysis commands.
|
|
7
|
+
* @param {import('commander').Command} cmd
|
|
8
|
+
* @returns {import('commander').Command}
|
|
9
|
+
*/
|
|
10
|
+
export function applyQueryOpts(cmd) {
|
|
11
|
+
return cmd
|
|
12
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
13
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
14
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
15
|
+
.option('-j, --json', 'Output as JSON')
|
|
16
|
+
.option('--limit <number>', 'Max results to return')
|
|
17
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
18
|
+
.option('--ndjson', 'Newline-delimited JSON output');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the effective noTests value: CLI flag > config > false.
|
|
23
|
+
* Commander sets opts.tests to false when --no-tests is passed.
|
|
24
|
+
* When --include-tests is passed, always return false (include tests).
|
|
25
|
+
* Otherwise, fall back to config.query.excludeTests.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveNoTests(opts) {
|
|
28
|
+
if (opts.includeTests) return false;
|
|
29
|
+
if (opts.tests === false) return true;
|
|
30
|
+
return config.query?.excludeTests || false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatSize(bytes) {
|
|
34
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
35
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
36
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { config };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { outputResult } from '../../infrastructure/result-formatter.js';
|