@optave/codegraph 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/package.json +7 -7
  2. package/src/ast-analysis/engine.js +365 -0
  3. package/src/ast-analysis/metrics.js +118 -0
  4. package/src/ast-analysis/visitor-utils.js +176 -0
  5. package/src/ast-analysis/visitor.js +162 -0
  6. package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
  7. package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
  8. package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
  9. package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
  10. package/src/ast.js +13 -140
  11. package/src/audit.js +2 -87
  12. package/src/batch.js +0 -25
  13. package/src/boundaries.js +1 -1
  14. package/src/branch-compare.js +1 -96
  15. package/src/builder.js +48 -179
  16. package/src/cfg.js +89 -883
  17. package/src/check.js +1 -84
  18. package/src/cli.js +20 -19
  19. package/src/cochange.js +1 -39
  20. package/src/commands/audit.js +88 -0
  21. package/src/commands/batch.js +26 -0
  22. package/src/commands/branch-compare.js +97 -0
  23. package/src/commands/cfg.js +55 -0
  24. package/src/commands/check.js +82 -0
  25. package/src/commands/cochange.js +37 -0
  26. package/src/commands/communities.js +69 -0
  27. package/src/commands/complexity.js +77 -0
  28. package/src/commands/dataflow.js +110 -0
  29. package/src/commands/flow.js +70 -0
  30. package/src/commands/manifesto.js +77 -0
  31. package/src/commands/owners.js +52 -0
  32. package/src/commands/query.js +21 -0
  33. package/src/commands/sequence.js +33 -0
  34. package/src/commands/structure.js +64 -0
  35. package/src/commands/triage.js +49 -0
  36. package/src/communities.js +12 -83
  37. package/src/complexity.js +42 -356
  38. package/src/cycles.js +1 -1
  39. package/src/dataflow.js +12 -665
  40. package/src/db/repository/build-stmts.js +104 -0
  41. package/src/db/repository/cfg.js +83 -0
  42. package/src/db/repository/cochange.js +41 -0
  43. package/src/db/repository/complexity.js +15 -0
  44. package/src/db/repository/dataflow.js +12 -0
  45. package/src/db/repository/edges.js +259 -0
  46. package/src/db/repository/embeddings.js +40 -0
  47. package/src/db/repository/graph-read.js +39 -0
  48. package/src/db/repository/index.js +42 -0
  49. package/src/db/repository/nodes.js +236 -0
  50. package/src/db.js +40 -1
  51. package/src/embedder.js +14 -34
  52. package/src/export.js +1 -1
  53. package/src/extractors/javascript.js +130 -5
  54. package/src/flow.js +2 -70
  55. package/src/index.js +23 -19
  56. package/src/{result-formatter.js → infrastructure/result-formatter.js} +1 -1
  57. package/src/kinds.js +1 -0
  58. package/src/manifesto.js +0 -76
  59. package/src/owners.js +1 -56
  60. package/src/queries-cli.js +1 -1
  61. package/src/queries.js +79 -280
  62. package/src/sequence.js +5 -44
  63. package/src/structure.js +16 -75
  64. package/src/triage.js +1 -54
  65. package/src/viewer.js +1 -1
  66. package/src/watcher.js +7 -4
  67. package/src/db/repository.js +0 -134
  68. /package/src/{test-filter.js → infrastructure/test-filter.js} +0 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Format co-change data for CLI output (single file).
