@optave/codegraph 3.1.4 → 3.1.5

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 (181) hide show
  1. package/README.md +26 -70
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +32 -12
  4. package/src/ast-analysis/shared.js +2 -2
  5. package/src/cli/commands/ast.js +2 -6
  6. package/src/cli/commands/audit.js +9 -10
  7. package/src/cli/commands/batch.js +4 -4
  8. package/src/cli/commands/branch-compare.js +1 -1
  9. package/src/cli/commands/build.js +1 -1
  10. package/src/cli/commands/cfg.js +3 -7
  11. package/src/cli/commands/check.js +12 -17
  12. package/src/cli/commands/children.js +3 -6
  13. package/src/cli/commands/co-change.js +5 -3
  14. package/src/cli/commands/communities.js +2 -6
  15. package/src/cli/commands/complexity.js +3 -2
  16. package/src/cli/commands/context.js +3 -7
  17. package/src/cli/commands/cycles.js +12 -8
  18. package/src/cli/commands/dataflow.js +3 -7
  19. package/src/cli/commands/deps.js +2 -6
  20. package/src/cli/commands/diff-impact.js +2 -6
  21. package/src/cli/commands/embed.js +1 -1
  22. package/src/cli/commands/export.js +34 -31
  23. package/src/cli/commands/exports.js +2 -6
  24. package/src/cli/commands/flow.js +3 -7
  25. package/src/cli/commands/fn-impact.js +3 -7
  26. package/src/cli/commands/impact.js +2 -6
  27. package/src/cli/commands/info.js +2 -2
  28. package/src/cli/commands/map.js +1 -1
  29. package/src/cli/commands/mcp.js +1 -1
  30. package/src/cli/commands/models.js +1 -1
  31. package/src/cli/commands/owners.js +1 -1
  32. package/src/cli/commands/path.js +2 -2
  33. package/src/cli/commands/plot.js +40 -31
  34. package/src/cli/commands/query.js +3 -7
  35. package/src/cli/commands/registry.js +2 -2
  36. package/src/cli/commands/roles.js +3 -7
  37. package/src/cli/commands/search.js +1 -1
  38. package/src/cli/commands/sequence.js +3 -7
  39. package/src/cli/commands/snapshot.js +6 -1
  40. package/src/cli/commands/stats.js +1 -1
  41. package/src/cli/commands/structure.js +5 -4
  42. package/src/cli/commands/triage.js +4 -4
  43. package/src/cli/commands/watch.js +1 -1
  44. package/src/cli/commands/where.js +2 -6
  45. package/src/cli/index.js +11 -5
  46. package/src/cli/shared/open-graph.js +13 -0
  47. package/src/cli/shared/options.js +22 -2
  48. package/src/cli.js +1 -1
  49. package/src/db/connection.js +127 -4
  50. package/src/{db.js → db/index.js} +12 -5
  51. package/src/db/migrations.js +1 -1
  52. package/src/db/query-builder.js +15 -8
  53. package/src/db/repository/base.js +1 -1
  54. package/src/db/repository/graph-read.js +3 -3
  55. package/src/db/repository/in-memory-repository.js +4 -13
  56. package/src/db/repository/nodes.js +3 -8
  57. package/src/{analysis → domain/analysis}/context.js +6 -6
  58. package/src/{analysis → domain/analysis}/dependencies.js +5 -5
  59. package/src/{analysis → domain/analysis}/exports.js +8 -4
  60. package/src/{analysis → domain/analysis}/impact.js +61 -58
  61. package/src/{analysis → domain/analysis}/module-map.js +3 -3
  62. package/src/{analysis → domain/analysis}/roles.js +4 -4
  63. package/src/{analysis → domain/analysis}/symbol-lookup.js +13 -7
  64. package/src/{builder → domain/graph/builder}/helpers.js +3 -3
  65. package/src/{builder → domain/graph/builder}/incremental.js +3 -3
  66. package/src/{builder → domain/graph/builder}/pipeline.js +4 -4
  67. package/src/{builder → domain/graph/builder}/stages/build-edges.js +2 -2
  68. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  69. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  70. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +6 -6
  71. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  72. package/src/{builder → domain/graph/builder}/stages/insert-nodes.js +1 -1
  73. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  74. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  75. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  76. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  77. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  78. package/src/{journal.js → domain/graph/journal.js} +1 -1
  79. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  80. package/src/{watcher.js → domain/graph/watcher.js} +5 -5
  81. package/src/{parser.js → domain/parser.js} +5 -5
  82. package/src/{queries.js → domain/queries.js} +16 -16
  83. package/src/{embeddings → domain/search}/generator.js +3 -3
  84. package/src/{embeddings → domain/search}/models.js +2 -2
  85. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  86. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  87. package/src/{embeddings → domain/search}/search/keyword.js +1 -1
  88. package/src/{embeddings → domain/search}/search/prepare.js +2 -2
  89. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  90. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  91. package/src/extractors/javascript.js +1 -1
  92. package/src/{ast.js → features/ast.js} +8 -8
  93. package/src/{audit.js → features/audit.js} +16 -44
  94. package/src/{batch.js → features/batch.js} +5 -5
  95. package/src/{boundaries.js → features/boundaries.js} +2 -2
  96. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  97. package/src/{cfg.js → features/cfg.js} +10 -10
  98. package/src/{check.js → features/check.js} +13 -30
  99. package/src/{cochange.js → features/cochange.js} +5 -5
  100. package/src/{communities.js → features/communities.js} +7 -7
  101. package/src/{complexity.js → features/complexity.js} +13 -13
  102. package/src/{dataflow.js → features/dataflow.js} +11 -11
  103. package/src/{export.js → features/export.js} +3 -3
  104. package/src/{flow.js → features/flow.js} +4 -4
  105. package/src/{viewer.js → features/graph-enrichment.js} +6 -6
  106. package/src/{manifesto.js → features/manifesto.js} +6 -6
  107. package/src/{owners.js → features/owners.js} +2 -2
  108. package/src/{sequence.js → features/sequence.js} +15 -15
  109. package/src/{snapshot.js → features/snapshot.js} +3 -3
  110. package/src/{structure.js → features/structure.js} +7 -7
  111. package/src/{triage.js → features/triage.js} +8 -8
  112. package/src/graph/builders/dependency.js +33 -14
  113. package/src/index.cjs +16 -0
  114. package/src/index.js +39 -39
  115. package/src/{native.js → infrastructure/native.js} +1 -1
  116. package/src/mcp/middleware.js +1 -1
  117. package/src/mcp/server.js +5 -5
  118. package/src/mcp/tool-registry.js +2 -2
  119. package/src/mcp/tools/ast-query.js +1 -1
  120. package/src/mcp/tools/audit.js +1 -1
  121. package/src/mcp/tools/batch-query.js +1 -1
  122. package/src/mcp/tools/branch-compare.js +3 -1
  123. package/src/mcp/tools/cfg.js +1 -1
  124. package/src/mcp/tools/check.js +3 -3
  125. package/src/mcp/tools/co-changes.js +1 -1
  126. package/src/mcp/tools/code-owners.js +1 -1
  127. package/src/mcp/tools/communities.js +1 -1
  128. package/src/mcp/tools/complexity.js +1 -1
  129. package/src/mcp/tools/dataflow.js +2 -2
  130. package/src/mcp/tools/execution-flow.js +2 -2
  131. package/src/mcp/tools/export-graph.js +2 -2
  132. package/src/mcp/tools/find-cycles.js +2 -2
  133. package/src/mcp/tools/list-repos.js +1 -1
  134. package/src/mcp/tools/sequence.js +1 -1
  135. package/src/mcp/tools/structure.js +1 -1
  136. package/src/mcp/tools/triage.js +2 -2
  137. package/src/{commands → presentation}/audit.js +2 -2
  138. package/src/{commands → presentation}/batch.js +1 -1
  139. package/src/{commands → presentation}/branch-compare.js +2 -2
  140. package/src/{commands → presentation}/cfg.js +1 -1
  141. package/src/{commands → presentation}/check.js +2 -2
  142. package/src/{commands → presentation}/communities.js +1 -1
  143. package/src/{commands → presentation}/complexity.js +1 -1
  144. package/src/{commands → presentation}/dataflow.js +1 -1
  145. package/src/{commands → presentation}/flow.js +2 -2
  146. package/src/{commands → presentation}/manifesto.js +1 -1
  147. package/src/{commands → presentation}/owners.js +1 -1
  148. package/src/presentation/queries-cli/exports.js +46 -0
  149. package/src/presentation/queries-cli/impact.js +198 -0
  150. package/src/presentation/queries-cli/index.js +5 -0
  151. package/src/presentation/queries-cli/inspect.js +334 -0
  152. package/src/presentation/queries-cli/overview.js +197 -0
  153. package/src/presentation/queries-cli/path.js +58 -0
  154. package/src/presentation/queries-cli.js +27 -0
  155. package/src/{commands → presentation}/query.js +1 -1
  156. package/src/presentation/result-formatter.js +126 -3
  157. package/src/{commands → presentation}/sequence.js +2 -2
  158. package/src/{commands → presentation}/structure.js +1 -1
  159. package/src/{commands → presentation}/triage.js +1 -1
  160. package/src/{constants.js → shared/constants.js} +1 -1
  161. package/src/shared/file-utils.js +2 -2
  162. package/src/shared/generators.js +2 -2
  163. package/src/shared/hierarchy.js +1 -1
  164. package/src/mcp.js +0 -2
  165. package/src/queries-cli.js +0 -866
  166. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  167. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  168. /package/src/{embeddings → domain/search}/index.js +0 -0
  169. /package/src/{embeddings → domain/search}/search/filters.js +0 -0
  170. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  171. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  172. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  173. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  174. /package/src/{config.js → infrastructure/config.js} +0 -0
  175. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  176. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  177. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  178. /package/src/{commands → presentation}/cochange.js +0 -0
  179. /package/src/{errors.js → shared/errors.js} +0 -0
  180. /package/src/{kinds.js → shared/kinds.js} +0 -0
  181. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -0,0 +1,197 @@
