@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,3 +1,5 @@
|
|
|
1
|
+
import { collectFile } from '../../db/query-builder.js';
|
|
2
|
+
|
|
1
3
|
export const command = {
|
|
2
4
|
name: 'owners [target]',
|
|
3
5
|
description: 'Show CODEOWNERS mapping for files and functions',
|
|
@@ -5,18 +7,18 @@ export const command = {
|
|
|
5
7
|
['-d, --db <path>', 'Path to graph.db'],
|
|
6
8
|
['--owner <owner>', 'Filter to a specific owner'],
|
|
7
9
|
['--boundary', 'Show cross-owner boundary edges'],
|
|
8
|
-
['-f, --file <path>', 'Scope to a specific file'],
|
|
10
|
+
['-f, --file <path>', 'Scope to a specific file (repeatable)', collectFile],
|
|
9
11
|
['-k, --kind <kind>', 'Filter by symbol kind'],
|
|
10
12
|
['-T, --no-tests', 'Exclude test/spec files'],
|
|
11
13
|
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
12
14
|
['-j, --json', 'Output as JSON'],
|
|
13
15
|
],
|
|
14
16
|
async execute([target], opts, ctx) {
|
|
15
|
-
const { owners } = await import('../../
|
|
17
|
+
const { owners } = await import('../../presentation/owners.js');
|
|
16
18
|
owners(opts.db, {
|
|
17
19
|
owner: opts.owner,
|
|
18
20
|
boundary: opts.boundary,
|
|
19
|
-
file: opts.file
|
|
21
|
+
file: opts.file && opts.file.length > 0 ? opts.file : target,
|
|
20
22
|
kind: opts.kind,
|
|
21
23
|
noTests: ctx.resolveNoTests(opts),
|
|
22
24
|
json: opts.json,
|
package/src/cli/commands/path.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EVERY_SYMBOL_KIND } from '../../queries.js';
|
|
2
|
-
import { symbolPath } from '../../queries-cli.js';
|
|
1
|
+
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
|
|
2
|
+
import { symbolPath } from '../../presentation/queries-cli.js';
|
|
3
3
|
|
|
4
4
|
export const command = {
|
|
5
5
|
name: 'path <from> <to>',
|
package/src/cli/commands/plot.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { openGraph } from '../shared/open-graph.js';
|
|
4
4
|
|
|
5
5
|
export const command = {
|
|
6
6
|
name: 'plot',
|
|
@@ -22,43 +22,52 @@ export const command = {
|
|
|
22
22
|
['--color-by <mode>', 'Color nodes by: kind | role | community | complexity'],
|
|
23
23
|
],
|
|
24
24
|
async execute(_args, opts, ctx) {
|
|
25
|
-
const { generatePlotHTML, loadPlotConfig } = await import('../../
|
|
25
|
+
const { generatePlotHTML, loadPlotConfig } = await import('../../features/graph-enrichment.js');
|
|
26
26
|
const os = await import('node:os');
|
|
27
|
-
const db =
|
|
27
|
+
const { db, close } = openGraph(opts);
|
|
28
28
|
|
|
29
29
|
let plotCfg;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
let html;
|
|
31
|
+
try {
|
|
32
|
+
if (opts.config) {
|
|
33
|
+
try {
|
|
34
|
+
plotCfg = JSON.parse(fs.readFileSync(opts.config, 'utf-8'));
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.error(`Failed to load config: ${e.message}`);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
plotCfg = loadPlotConfig(process.cwd());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (opts.cluster) plotCfg.clusterBy = opts.cluster;
|
|
45
|
+
if (opts.colorBy) plotCfg.colorBy = opts.colorBy;
|
|
46
|
+
if (opts.sizeBy) plotCfg.sizeBy = opts.sizeBy;
|
|
47
|
+
if (opts.seed) plotCfg.seedStrategy = opts.seed;
|
|
48
|
+
if (opts.seedCount) plotCfg.seedCount = parseInt(opts.seedCount, 10);
|
|
49
|
+
if (opts.overlay) {
|
|
50
|
+
const parts = opts.overlay.split(',').map((s) => s.trim());
|
|
51
|
+
if (!plotCfg.overlays) plotCfg.overlays = {};
|
|
52
|
+
if (parts.includes('complexity')) plotCfg.overlays.complexity = true;
|
|
53
|
+
if (parts.includes('risk')) plotCfg.overlays.risk = true;
|
|
38
54
|
}
|
|
39
|
-
} else {
|
|
40
|
-
plotCfg = loadPlotConfig(process.cwd());
|
|
41
|
-
}
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (parts.includes('complexity')) plotCfg.overlays.complexity = true;
|
|
52
|
-
if (parts.includes('risk')) plotCfg.overlays.risk = true;
|
|
56
|
+
html = generatePlotHTML(db, {
|
|
57
|
+
fileLevel: !opts.functions,
|
|
58
|
+
noTests: ctx.resolveNoTests(opts),
|
|
59
|
+
minConfidence: parseFloat(opts.minConfidence),
|
|
60
|
+
config: plotCfg,
|
|
61
|
+
});
|
|
62
|
+
} finally {
|
|
63
|
+
close();
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
61
|
-
db.close();
|
|
66
|
+
if (!html) {
|
|
67
|
+
console.error('generatePlotHTML returned no output');
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
62
71
|
|
|
63
72
|
const outPath = opts.output || path.join(os.tmpdir(), `codegraph-plot-${Date.now()}.html`);
|
|
64
73
|
fs.writeFileSync(outPath, html, 'utf-8');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { collectFile } from '../../db/query-builder.js';
|
|
2
|
+
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
|
|
3
|
+
import { fnDeps, symbolPath } from '../../presentation/queries-cli.js';
|
|
3
4
|
|
|
4
5
|
export const command = {
|
|
5
6
|
name: 'query <name>',
|
|
@@ -7,7 +8,11 @@ export const command = {
|
|
|
7
8
|
queryOpts: true,
|
|
8
9
|
options: [
|
|
9
10
|
['--depth <n>', 'Transitive caller depth', '3'],
|
|
10
|
-
[
|
|
11
|
+
[
|
|
12
|
+
'-f, --file <path>',
|
|
13
|
+
'Scope search to functions in this file (partial match, repeatable)',
|
|
14
|
+
collectFile,
|
|
15
|
+
],
|
|
11
16
|
['-k, --kind <kind>', 'Filter to a specific symbol kind'],
|
|
12
17
|
['--path <to>', 'Path mode: find shortest path to <to>'],
|
|
13
18
|
['--kinds <kinds>', 'Path mode: comma-separated edge kinds to follow (default: calls)'],
|
|
@@ -38,11 +43,7 @@ export const command = {
|
|
|
38
43
|
depth: parseInt(opts.depth, 10),
|
|
39
44
|
file: opts.file,
|
|
40
45
|
kind: opts.kind,
|
|
41
|
-
|
|
42
|
-
json: opts.json,
|
|
43
|
-
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
44
|
-
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
45
|
-
ndjson: opts.ndjson,
|
|
46
|
+
...ctx.resolveQueryOpts(opts),
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
},
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { ConfigError } from '../../errors.js';
|
|
4
3
|
import {
|
|
5
4
|
listRepos,
|
|
6
5
|
pruneRegistry,
|
|
7
6
|
REGISTRY_PATH,
|
|
8
7
|
registerRepo,
|
|
9
8
|
unregisterRepo,
|
|
10
|
-
} from '../../registry.js';
|
|
9
|
+
} from '../../infrastructure/registry.js';
|
|
10
|
+
import { ConfigError } from '../../shared/errors.js';
|
|
11
11
|
|
|
12
12
|
export const command = {
|
|
13
13
|
name: 'registry',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { collectFile } from '../../db/query-builder.js';
|
|
2
|
+
import { VALID_ROLES } from '../../domain/queries.js';
|
|
3
|
+
import { roles } from '../../presentation/queries-cli.js';
|
|
3
4
|
|
|
4
5
|
export const command = {
|
|
5
6
|
name: 'roles',
|
|
@@ -7,7 +8,7 @@ export const command = {
|
|
|
7
8
|
options: [
|
|
8
9
|
['-d, --db <path>', 'Path to graph.db'],
|
|
9
10
|
['--role <role>', `Filter by role (${VALID_ROLES.join(', ')})`],
|
|
10
|
-
['-f, --file <path>', 'Scope to a specific file (partial match)'],
|
|
11
|
+
['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
|
|
11
12
|
['-T, --no-tests', 'Exclude test/spec files'],
|
|
12
13
|
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
13
14
|
['-j, --json', 'Output as JSON'],
|
|
@@ -24,11 +25,7 @@ export const command = {
|
|
|
24
25
|
roles(opts.db, {
|
|
25
26
|
role: opts.role,
|
|
26
27
|
file: opts.file,
|
|
27
|
-
|
|
28
|
-
json: opts.json,
|
|
29
|
-
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
30
|
-
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
31
|
-
ndjson: opts.ndjson,
|
|
28
|
+
...ctx.resolveQueryOpts(opts),
|
|
32
29
|
});
|
|
33
30
|
},
|
|
34
31
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collectFile } from '../../db/query-builder.js';
|
|
2
|
+
import { search } from '../../domain/search/index.js';
|
|
2
3
|
|
|
3
4
|
export const command = {
|
|
4
5
|
name: 'search <query>',
|
|
@@ -11,7 +12,7 @@ export const command = {
|
|
|
11
12
|
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
12
13
|
['--min-score <score>', 'Minimum similarity threshold', '0.2'],
|
|
13
14
|
['-k, --kind <kind>', 'Filter by kind: function, method, class'],
|
|
14
|
-
['--file <pattern>', 'Filter by file path pattern'],
|
|
15
|
+
['--file <pattern>', 'Filter by file path pattern (repeatable)', collectFile],
|
|
15
16
|
['--rrf-k <number>', 'RRF k parameter for multi-query ranking', '60'],
|
|
16
17
|
['--mode <mode>', 'Search mode: hybrid, semantic, keyword (default: hybrid)'],
|
|
17
18
|
['-j, --json', 'Output as JSON'],
|
|
@@ -25,6 +26,11 @@ export const command = {
|
|
|
25
26
|
}
|
|
26
27
|
},
|
|
27
28
|
async execute([query], opts, ctx) {
|
|
29
|
+
// --file collects into an array; pass single element unwrapped for single
|
|
30
|
+
// value, or pass the raw array for multi-file scoping.
|
|
31
|
+
const fileArr = opts.file || [];
|
|
32
|
+
const filePattern =
|
|
33
|
+
fileArr.length === 1 ? fileArr[0] : fileArr.length > 1 ? fileArr : undefined;
|
|
28
34
|
await search(query, opts.db, {
|
|
29
35
|
limit: parseInt(opts.limit, 10),
|
|
30
36
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
@@ -32,7 +38,7 @@ export const command = {
|
|
|
32
38
|
minScore: parseFloat(opts.minScore),
|
|
33
39
|
model: opts.model,
|
|
34
40
|
kind: opts.kind,
|
|
35
|
-
filePattern
|
|
41
|
+
filePattern,
|
|
36
42
|
rrfK: parseInt(opts.rrfK, 10),
|
|
37
43
|
mode: opts.mode,
|
|
38
44
|
json: opts.json,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collectFile } from '../../db/query-builder.js';
|
|
2
|
+
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
|
|
2
3
|
|
|
3
4
|
export const command = {
|
|
4
5
|
name: 'sequence <name>',
|
|
@@ -7,7 +8,7 @@ export const command = {
|
|
|
7
8
|
options: [
|
|
8
9
|
['--depth <n>', 'Max forward traversal depth', '10'],
|
|
9
10
|
['--dataflow', 'Annotate with parameter names and return arrows from dataflow table'],
|
|
10
|
-
['-f, --file <path>', 'Scope to a specific file (partial match)'],
|
|
11
|
+
['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
|
|
11
12
|
['-k, --kind <kind>', 'Filter by symbol kind'],
|
|
12
13
|
],
|
|
13
14
|
validate([_name], opts) {
|
|
@@ -16,17 +17,13 @@ export const command = {
|
|
|
16
17
|
}
|
|
17
18
|
},
|
|
18
19
|
async execute([name], opts, ctx) {
|
|
19
|
-
const { sequence } = await import('../../
|
|
20
|
+
const { sequence } = await import('../../presentation/sequence.js');
|
|
20
21
|
sequence(name, opts.db, {
|
|
21
22
|
depth: parseInt(opts.depth, 10),
|
|
22
23
|
file: opts.file,
|
|
23
24
|
kind: opts.kind,
|
|
24
|
-
noTests: ctx.resolveNoTests(opts),
|
|
25
|
-
json: opts.json,
|
|
26
25
|
dataflow: opts.dataflow,
|
|
27
|
-
|
|
28
|
-
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
29
|
-
ndjson: opts.ndjson,
|
|
26
|
+
...ctx.resolveQueryOpts(opts),
|
|
30
27
|
});
|
|
31
28
|
},
|
|
32
29
|
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
snapshotDelete,
|
|
3
|
+
snapshotList,
|
|
4
|
+
snapshotRestore,
|
|
5
|
+
snapshotSave,
|
|
6
|
+
} from '../../features/snapshot.js';
|
|
2
7
|
|
|
3
8
|
export const command = {
|
|
4
9
|
name: 'snapshot',
|
|
@@ -15,15 +15,16 @@ export const command = {
|
|
|
15
15
|
['--ndjson', 'Newline-delimited JSON output'],
|
|
16
16
|
],
|
|
17
17
|
async execute([dir], opts, ctx) {
|
|
18
|
-
const { structureData, formatStructure } = await import('../../
|
|
18
|
+
const { structureData, formatStructure } = await import('../../presentation/structure.js');
|
|
19
|
+
const qOpts = ctx.resolveQueryOpts(opts);
|
|
19
20
|
const data = structureData(opts.db, {
|
|
20
21
|
directory: dir,
|
|
21
22
|
depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
|
|
22
23
|
sort: opts.sort,
|
|
23
24
|
full: opts.full,
|
|
24
|
-
noTests:
|
|
25
|
-
limit:
|
|
26
|
-
offset:
|
|
25
|
+
noTests: qOpts.noTests,
|
|
26
|
+
limit: qOpts.limit,
|
|
27
|
+
offset: qOpts.offset,
|
|
27
28
|
});
|
|
28
29
|
if (!ctx.outputResult(data, 'directories', opts)) {
|
|
29
30
|
console.log(formatStructure(data));
|
|
@@ -1,5 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EVERY_SYMBOL_KIND, VALID_ROLES } from '../../queries.js';
|
|
1
|
+
import { collectFile } from '../../db/query-builder.js';
|
|
2
|
+
import { EVERY_SYMBOL_KIND, VALID_ROLES } from '../../domain/queries.js';
|
|
3
|
+
import { ConfigError } from '../../shared/errors.js';
|
|
4
|
+
|
|
5
|
+
function validateFilters(opts) {
|
|
6
|
+
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
7
|
+
throw new ConfigError(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
8
|
+
}
|
|
9
|
+
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
10
|
+
throw new ConfigError(`Invalid role "${opts.role}". Valid: ${VALID_ROLES.join(', ')}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseWeights(raw) {
|
|
15
|
+
if (!raw) return undefined;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
throw new ConfigError('Invalid --weights JSON', { cause: err });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function runHotspots(opts, ctx) {
|
|
24
|
+
const { hotspotsData, formatHotspots } = await import('../../presentation/structure.js');
|
|
25
|
+
const metric = opts.sort === 'risk' ? 'fan-in' : opts.sort;
|
|
26
|
+
const data = hotspotsData(opts.db, {
|
|
27
|
+
metric,
|
|
28
|
+
level: opts.level,
|
|
29
|
+
limit: parseInt(opts.limit, 10),
|
|
30
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
31
|
+
noTests: ctx.resolveNoTests(opts),
|
|
32
|
+
});
|
|
33
|
+
if (!ctx.outputResult(data, 'hotspots', opts)) {
|
|
34
|
+
console.log(formatHotspots(data));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
3
37
|
|
|
4
38
|
export const command = {
|
|
5
39
|
name: 'triage',
|
|
@@ -20,7 +54,7 @@ export const command = {
|
|
|
20
54
|
],
|
|
21
55
|
['--min-score <score>', 'Only show symbols with risk score >= threshold'],
|
|
22
56
|
['--role <role>', 'Filter by role (entry, core, utility, adapter, leaf, dead)'],
|
|
23
|
-
['-f, --file <path>', 'Scope to a specific file (partial match)'],
|
|
57
|
+
['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
|
|
24
58
|
['-k, --kind <kind>', 'Filter by symbol kind (function, method, class)'],
|
|
25
59
|
['-T, --no-tests', 'Exclude test/spec files from results'],
|
|
26
60
|
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
@@ -31,36 +65,13 @@ export const command = {
|
|
|
31
65
|
],
|
|
32
66
|
async execute(_args, opts, ctx) {
|
|
33
67
|
if (opts.level === 'file' || opts.level === 'directory') {
|
|
34
|
-
|
|
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
|
-
}
|
|
68
|
+
await runHotspots(opts, ctx);
|
|
46
69
|
return;
|
|
47
70
|
}
|
|
48
71
|
|
|
49
|
-
|
|
50
|
-
|
|
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');
|
|
72
|
+
validateFilters(opts);
|
|
73
|
+
const weights = parseWeights(opts.weights);
|
|
74
|
+
const { triage } = await import('../../presentation/triage.js');
|
|
64
75
|
triage(opts.db, {
|
|
65
76
|
limit: parseInt(opts.limit, 10),
|
|
66
77
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { where } from '../../queries-cli.js';
|
|
1
|
+
import { where } from '../../presentation/queries-cli.js';
|
|
2
2
|
|
|
3
3
|
export const command = {
|
|
4
4
|
name: 'where [name]',
|
|
@@ -14,11 +14,7 @@ export const command = {
|
|
|
14
14
|
const target = opts.file || name;
|
|
15
15
|
where(target, opts.db, {
|
|
16
16
|
file: !!opts.file,
|
|
17
|
-
|
|
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,
|
|
17
|
+
...ctx.resolveQueryOpts(opts),
|
|
22
18
|
});
|
|
23
19
|
},
|
|
24
20
|
};
|
package/src/cli/index.js
CHANGED
|
@@ -2,10 +2,16 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { setVerbose } from '../infrastructure/logger.js';
|
|
6
|
+
import { checkForUpdates, printUpdateNotification } from '../infrastructure/update-check.js';
|
|
7
|
+
import { ConfigError } from '../shared/errors.js';
|
|
8
|
+
import {
|
|
9
|
+
applyQueryOpts,
|
|
10
|
+
config,
|
|
11
|
+
formatSize,
|
|
12
|
+
resolveNoTests,
|
|
13
|
+
resolveQueryOpts,
|
|
14
|
+
} from './shared/options.js';
|
|
9
15
|
import { outputResult } from './shared/output.js';
|
|
10
16
|
|
|
11
17
|
const __cliDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1'));
|
|
@@ -35,7 +41,7 @@ program
|
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
/** Shared context passed to every command's execute(). */
|
|
38
|
-
const ctx = { config, resolveNoTests, formatSize, outputResult, program };
|
|
44
|
+
const ctx = { config, resolveNoTests, resolveQueryOpts, formatSize, outputResult, program };
|
|
39
45
|
|
|
40
46
|
/**
|
|
41
47
|
* Register a command definition onto a Commander parent.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { openReadonlyOrFail } from '../../db/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Open the graph database in readonly mode with a clean close() handle.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} [opts]
|
|
7
|
+
* @param {string} [opts.db] - Custom path to graph.db
|
|
8
|
+
* @returns {{ db: import('better-sqlite3').Database, close: () => void }}
|
|
9
|
+
*/
|
|
10
|
+
export function openGraph(opts = {}) {
|
|
11
|
+
const db = openReadonlyOrFail(opts.db);
|
|
12
|
+
return { db, close: () => db.close() };
|
|
13
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadConfig } from '../../config.js';
|
|
1
|
+
import { loadConfig } from '../../infrastructure/config.js';
|
|
2
2
|
|
|
3
3
|
const config = loadConfig(process.cwd());
|
|
4
4
|
|
|
@@ -15,7 +15,9 @@ export function applyQueryOpts(cmd) {
|
|
|
15
15
|
.option('-j, --json', 'Output as JSON')
|
|
16
16
|
.option('--limit <number>', 'Max results to return')
|
|
17
17
|
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
18
|
-
.option('--ndjson', 'Newline-delimited JSON output')
|
|
18
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
19
|
+
.option('--table', 'Output as aligned table')
|
|
20
|
+
.option('--csv', 'Output as CSV');
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -30,6 +32,24 @@ export function resolveNoTests(opts) {
|
|
|
30
32
|
return config.query?.excludeTests || false;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Extract the common query option fields shared by most analysis commands.
|
|
37
|
+
*
|
|
38
|
+
* Spreads cleanly into per-command option objects:
|
|
39
|
+
* `{ ...resolveQueryOpts(opts), depth: parseInt(opts.depth, 10) }`
|
|
40
|
+
*/
|
|
41
|
+
export function resolveQueryOpts(opts) {
|
|
42
|
+
return {
|
|
43
|
+
noTests: resolveNoTests(opts),
|
|
44
|
+
json: opts.json,
|
|
45
|
+
ndjson: opts.ndjson,
|
|
46
|
+
table: opts.table,
|
|
47
|
+
csv: opts.csv,
|
|
48
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
49
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
export function formatSize(bytes) {
|
|
34
54
|
if (bytes < 1024) return `${bytes} B`;
|
|
35
55
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|