3
+ */
4
+ export function formatCoChange(data) {
5
+ if (data.error) return data.error;
6
+ if (data.partners.length === 0) return `No co-change partners found for ${data.file}`;
7
+
8
+ const lines = [`\nCo-change partners for ${data.file}:\n`];
9
+ for (const p of data.partners) {
10
+ const pct = `${(p.jaccard * 100).toFixed(0)}%`.padStart(4);
11
+ const commits = `${p.commitCount} commits`.padStart(12);
12
+ lines.push(` ${pct} ${commits} ${p.file}`);
13
+ }
14
+ if (data.meta?.analyzedAt) {
15
+ lines.push(`\n Analyzed: ${data.meta.analyzedAt} | Window: ${data.meta.since || 'all'}`);
16
+ }
17
+ return lines.join('\n');
18
+ }
19
+
20
+ /**
21
+ * Format top co-change pairs for CLI output (global view).
22
+ */
23
+ export function formatCoChangeTop(data) {
24
+ if (data.error) return data.error;
25
+ if (data.pairs.length === 0) return 'No co-change pairs found.';
26
+
27
+ const lines = ['\nTop co-change pairs:\n'];
28
+ for (const p of data.pairs) {
29
+ const pct = `${(p.jaccard * 100).toFixed(0)}%`.padStart(4);
30
+ const commits = `${p.commitCount} commits`.padStart(12);
31
+ lines.push(` ${pct} ${commits} ${p.fileA} <-> ${p.fileB}`);
32
+ }
33
+ if (data.meta?.analyzedAt) {
34
+ lines.push(`\n Analyzed: ${data.meta.analyzedAt} | Window: ${data.meta.since || 'all'}`);
35
+ }
36
+ return lines.join('\n');
37
+ }
@@ -0,0 +1,69 @@
1
+ import { communitiesData } from '../communities.js';
2
+ import { outputResult } from '../infrastructure/result-formatter.js';
3
+
4
+ /**
5
+ * CLI entry point: run community detection and print results.
6
+ */
7
+ export function communities(customDbPath, opts = {}) {
8
+ const data = communitiesData(customDbPath, opts);
9
+
10
+ if (outputResult(data, 'communities', opts)) return;
11
+
12
+ if (data.summary.communityCount === 0) {
13
+ console.log(
14
+ '\nNo communities detected. The graph may be too small or disconnected.\n' +
15
+ 'Run "codegraph build" first to populate the graph.\n',
16
+ );
17
+ return;
18
+ }
19
+
20
+ const mode = opts.functions ? 'Function' : 'File';
21
+ console.log(`\n# ${mode}-Level Communities\n`);
22
+ console.log(
23
+ ` ${data.summary.communityCount} communities | ${data.summary.nodeCount} nodes | modularity: ${data.summary.modularity} | drift: ${data.summary.driftScore}%\n`,
24
+ );
25
+
26
+ if (!opts.drift) {
27
+ for (const c of data.communities) {
28
+ const dirs = Object.entries(c.directories)
29
+ .sort((a, b) => b[1] - a[1])
30
+ .map(([d, n]) => `${d} (${n})`)
31
+ .join(', ');
32
+ console.log(` Community ${c.id} (${c.size} members): ${dirs}`);
33
+ if (c.members) {
34
+ const shown = c.members.slice(0, 8);
35
+ for (const m of shown) {
36
+ const kind = m.kind ? ` [${m.kind}]` : '';
37
+ console.log(` - ${m.name}${kind} ${m.file}`);
38
+ }
39
+ if (c.members.length > 8) {
40
+ console.log(` ... and ${c.members.length - 8} more`);
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ // Drift analysis
47
+ const d = data.drift;
48
+ if (d.splitCandidates.length > 0 || d.mergeCandidates.length > 0) {
49
+ console.log(`\n# Drift Analysis (score: ${data.summary.driftScore}%)\n`);
50
+
51
+ if (d.splitCandidates.length > 0) {
52
+ console.log(' Split candidates (directories spanning multiple communities):');
53
+ for (const s of d.splitCandidates.slice(0, 10)) {
54
+ console.log(` - ${s.directory} → ${s.communityCount} communities`);
55
+ }
56
+ }
57
+
58
+ if (d.mergeCandidates.length > 0) {
59
+ console.log(' Merge candidates (communities spanning multiple directories):');
60
+ for (const m of d.mergeCandidates.slice(0, 10)) {
61
+ console.log(
62
+ ` - Community ${m.communityId} (${m.size} members) → ${m.directoryCount} dirs: ${m.directories.join(', ')}`,
63
+ );
64
+ }
65
+ }
66
+ }
67
+
68
+ console.log();
69
+ }
@@ -0,0 +1,77 @@
1
+ import { complexityData } from '../complexity.js';
2
+ import { outputResult } from '../infrastructure/result-formatter.js';
3
+
4
+ /**
5
+ * Format complexity output for CLI display.
6
+ */
7
+ export function complexity(customDbPath, opts = {}) {
8
+ const data = complexityData(customDbPath, opts);
9
+
10
+ if (outputResult(data, 'functions', opts)) return;
11
+
12
+ if (data.functions.length === 0) {
13
+ if (data.summary === null) {
14
+ if (data.hasGraph) {
15
+ console.log(
16
+ '\nNo complexity data found, but a graph exists. Run "codegraph build --no-incremental" to populate complexity metrics.\n',
17
+ );
18
+ } else {
19
+ console.log(
20
+ '\nNo complexity data found. Run "codegraph build" first to analyze your codebase.\n',
21
+ );
22
+ }
23
+ } else {
24
+ console.log('\nNo functions match the given filters.\n');
25
+ }
26
+ return;
27
+ }
28
+
29
+ const header = opts.aboveThreshold ? 'Functions Above Threshold' : 'Function Complexity';
30
+ console.log(`\n# ${header}\n`);
31
+
32
+ if (opts.health) {
33
+ // Health-focused view with Halstead + MI columns
34
+ console.log(
35
+ ` ${'Function'.padEnd(35)} ${'File'.padEnd(25)} ${'MI'.padStart(5)} ${'Vol'.padStart(7)} ${'Diff'.padStart(6)} ${'Effort'.padStart(9)} ${'Bugs'.padStart(6)} ${'LOC'.padStart(5)} ${'SLOC'.padStart(5)}`,
36
+ );
37
+ console.log(
38
+ ` ${'─'.repeat(35)} ${'─'.repeat(25)} ${'─'.repeat(5)} ${'─'.repeat(7)} ${'─'.repeat(6)} ${'─'.repeat(9)} ${'─'.repeat(6)} ${'─'.repeat(5)} ${'─'.repeat(5)}`,
39
+ );
40
+
41
+ for (const fn of data.functions) {
42
+ const name = fn.name.length > 33 ? `${fn.name.slice(0, 32)}…` : fn.name;
43
+ const file = fn.file.length > 23 ? `…${fn.file.slice(-22)}` : fn.file;
44
+ const miWarn = fn.exceeds?.includes('maintainabilityIndex') ? '!' : ' ';
45
+ console.log(
46
+ ` ${name.padEnd(35)} ${file.padEnd(25)} ${String(fn.maintainabilityIndex).padStart(5)}${miWarn}${String(fn.halstead.volume).padStart(7)} ${String(fn.halstead.difficulty).padStart(6)} ${String(fn.halstead.effort).padStart(9)} ${String(fn.halstead.bugs).padStart(6)} ${String(fn.loc).padStart(5)} ${String(fn.sloc).padStart(5)}`,
47
+ );
48
+ }
49
+ } else {
50
+ // Default view with MI column appended
51
+ console.log(
52
+ ` ${'Function'.padEnd(40)} ${'File'.padEnd(30)} ${'Cog'.padStart(4)} ${'Cyc'.padStart(4)} ${'Nest'.padStart(5)} ${'MI'.padStart(5)}`,
53
+ );
54
+ console.log(
55
+ ` ${'─'.repeat(40)} ${'─'.repeat(30)} ${'─'.repeat(4)} ${'─'.repeat(4)} ${'─'.repeat(5)} ${'─'.repeat(5)}`,
56
+ );
57
+
58
+ for (const fn of data.functions) {
59
+ const name = fn.name.length > 38 ? `${fn.name.slice(0, 37)}…` : fn.name;
60
+ const file = fn.file.length > 28 ? `…${fn.file.slice(-27)}` : fn.file;
61
+ const warn = fn.exceeds ? ' !' : '';
62
+ const mi = fn.maintainabilityIndex > 0 ? String(fn.maintainabilityIndex) : '-';
63
+ console.log(
64
+ ` ${name.padEnd(40)} ${file.padEnd(30)} ${String(fn.cognitive).padStart(4)} ${String(fn.cyclomatic).padStart(4)} ${String(fn.maxNesting).padStart(5)} ${mi.padStart(5)}${warn}`,
65
+ );
66
+ }
67
+ }
68
+
69
+ if (data.summary) {
70
+ const s = data.summary;
71
+ const miPart = s.avgMI != null ? ` | avg MI: ${s.avgMI}` : '';
72
+ console.log(
73
+ `\n ${s.analyzed} functions analyzed | avg cognitive: ${s.avgCognitive} | avg cyclomatic: ${s.avgCyclomatic}${miPart} | ${s.aboveWarn} above threshold`,
74
+ );
75
+ }
76
+ console.log();
77
+ }
@@ -0,0 +1,110 @@
1
+ import { dataflowData, dataflowImpactData } from '../dataflow.js';
2
+ import { outputResult } from '../infrastructure/result-formatter.js';
3
+
4
+ /**
5
+ * CLI display for dataflow command.
6
+ */
7
+ export function dataflow(name, customDbPath, opts = {}) {
8
+ if (opts.impact) {
9
+ return dataflowImpact(name, customDbPath, opts);
10
+ }
11
+
12
+ const data = dataflowData(name, customDbPath, opts);
13
+
14
+ if (outputResult(data, 'results', opts)) return;
15
+
16
+ if (data.warning) {
17
+ console.log(`⚠ ${data.warning}`);
18
+ return;
19
+ }
20
+ if (data.results.length === 0) {
21
+ console.log(`No symbols matching "${name}".`);
22
+ return;
23
+ }
24
+
25
+ for (const r of data.results) {
26
+ console.log(`\n${r.kind} ${r.name} (${r.file}:${r.line})`);
27
+ console.log('─'.repeat(60));
28
+
29
+ if (r.flowsTo.length > 0) {
30
+ console.log('\n Data flows TO:');
31
+ for (const f of r.flowsTo) {
32
+ const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
33
+ console.log(` → ${f.target} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
34
+ }
35
+ }
36
+
37
+ if (r.flowsFrom.length > 0) {
38
+ console.log('\n Data flows FROM:');
39
+ for (const f of r.flowsFrom) {
40
+ const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
41
+ console.log(` ← ${f.source} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
42
+ }
43
+ }
44
+
45
+ if (r.returns.length > 0) {
46
+ console.log('\n Return value consumed by:');
47
+ for (const c of r.returns) {
48
+ console.log(` → ${c.consumer} (${c.file}:${c.line}) ${c.expression}`);
49
+ }
50
+ }
51
+
52
+ if (r.returnedBy.length > 0) {
53
+ console.log('\n Uses return value of:');
54
+ for (const p of r.returnedBy) {
55
+ console.log(` ← ${p.producer} (${p.file}:${p.line}) ${p.expression}`);
56
+ }
57
+ }
58
+
59
+ if (r.mutates.length > 0) {
60
+ console.log('\n Mutates:');
61
+ for (const m of r.mutates) {
62
+ console.log(` ✎ ${m.expression} (line ${m.line})`);
63
+ }
64
+ }
65
+
66
+ if (r.mutatedBy.length > 0) {
67
+ console.log('\n Mutated by:');
68
+ for (const m of r.mutatedBy) {
69
+ console.log(` ✎ ${m.source} — ${m.expression} (line ${m.line})`);
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * CLI display for dataflow --impact.
77
+ */
78
+ function dataflowImpact(name, customDbPath, opts = {}) {
79
+ const data = dataflowImpactData(name, customDbPath, {
80
+ noTests: opts.noTests,
81
+ depth: opts.depth ? Number(opts.depth) : 5,
82
+ file: opts.file,
83
+ kind: opts.kind,
84
+ limit: opts.limit,
85
+ offset: opts.offset,
86
+ });
87
+
88
+ if (outputResult(data, 'results', opts)) return;
89
+
90
+ if (data.warning) {
91
+ console.log(`⚠ ${data.warning}`);
92
+ return;
93
+ }
94
+ if (data.results.length === 0) {
95
+ console.log(`No symbols matching "${name}".`);
96
+ return;
97
+ }
98
+
99
+ for (const r of data.results) {
100
+ console.log(
101
+ `\n${r.kind} ${r.name} (${r.file}:${r.line}) — ${r.totalAffected} data-dependent consumer${r.totalAffected !== 1 ? 's' : ''}`,
102
+ );
103
+ for (const [level, items] of Object.entries(r.levels)) {
104
+ console.log(` Level ${level}:`);
105
+ for (const item of items) {
106
+ console.log(` ${item.name} (${item.file}:${item.line})`);
107
+ }
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,70 @@
1
+ import { flowData, listEntryPointsData } from '../flow.js';
2
+ import { outputResult } from '../infrastructure/result-formatter.js';
3
+ import { kindIcon } from '../queries.js';
4
+
5
+ /**
6
+ * CLI formatter — text or JSON output.
7
+ */
8
+ export function flow(name, dbPath, opts = {}) {
9
+ if (opts.list) {
10
+ const data = listEntryPointsData(dbPath, {
11
+ noTests: opts.noTests,
12
+ limit: opts.limit,
13
+ offset: opts.offset,
14
+ });
15
+ if (outputResult(data, 'entries', opts)) return;
16
+ if (data.count === 0) {
17
+ console.log('No entry points found. Run "codegraph build" first.');
18
+ return;
19
+ }
20
+ console.log(`\nEntry points (${data.count} total):\n`);
21
+ for (const [type, entries] of Object.entries(data.byType)) {
22
+ console.log(` ${type} (${entries.length}):`);
23
+ for (const e of entries) {
24
+ console.log(` [${kindIcon(e.kind)}] ${e.name} ${e.file}:${e.line}`);
25
+ }
26
+ console.log();
27
+ }
28
+ return;
29
+ }
30
+
31
+ const data = flowData(name, dbPath, opts);
32
+ if (outputResult(data, 'steps', opts)) return;
33
+
34
+ if (!data.entry) {
35
+ console.log(`No matching entry point or function found for "${name}".`);
36
+ return;
37
+ }
38
+
39
+ const e = data.entry;
40
+ const typeTag = e.type !== 'exported' ? ` (${e.type})` : '';
41
+ console.log(`\nFlow from: [${kindIcon(e.kind)}] ${e.name}${typeTag} ${e.file}:${e.line}`);
42
+ console.log(
43
+ `Depth: ${data.depth} Reached: ${data.totalReached} nodes Leaves: ${data.leaves.length}`,
44
+ );
45
+ if (data.truncated) {
46
+ console.log(` (truncated at depth ${data.depth})`);
47
+ }
48
+ console.log();
49
+
50
+ if (data.steps.length === 0) {
51
+ console.log(' (leaf node — no callees)');
52
+ return;
53
+ }
54
+
55
+ for (const step of data.steps) {
56
+ console.log(` depth ${step.depth}:`);
57
+ for (const n of step.nodes) {
58
+ const isLeaf = data.leaves.some((l) => l.name === n.name && l.file === n.file);
59
+ const leafTag = isLeaf ? ' [leaf]' : '';
60
+ console.log(` [${kindIcon(n.kind)}] ${n.name} ${n.file}:${n.line}${leafTag}`);
61
+ }
62
+ }
63
+
64
+ if (data.cycles.length > 0) {
65
+ console.log('\n Cycles detected:');
66
+ for (const c of data.cycles) {
67
+ console.log(` ${c.from} -> ${c.to} (at depth ${c.depth})`);
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,77 @@
1
+ import { outputResult } from '../infrastructure/result-formatter.js';
2
+ import { manifestoData } from '../manifesto.js';
3
+
4
+ /**
5
+ * CLI formatter — prints manifesto results and exits with code 1 on failure.
6
+ */
7
+ export function manifesto(customDbPath, opts = {}) {
8
+ const data = manifestoData(customDbPath, opts);
9
+
10
+ if (outputResult(data, 'violations', opts)) {
11
+ if (!data.passed) process.exit(1);
12
+ return;
13
+ }
14
+
15
+ console.log('\n# Manifesto Rules\n');
16
+
17
+ // Rules table
18
+ console.log(
19
+ ` ${'Rule'.padEnd(20)} ${'Level'.padEnd(10)} ${'Status'.padEnd(8)} ${'Warn'.padStart(6)} ${'Fail'.padStart(6)} ${'Violations'.padStart(11)}`,
20
+ );
21
+ console.log(
22
+ ` ${'─'.repeat(20)} ${'─'.repeat(10)} ${'─'.repeat(8)} ${'─'.repeat(6)} ${'─'.repeat(6)} ${'─'.repeat(11)}`,
23
+ );
24
+
25
+ for (const rule of data.rules) {
26
+ const warn = rule.thresholds.warn != null ? String(rule.thresholds.warn) : '—';
27
+ const fail = rule.thresholds.fail != null ? String(rule.thresholds.fail) : '—';
28
+ const statusIcon = rule.status === 'pass' ? 'pass' : rule.status === 'warn' ? 'WARN' : 'FAIL';
29
+ console.log(
30
+ ` ${rule.name.padEnd(20)} ${rule.level.padEnd(10)} ${statusIcon.padEnd(8)} ${warn.padStart(6)} ${fail.padStart(6)} ${String(rule.violationCount).padStart(11)}`,
31
+ );
32
+ }
33
+
34
+ // Summary
35
+ const s = data.summary;
36
+ console.log(
37
+ `\n ${s.total} rules | ${s.passed} passed | ${s.warned} warned | ${s.failed} failed | ${s.violationCount} violations`,
38
+ );
39
+
40
+ // Violations detail
41
+ if (data.violations.length > 0) {
42
+ const failViolations = data.violations.filter((v) => v.level === 'fail');
43
+ const warnViolations = data.violations.filter((v) => v.level === 'warn');
44
+
45
+ if (failViolations.length > 0) {
46
+ console.log(`\n## Failures (${failViolations.length})\n`);
47
+ for (const v of failViolations.slice(0, 20)) {
48
+ const loc = v.line ? `${v.file}:${v.line}` : v.file;
49
+ console.log(
50
+ ` [FAIL] ${v.rule}: ${v.name} (${v.value}) at ${loc} — threshold ${v.threshold}`,
51
+ );
52
+ }
53
+ if (failViolations.length > 20) {
54
+ console.log(` ... and ${failViolations.length - 20} more`);
55
+ }
56
+ }
57
+
58
+ if (warnViolations.length > 0) {
59
+ console.log(`\n## Warnings (${warnViolations.length})\n`);
60
+ for (const v of warnViolations.slice(0, 20)) {
61
+ const loc = v.line ? `${v.file}:${v.line}` : v.file;
62
+ console.log(
63
+ ` [WARN] ${v.rule}: ${v.name} (${v.value}) at ${loc} — threshold ${v.threshold}`,
64
+ );
65
+ }
66
+ if (warnViolations.length > 20) {
67
+ console.log(` ... and ${warnViolations.length - 20} more`);
68
+ }
69
+ }
70
+ }
71
+
72
+ console.log();
73
+
74
+ if (!data.passed) {
75
+ process.exit(1);
76
+ }
77
+ }
@@ -0,0 +1,52 @@
1
+ import { outputResult } from '../infrastructure/result-formatter.js';
2
+ import { ownersData } from '../owners.js';
3
+
4
+ /**
5
+ * CLI display function for the `owners` command.
6
+ */
7
+ export function owners(customDbPath, opts = {}) {
8
+ const data = ownersData(customDbPath, opts);
9
+ if (outputResult(data, null, opts)) return;
10
+
11
+ if (!data.codeownersFile) {
12
+ console.log('No CODEOWNERS file found.');
13
+ return;
14
+ }
15
+
16
+ console.log(`\nCODEOWNERS: ${data.codeownersFile}\n`);
17
+
18
+ const s = data.summary;
19
+ console.log(
20
+ ` Coverage: ${s.coveragePercent}% (${s.ownedFiles}/${s.totalFiles} files owned, ${s.ownerCount} owners)\n`,
21
+ );
22
+
23
+ if (s.byOwner.length > 0) {
24
+ console.log(' Owners:\n');
25
+ for (const o of s.byOwner) {
26
+ console.log(` ${o.owner} ${o.fileCount} files`);
27
+ }
28
+ console.log();
29
+ }
30
+
31
+ if (data.files.length > 0 && opts.owner) {
32
+ console.log(` Files owned by ${opts.owner}:\n`);
33
+ for (const f of data.files) {
34
+ console.log(` ${f.file}`);
35
+ }
36
+ console.log();
37
+ }
38
+
39
+ if (data.boundaries.length > 0) {
40
+ console.log(` Cross-owner boundaries: ${data.boundaries.length} edges\n`);
41
+ const shown = data.boundaries.slice(0, 30);
42
+ for (const b of shown) {
43
+ const srcOwner = b.from.owners.join(', ') || '(unowned)';
44
+ const tgtOwner = b.to.owners.join(', ') || '(unowned)';
45
+ console.log(` ${b.from.name} [${srcOwner}] -> ${b.to.name} [${tgtOwner}]`);
46
+ }
47
+ if (data.boundaries.length > 30) {
48
+ console.log(` ... and ${data.boundaries.length - 30} more`);
49
+ }
50
+ console.log();
51
+ }
52
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Re-export all query CLI wrappers from queries-cli.js.
3
+ * This barrel file provides the standard src/commands/ import path.
4
+ */
5
+ export {
6
+ children,
7
+ context,
8
+ diffImpact,
9
+ explain,
10
+ fileDeps,
11
+ fileExports,
12
+ fnDeps,
13
+ fnImpact,
14
+ impactAnalysis,
15
+ moduleMap,
16
+ queryName,
17
+ roles,
18
+ stats,
19
+ symbolPath,
20
+ where,
21
+ } from '../queries-cli.js';
@@ -0,0 +1,33 @@
1
+ import { outputResult } from '../infrastructure/result-formatter.js';
2
+ import { kindIcon } from '../queries.js';
3
+ import { sequenceData, sequenceToMermaid } from '../sequence.js';
4
+
5
+ /**
6
+ * CLI entry point — format sequence data as mermaid, JSON, or ndjson.
7
+ */
8
+ export function sequence(name, dbPath, opts = {}) {
9
+ const data = sequenceData(name, dbPath, opts);
10
+
11
+ if (outputResult(data, 'messages', opts)) return;
12
+
13
+ // Default: mermaid format
14
+ if (!data.entry) {
15
+ console.log(`No matching function found for "${name}".`);
16
+ return;
17
+ }
18
+
19
+ const e = data.entry;
20
+ console.log(`\nSequence from: [${kindIcon(e.kind)}] ${e.name} ${e.file}:${e.line}`);
21
+ console.log(`Participants: ${data.participants.length} Messages: ${data.totalMessages}`);
22
+ if (data.truncated) {
23
+ console.log(` (truncated at depth ${data.depth})`);
24
+ }
25
+ console.log();
26
+
27
+ if (data.messages.length === 0) {
28
+ console.log(' (leaf node — no callees)');
29
+ return;
30
+ }
31
+
32
+ console.log(sequenceToMermaid(data));
33
+ }
@@ -0,0 +1,64 @@
1
+ import path from 'node:path';
2
+ import { hotspotsData, moduleBoundariesData, structureData } from '../structure.js';
3
+
4
+ export { structureData, hotspotsData, moduleBoundariesData };
5
+
6
+ export function formatStructure(data) {
7
+ if (data.count === 0) return 'No directory structure found. Run "codegraph build" first.';
8
+
9
+ const lines = [`\nProject structure (${data.count} directories):\n`];
10
+ for (const d of data.directories) {
11
+ const cohStr = d.cohesion !== null ? ` cohesion=${d.cohesion.toFixed(2)}` : '';
12
+ const depth = d.directory.split('/').length - 1;
13
+ const indent = ' '.repeat(depth);
14
+ lines.push(
15
+ `${indent}${d.directory}/ (${d.fileCount} files, ${d.symbolCount} symbols, <-${d.fanIn} ->${d.fanOut}${cohStr})`,
16
+ );
17
+ for (const f of d.files) {
18
+ lines.push(
19
+ `${indent} ${path.basename(f.file)} ${f.lineCount}L ${f.symbolCount}sym <-${f.fanIn} ->${f.fanOut}`,
20
+ );
21
+ }
22
+ }
23
+ if (data.warning) {
24
+ lines.push('');
25
+ lines.push(`⚠ ${data.warning}`);
26
+ }
27
+ return lines.join('\n');
28
+ }
29
+
30
+ export function formatHotspots(data) {
31
+ if (data.hotspots.length === 0) return 'No hotspots found. Run "codegraph build" first.';
32
+
33
+ const lines = [`\nHotspots by ${data.metric} (${data.level}-level, top ${data.limit}):\n`];
34
+ let rank = 1;
35
+ for (const h of data.hotspots) {
36
+ const extra =
37
+ h.kind === 'directory'
38
+ ? `${h.fileCount} files, cohesion=${h.cohesion !== null ? h.cohesion.toFixed(2) : 'n/a'}`
39
+ : `${h.lineCount || 0}L, ${h.symbolCount || 0} symbols`;
40
+ lines.push(
41
+ ` ${String(rank++).padStart(2)}. ${h.name} <-${h.fanIn || 0} ->${h.fanOut || 0} (${extra})`,
42
+ );
43
+ }
44
+ return lines.join('\n');
45
+ }
46
+
47
+ export function formatModuleBoundaries(data) {
48
+ if (data.count === 0) return `No modules found with cohesion >= ${data.threshold}.`;
49
+
50
+ const lines = [`\nModule boundaries (cohesion >= ${data.threshold}, ${data.count} modules):\n`];
51
+ for (const m of data.modules) {
52
+ lines.push(
53
+ ` ${m.directory}/ cohesion=${m.cohesion.toFixed(2)} (${m.fileCount} files, ${m.symbolCount} symbols)`,
54
+ );
55
+ lines.push(` Incoming: ${m.fanIn} edges Outgoing: ${m.fanOut} edges`);
56
+ if (m.files.length > 0) {
57
+ lines.push(
58
+ ` Files: ${m.files.slice(0, 5).join(', ')}${m.files.length > 5 ? ` ... +${m.files.length - 5}` : ''}`,
59
+ );
60
+ }
61
+ lines.push('');
62
+ }
63
+ return lines.join('\n');
64
+ }
@@ -0,0 +1,49 @@
1
+ import { outputResult } from '../infrastructure/result-formatter.js';
2
+ import { triageData } from '../triage.js';
3
+
4
+ /**
5
+ * Print triage results to console.
6
+ */
7
+ export function triage(customDbPath, opts = {}) {
8
+ const data = triageData(customDbPath, opts);
9
+
10
+ if (outputResult(data, 'items', opts)) return;
11
+
12
+ if (data.items.length === 0) {
13
+ if (data.summary.total === 0) {
14
+ console.log('\nNo symbols found. Run "codegraph build" first.\n');
15
+ } else {
16
+ console.log('\nNo symbols match the given filters.\n');
17
+ }
18
+ return;
19
+ }
20
+
21
+ console.log('\n# Risk Audit Queue\n');
22
+
23
+ console.log(
24
+ ` ${'Symbol'.padEnd(35)} ${'File'.padEnd(28)} ${'Role'.padEnd(8)} ${'Score'.padStart(6)} ${'Fan-In'.padStart(7)} ${'Cog'.padStart(4)} ${'Churn'.padStart(6)} ${'MI'.padStart(5)}`,
25
+ );
26
+ console.log(
27
+ ` ${'─'.repeat(35)} ${'─'.repeat(28)} ${'─'.repeat(8)} ${'─'.repeat(6)} ${'─'.repeat(7)} ${'─'.repeat(4)} ${'─'.repeat(6)} ${'─'.repeat(5)}`,
28
+ );
29
+
30
+ for (const it of data.items) {
31
+ const name = it.name.length > 33 ? `${it.name.slice(0, 32)}…` : it.name;
32
+ const file = it.file.length > 26 ? `…${it.file.slice(-25)}` : it.file;
33
+ const role = (it.role || '-').padEnd(8);
34
+ const score = it.riskScore.toFixed(2).padStart(6);
35
+ const fanIn = String(it.fanIn).padStart(7);
36
+ const cog = String(it.cognitive).padStart(4);
37
+ const churn = String(it.churn).padStart(6);
38
+ const mi = it.maintainabilityIndex > 0 ? String(it.maintainabilityIndex).padStart(5) : ' -';
39
+ console.log(
40
+ ` ${name.padEnd(35)} ${file.padEnd(28)} ${role} ${score} ${fanIn} ${cog} ${churn} ${mi}`,
41
+ );
42
+ }
43
+
44
+ const s = data.summary;
45
+ console.log(
46
+ `\n ${s.analyzed} symbols scored (of ${s.total} total) | avg: ${s.avgScore.toFixed(2)} | max: ${s.maxScore.toFixed(2)} | sort: ${opts.sort || 'risk'}`,
47
+ );
48
+ console.log();
49
+ }