1
+ import path from 'node:path';
2
+ import { kindIcon, moduleMapData, rolesData, statsData } from '../../domain/queries.js';
3
+ import { outputResult } from '../../infrastructure/result-formatter.js';
4
+
5
+ export async function stats(customDbPath, opts = {}) {
6
+ const data = statsData(customDbPath, { noTests: opts.noTests });
7
+
8
+ // Community detection summary (async import for lazy-loading)
9
+ try {
10
+ const { communitySummaryForStats } = await import('../../features/communities.js');
11
+ data.communities = communitySummaryForStats(customDbPath, { noTests: opts.noTests });
12
+ } catch {
13
+ /* graphology may not be available */
14
+ }
15
+
16
+ if (outputResult(data, null, opts)) return;
17
+
18
+ // Human-readable output
19
+ console.log('\n# Codegraph Stats\n');
20
+
21
+ // Nodes
22
+ console.log(`Nodes: ${data.nodes.total} total`);
23
+ const kindEntries = Object.entries(data.nodes.byKind).sort((a, b) => b[1] - a[1]);
24
+ const kindParts = kindEntries.map(([k, v]) => `${k} ${v}`);
25
+ for (let i = 0; i < kindParts.length; i += 3) {
26
+ const row = kindParts
27
+ .slice(i, i + 3)
28
+ .map((p) => p.padEnd(18))
29
+ .join('');
30
+ console.log(` ${row}`);
31
+ }
32
+
33
+ // Edges
34
+ console.log(`\nEdges: ${data.edges.total} total`);
35
+ const edgeEntries = Object.entries(data.edges.byKind).sort((a, b) => b[1] - a[1]);
36
+ const edgeParts = edgeEntries.map(([k, v]) => `${k} ${v}`);
37
+ for (let i = 0; i < edgeParts.length; i += 3) {
38
+ const row = edgeParts
39
+ .slice(i, i + 3)
40
+ .map((p) => p.padEnd(18))
41
+ .join('');
42
+ console.log(` ${row}`);
43
+ }
44
+
45
+ // Files
46
+ console.log(`\nFiles: ${data.files.total} (${data.files.languages} languages)`);
47
+ const langEntries = Object.entries(data.files.byLanguage).sort((a, b) => b[1] - a[1]);
48
+ const langParts = langEntries.map(([k, v]) => `${k} ${v}`);
49
+ for (let i = 0; i < langParts.length; i += 3) {
50
+ const row = langParts
51
+ .slice(i, i + 3)
52
+ .map((p) => p.padEnd(18))
53
+ .join('');
54
+ console.log(` ${row}`);
55
+ }
56
+
57
+ // Cycles
58
+ console.log(
59
+ `\nCycles: ${data.cycles.fileLevel} file-level, ${data.cycles.functionLevel} function-level`,
60
+ );
61
+
62
+ // Hotspots
63
+ if (data.hotspots.length > 0) {
64
+ console.log(`\nTop ${data.hotspots.length} coupling hotspots:`);
65
+ for (let i = 0; i < data.hotspots.length; i++) {
66
+ const h = data.hotspots[i];
67
+ console.log(
68
+ ` ${String(i + 1).padStart(2)}. ${h.file.padEnd(35)} fan-in: ${String(h.fanIn).padStart(3)} fan-out: ${String(h.fanOut).padStart(3)}`,
69
+ );
70
+ }
71
+ }
72
+
73
+ // Embeddings
74
+ if (data.embeddings) {
75
+ const e = data.embeddings;
76
+ console.log(
77
+ `\nEmbeddings: ${e.count} vectors (${e.model || 'unknown'}, ${e.dim || '?'}d) built ${e.builtAt || 'unknown'}`,
78
+ );
79
+ } else {
80
+ console.log('\nEmbeddings: not built');
81
+ }
82
+
83
+ // Quality
84
+ if (data.quality) {
85
+ const q = data.quality;
86
+ const cc = q.callerCoverage;
87
+ const cf = q.callConfidence;
88
+ console.log(`\nGraph Quality: ${q.score}/100`);
89
+ console.log(
90
+ ` Caller coverage: ${(cc.ratio * 100).toFixed(1)}% (${cc.covered}/${cc.total} functions have >=1 caller)`,
91
+ );
92
+ console.log(
93
+ ` Call confidence: ${(cf.ratio * 100).toFixed(1)}% (${cf.highConf}/${cf.total} call edges are high-confidence)`,
94
+ );
95
+ if (q.falsePositiveWarnings.length > 0) {
96
+ console.log(' False-positive warnings:');
97
+ for (const fp of q.falsePositiveWarnings) {
98
+ console.log(` ! ${fp.name} (${fp.callerCount} callers) -- ${fp.file}:${fp.line}`);
99
+ }
100
+ }
101
+ }
102
+
103
+ // Roles
104
+ if (data.roles && Object.keys(data.roles).length > 0) {
105
+ const total = Object.values(data.roles).reduce((a, b) => a + b, 0);
106
+ console.log(`\nRoles: ${total} classified symbols`);
107
+ const roleParts = Object.entries(data.roles)
108
+ .sort((a, b) => b[1] - a[1])
109
+ .map(([k, v]) => `${k} ${v}`);
110
+ for (let i = 0; i < roleParts.length; i += 3) {
111
+ const row = roleParts
112
+ .slice(i, i + 3)
113
+ .map((p) => p.padEnd(18))
114
+ .join('');
115
+ console.log(` ${row}`);
116
+ }
117
+ }
118
+
119
+ // Complexity
120
+ if (data.complexity) {
121
+ const cx = data.complexity;
122
+ const miPart = cx.avgMI != null ? ` | avg MI: ${cx.avgMI} | min MI: ${cx.minMI}` : '';
123
+ console.log(
124
+ `\nComplexity: ${cx.analyzed} functions | avg cognitive: ${cx.avgCognitive} | avg cyclomatic: ${cx.avgCyclomatic} | max cognitive: ${cx.maxCognitive}${miPart}`,
125
+ );
126
+ }
127
+
128
+ // Communities
129
+ if (data.communities) {
130
+ const cm = data.communities;
131
+ console.log(
132
+ `\nCommunities: ${cm.communityCount} detected | modularity: ${cm.modularity} | drift: ${cm.driftScore}%`,
133
+ );
134
+ }
135
+
136
+ console.log();
137
+ }
138
+
139
+ export function moduleMap(customDbPath, limit = 20, opts = {}) {
140
+ const data = moduleMapData(customDbPath, limit, { noTests: opts.noTests });
141
+ if (outputResult(data, 'topNodes', opts)) return;
142
+
143
+ console.log(`\nModule map (top ${limit} most-connected nodes):\n`);
144
+ const dirs = new Map();
145
+ for (const n of data.topNodes) {
146
+ if (!dirs.has(n.dir)) dirs.set(n.dir, []);
147
+ dirs.get(n.dir).push(n);
148
+ }
149
+ for (const [dir, files] of [...dirs].sort()) {
150
+ console.log(` [${dir}/]`);
151
+ for (const f of files) {
152
+ const coupling = f.inEdges + f.outEdges;
153
+ const bar = '#'.repeat(Math.min(coupling, 40));
154
+ console.log(
155
+ ` ${path.basename(f.file).padEnd(35)} <-${String(f.inEdges).padStart(3)} ->${String(f.outEdges).padStart(3)} =${String(coupling).padStart(3)} ${bar}`,
156
+ );
157
+ }
158
+ }
159
+ console.log(
160
+ `\n Total: ${data.stats.totalFiles} files, ${data.stats.totalNodes} symbols, ${data.stats.totalEdges} edges\n`,
161
+ );
162
+ }
163
+
164
+ export function roles(customDbPath, opts = {}) {
165
+ const data = rolesData(customDbPath, opts);
166
+ if (outputResult(data, 'symbols', opts)) return;
167
+
168
+ if (data.count === 0) {
169
+ console.log('No classified symbols found. Run "codegraph build" first.');
170
+ return;
171
+ }
172
+
173
+ const total = data.count;
174
+ console.log(`\nNode roles (${total} symbols):\n`);
175
+
176
+ const summaryParts = Object.entries(data.summary)
177
+ .sort((a, b) => b[1] - a[1])
178
+ .map(([role, count]) => `${role}: ${count}`);
179
+ console.log(` ${summaryParts.join(' ')}\n`);
180
+
181
+ const byRole = {};
182
+ for (const s of data.symbols) {
183
+ if (!byRole[s.role]) byRole[s.role] = [];
184
+ byRole[s.role].push(s);
185
+ }
186
+
187
+ for (const [role, symbols] of Object.entries(byRole)) {
188
+ console.log(`## ${role} (${symbols.length})`);
189
+ for (const s of symbols.slice(0, 30)) {
190
+ console.log(` ${kindIcon(s.kind)} ${s.name} ${s.file}:${s.line}`);
191
+ }
192
+ if (symbols.length > 30) {
193
+ console.log(` ... and ${symbols.length - 30} more`);
194
+ }
195
+ console.log();
196
+ }
197
+ }
@@ -0,0 +1,58 @@
1
+ import { kindIcon, pathData } from '../../domain/queries.js';
2
+ import { outputResult } from '../../infrastructure/result-formatter.js';
3
+
4
+ export function symbolPath(from, to, customDbPath, opts = {}) {
5
+ const data = pathData(from, to, customDbPath, opts);
6
+ if (outputResult(data, null, opts)) return;
7
+
8
+ if (data.error) {
9
+ console.log(data.error);
10
+ return;
11
+ }
12
+
13
+ if (!data.found) {
14
+ const dir = data.reverse ? 'reverse ' : '';
15
+ console.log(`No ${dir}path from "${from}" to "${to}" within ${data.maxDepth} hops.`);
16
+ if (data.fromCandidates.length > 1) {
17
+ console.log(
18
+ `\n "${from}" matched ${data.fromCandidates.length} symbols — using top match: ${data.fromCandidates[0].name} (${data.fromCandidates[0].file}:${data.fromCandidates[0].line})`,
19
+ );
20
+ }
21
+ if (data.toCandidates.length > 1) {
22
+ console.log(
23
+ ` "${to}" matched ${data.toCandidates.length} symbols — using top match: ${data.toCandidates[0].name} (${data.toCandidates[0].file}:${data.toCandidates[0].line})`,
24
+ );
25
+ }
26
+ return;
27
+ }
28
+
29
+ if (data.hops === 0) {
30
+ console.log(`\n"${from}" and "${to}" resolve to the same symbol (0 hops):`);
31
+ const n = data.path[0];
32
+ console.log(` ${kindIcon(n.kind)} ${n.name} (${n.kind}) -- ${n.file}:${n.line}\n`);
33
+ return;
34
+ }
35
+
36
+ const dir = data.reverse ? ' (reverse)' : '';
37
+ console.log(
38
+ `\nPath from ${from} to ${to} (${data.hops} ${data.hops === 1 ? 'hop' : 'hops'})${dir}:\n`,
39
+ );
40
+ for (let i = 0; i < data.path.length; i++) {
41
+ const n = data.path[i];
42
+ const indent = ' '.repeat(i + 1);
43
+ if (i === 0) {
44
+ console.log(`${indent}${kindIcon(n.kind)} ${n.name} (${n.kind}) -- ${n.file}:${n.line}`);
45
+ } else {
46
+ console.log(
47
+ `${indent}--[${n.edgeKind}]--> ${kindIcon(n.kind)} ${n.name} (${n.kind}) -- ${n.file}:${n.line}`,
48
+ );
49
+ }
50
+ }
51
+
52
+ if (data.alternateCount > 0) {
53
+ console.log(
54
+ `\n (${data.alternateCount} alternate shortest ${data.alternateCount === 1 ? 'path' : 'paths'} at same depth)`,
55
+ );
56
+ }
57
+ console.log();
58
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * queries-cli.js — barrel re-export for backward compatibility.
3
+ *
4
+ * The actual implementations live in queries-cli/ split by concern:
5
+ * path.js — symbolPath
6
+ * overview.js — stats, moduleMap, roles
7
+ * inspect.js — where, queryName, context, children, explain
8
+ * impact.js — fileDeps, fnDeps, impactAnalysis, fnImpact, diffImpact
9
+ * exports.js — fileExports
10
+ */
11
+ export {
12
+ children,
13
+ context,
14
+ diffImpact,
15
+ explain,
16
+ fileDeps,
17
+ fileExports,
18
+ fnDeps,
19
+ fnImpact,
20
+ impactAnalysis,
21
+ moduleMap,
22
+ queryName,
23
+ roles,
24
+ stats,
25
+ symbolPath,
26
+ where,
27
+ } from './queries-cli/index.js';
@@ -18,4 +18,4 @@ export {
18
18
  stats,
19
19
  symbolPath,
20
20
  where,
21
- } from '../queries-cli.js';
21
+ } from './queries-cli/index.js';
@@ -1,11 +1,128 @@
1
- import { printNdjson } from '../paginate.js';
1
+ import { printNdjson } from '../shared/paginate.js';
2
+ import { formatTable, truncEnd } from './table.js';
2
3
 
3
4
  /**
4
- * Shared JSON / NDJSON output dispatch for CLI wrappers.
5
+ * Flatten a nested object into dot-notation keys.
6
+ * Arrays are JSON-stringified; nested objects are recursed.
7
+ *
8
+ * Note: this assumes input objects do not contain literal dot-notation keys
9
+ * (e.g. `{ "a.b": 1 }`). If they do, flattened keys will silently collide
10
+ * with nested paths (e.g. `{ a: { b: 2 } }` also produces `"a.b"`).
11
+ */
12
+ function flattenObject(obj, prefix = '') {
13
+ const result = {};
14
+ for (const [key, value] of Object.entries(obj)) {
15
+ const fullKey = prefix ? `${prefix}.${key}` : key;
16
+ if (
17
+ value !== null &&
18
+ typeof value === 'object' &&
19
+ !Array.isArray(value) &&
20
+ Object.getPrototypeOf(value) === Object.prototype
21
+ ) {
22
+ Object.assign(result, flattenObject(value, fullKey));
23
+ } else if (Array.isArray(value)) {
24
+ result[fullKey] = JSON.stringify(value);
25
+ } else {
26
+ result[fullKey] = value;
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+
32
+ /**
33
+ * Flatten items array and derive column names.
34
+ * Shared by printCsv and printAutoTable.
35
+ * @returns {{ flatItems: object[], columns: string[] } | null}
36
+ */
37
+ function prepareFlatItems(data, field) {
38
+ const items = field ? data[field] : data;
39
+ if (!Array.isArray(items)) return null;
40
+
41
+ const flatItems = items.map((item) =>
42
+ typeof item === 'object' && item !== null && !Array.isArray(item)
43
+ ? flattenObject(item)
44
+ : { value: item },
45
+ );
46
+ const columns = (() => {
47
+ const keys = new Set();
48
+ for (const item of flatItems) for (const key of Object.keys(item)) keys.add(key);
49
+ return [...keys];
50
+ })();
51
+ if (columns.length === 0) columns.push('value');
52
+
53
+ return { flatItems, columns };
54
+ }
55
+
56
+ /** Escape a value for CSV output (LF line endings). */
57
+ function escapeCsv(val) {
58
+ const str = val == null ? '' : String(val);
59
+ if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) {
60
+ return `"${str.replace(/"/g, '""')}"`;
61
+ }
62
+ return str;
63
+ }
64
+
65
+ /**
66
+ * Print data as CSV to stdout.
67
+ * @param {object} data - Result object from a *Data() function
68
+ * @param {string} field - Array field name (e.g. 'results')
69
+ */
70
+ function printCsv(data, field) {
71
+ const prepared = prepareFlatItems(data, field);
72
+ if (!prepared) return false;
73
+ const { flatItems, columns } = prepared;
74
+
75
+ console.log(columns.map(escapeCsv).join(','));
76
+ for (const row of flatItems) {
77
+ console.log(columns.map((col) => escapeCsv(row[col])).join(','));
78
+ }
79
+ return true;
80
+ }
81
+
82
+ const MAX_COL_WIDTH = 40;
83
+
84
+ /**
85
+ * Print data as an aligned table to stdout.
86
+ * @param {object} data - Result object from a *Data() function
87
+ * @param {string} field - Array field name (e.g. 'results')
88
+ */
89
+ function printAutoTable(data, field) {
90
+ const prepared = prepareFlatItems(data, field);
91
+ if (!prepared) return false;
92
+ const { flatItems, columns } = prepared;
93
+
94
+ const colDefs = columns.map((col) => {
95
+ const maxLen = flatItems.reduce(
96
+ (max, item) => Math.max(max, String(item[col] ?? '').length),
97
+ col.length,
98
+ );
99
+ const isNumeric =
100
+ flatItems.length > 0 &&
101
+ flatItems.every((item) => {
102
+ const v = item[col];
103
+ return v == null || v === '' || (typeof v !== 'boolean' && Number.isFinite(Number(v)));
104
+ });
105
+ return {
106
+ header: col,
107
+ width: Math.min(maxLen, MAX_COL_WIDTH),
108
+ align: isNumeric ? 'right' : 'left',
109
+ };
110
+ });
111
+
112
+ const rows = flatItems.map((item) =>
113
+ columns.map((col) => truncEnd(String(item[col] ?? ''), MAX_COL_WIDTH)),
114
+ );
115
+
116
+ console.log(formatTable({ columns: colDefs, rows }));
117
+ return true;
118
+ }
119
+
120
+ /**
121
+ * Shared JSON / NDJSON / table / CSV output dispatch for CLI wrappers.
5
122
  *
6
123
  * @param {object} data - Result object from a *Data() function
7
124
  * @param {string} field - Array field name for NDJSON streaming (e.g. 'results')
8
- * @param {object} opts - CLI options ({ json?, ndjson? })
125
+ * @param {object} opts - CLI options ({ json?, ndjson?, table?, csv? })
9
126
  * @returns {boolean} true if output was handled (caller should return early)
10
127
  */
11
128
  export function outputResult(data, field, opts) {
@@ -17,5 +134,11 @@ export function outputResult(data, field, opts) {
17
134
  console.log(JSON.stringify(data, null, 2));
18
135
  return true;
19
136
  }
137
+ if (opts.csv) {
138
+ return printCsv(data, field) !== false;
139
+ }
140
+ if (opts.table) {
141
+ return printAutoTable(data, field) !== false;
142
+ }
20
143
  return false;
21
144
  }
@@ -1,6 +1,6 @@
1
+ import { kindIcon } from '../domain/queries.js';
2
+ import { sequenceData, sequenceToMermaid } from '../features/sequence.js';
1
3
  import { outputResult } from '../infrastructure/result-formatter.js';
2
- import { kindIcon } from '../queries.js';
3
- import { sequenceData, sequenceToMermaid } from '../sequence.js';
4
4
 
5
5
  /**
6
6
  * CLI entry point — format sequence data as mermaid, JSON, or ndjson.
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { hotspotsData, moduleBoundariesData, structureData } from '../structure.js';
2
+ import { hotspotsData, moduleBoundariesData, structureData } from '../features/structure.js';
3
3
 
4
4
  export { hotspotsData, moduleBoundariesData, structureData };
5
5
 
@@ -1,5 +1,5 @@
1
+ import { triageData } from '../features/triage.js';
1
2
  import { outputResult } from '../infrastructure/result-formatter.js';
2
- import { triageData } from '../triage.js';
3
3
 
4
4
  /**
5
5
  * Print triage results to console.
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { SUPPORTED_EXTENSIONS } from './parser.js';
2
+ import { SUPPORTED_EXTENSIONS } from '../domain/parser.js';
3
3
 
4
4
  export const IGNORE_DIRS = new Set([
5
5
  'node_modules',
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { debug } from '../logger.js';
4
- import { LANGUAGE_REGISTRY } from '../parser.js';
3
+ import { LANGUAGE_REGISTRY } from '../domain/parser.js';
4
+ import { debug } from '../infrastructure/logger.js';
5
5
 
6
6
  /**
7
7
  * Resolve a file path relative to repoRoot, rejecting traversal outside the repo.
@@ -1,6 +1,6 @@
1
- import { iterateFunctionNodes, openReadonlyOrFail } from '../db.js';
1
+ import { iterateFunctionNodes, openReadonlyOrFail } from '../db/index.js';
2
2
  import { isTestFile } from '../infrastructure/test-filter.js';
3
- import { ALL_SYMBOL_KINDS } from '../kinds.js';
3
+ import { ALL_SYMBOL_KINDS } from './kinds.js';
4
4
 
5
5
  /**
6
6
  * Generator: stream functions one-by-one using .iterate() for memory efficiency.
@@ -1,4 +1,4 @@
1
- import { getClassHierarchy } from '../db.js';
1
+ import { getClassHierarchy } from '../db/index.js';
2
2
 
3
3
  export function resolveMethodViaHierarchy(db, methodName) {
4
4
  const methods = db
package/src/mcp.js DELETED
@@ -1,2 +0,0 @@
1
- export { startMCPServer } from './mcp/server.js';
2
- export { buildToolList, TOOLS } from './mcp/tool-registry.js';