@optave/codegraph 3.1.3 → 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 (232) hide show
  1. package/README.md +38 -84
  2. package/package.json +13 -8
  3. package/src/ast-analysis/engine.js +32 -12
  4. package/src/ast-analysis/shared.js +6 -5
  5. package/src/cli/commands/ast.js +22 -0
  6. package/src/cli/commands/audit.js +45 -0
  7. package/src/cli/commands/batch.js +68 -0
  8. package/src/cli/commands/branch-compare.js +21 -0
  9. package/src/cli/commands/build.js +26 -0
  10. package/src/cli/commands/cfg.js +26 -0
  11. package/src/cli/commands/check.js +74 -0
  12. package/src/cli/commands/children.js +28 -0
  13. package/src/cli/commands/co-change.js +67 -0
  14. package/src/cli/commands/communities.js +19 -0
  15. package/src/cli/commands/complexity.js +46 -0
  16. package/src/cli/commands/context.js +30 -0
  17. package/src/cli/commands/cycles.js +32 -0
  18. package/src/cli/commands/dataflow.js +28 -0
  19. package/src/cli/commands/deps.js +12 -0
  20. package/src/cli/commands/diff-impact.js +26 -0
  21. package/src/cli/commands/embed.js +30 -0
  22. package/src/cli/commands/export.js +78 -0
  23. package/src/cli/commands/exports.js +14 -0
  24. package/src/cli/commands/flow.js +32 -0
  25. package/src/cli/commands/fn-impact.js +26 -0
  26. package/src/cli/commands/impact.js +12 -0
  27. package/src/cli/commands/info.js +76 -0
  28. package/src/cli/commands/map.js +19 -0
  29. package/src/cli/commands/mcp.js +18 -0
  30. package/src/cli/commands/models.js +19 -0
  31. package/src/cli/commands/owners.js +25 -0
  32. package/src/cli/commands/path.js +36 -0
  33. package/src/cli/commands/plot.js +89 -0
  34. package/src/cli/commands/query.js +45 -0
  35. package/src/cli/commands/registry.js +100 -0
  36. package/src/cli/commands/roles.js +30 -0
  37. package/src/cli/commands/search.js +42 -0
  38. package/src/cli/commands/sequence.js +28 -0
  39. package/src/cli/commands/snapshot.js +66 -0
  40. package/src/cli/commands/stats.js +15 -0
  41. package/src/cli/commands/structure.js +33 -0
  42. package/src/cli/commands/triage.js +78 -0
  43. package/src/cli/commands/watch.js +12 -0
  44. package/src/cli/commands/where.js +20 -0
  45. package/src/cli/index.js +124 -0
  46. package/src/cli/shared/open-graph.js +13 -0
  47. package/src/cli/shared/options.js +59 -0
  48. package/src/cli/shared/output.js +1 -0
  49. package/src/cli.js +11 -1522
  50. package/src/db/connection.js +130 -7
  51. package/src/{db.js → db/index.js} +17 -5
  52. package/src/db/migrations.js +42 -1
  53. package/src/db/query-builder.js +20 -12
  54. package/src/db/repository/base.js +201 -0
  55. package/src/db/repository/graph-read.js +7 -4
  56. package/src/db/repository/in-memory-repository.js +575 -0
  57. package/src/db/repository/index.js +5 -1
  58. package/src/db/repository/nodes.js +60 -6
  59. package/src/db/repository/sqlite-repository.js +219 -0
  60. package/src/domain/analysis/context.js +408 -0
  61. package/src/domain/analysis/dependencies.js +341 -0
  62. package/src/domain/analysis/exports.js +134 -0
  63. package/src/domain/analysis/impact.js +466 -0
  64. package/src/domain/analysis/module-map.js +322 -0
  65. package/src/domain/analysis/roles.js +45 -0
  66. package/src/domain/analysis/symbol-lookup.js +238 -0
  67. package/src/domain/graph/builder/context.js +85 -0
  68. package/src/domain/graph/builder/helpers.js +218 -0
  69. package/src/domain/graph/builder/incremental.js +178 -0
  70. package/src/domain/graph/builder/pipeline.js +130 -0
  71. package/src/domain/graph/builder/stages/build-edges.js +297 -0
  72. package/src/domain/graph/builder/stages/build-structure.js +113 -0
  73. package/src/domain/graph/builder/stages/collect-files.js +44 -0
  74. package/src/domain/graph/builder/stages/detect-changes.js +413 -0
  75. package/src/domain/graph/builder/stages/finalize.js +139 -0
  76. package/src/domain/graph/builder/stages/insert-nodes.js +195 -0
  77. package/src/domain/graph/builder/stages/parse-files.js +28 -0
  78. package/src/domain/graph/builder/stages/resolve-imports.js +143 -0
  79. package/src/domain/graph/builder/stages/run-analyses.js +44 -0
  80. package/src/domain/graph/builder.js +11 -0
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/domain/graph/cycles.js +82 -0
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +3 -3
  85. package/src/{watcher.js → domain/graph/watcher.js} +10 -150
  86. package/src/{parser.js → domain/parser.js} +5 -5
  87. package/src/domain/queries.js +48 -0
  88. package/src/domain/search/generator.js +163 -0
  89. package/src/domain/search/index.js +13 -0
  90. package/src/domain/search/models.js +218 -0
  91. package/src/domain/search/search/cli-formatter.js +151 -0
  92. package/src/domain/search/search/filters.js +46 -0
  93. package/src/domain/search/search/hybrid.js +121 -0
  94. package/src/domain/search/search/keyword.js +68 -0
  95. package/src/domain/search/search/prepare.js +66 -0
  96. package/src/domain/search/search/semantic.js +145 -0
  97. package/src/domain/search/stores/fts5.js +27 -0
  98. package/src/domain/search/stores/sqlite-blob.js +24 -0
  99. package/src/domain/search/strategies/source.js +14 -0
  100. package/src/domain/search/strategies/structured.js +43 -0
  101. package/src/domain/search/strategies/text-utils.js +43 -0
  102. package/src/extractors/csharp.js +10 -2
  103. package/src/extractors/go.js +3 -1
  104. package/src/extractors/helpers.js +71 -0
  105. package/src/extractors/java.js +9 -2
  106. package/src/extractors/javascript.js +39 -2
  107. package/src/extractors/php.js +3 -1
  108. package/src/extractors/python.js +14 -3
  109. package/src/extractors/rust.js +3 -1
  110. package/src/{ast.js → features/ast.js} +8 -8
  111. package/src/{audit.js → features/audit.js} +16 -44
  112. package/src/{batch.js → features/batch.js} +6 -5
  113. package/src/{boundaries.js → features/boundaries.js} +2 -2
  114. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  115. package/src/{cfg.js → features/cfg.js} +11 -12
  116. package/src/{check.js → features/check.js} +13 -30
  117. package/src/{cochange.js → features/cochange.js} +5 -5
  118. package/src/{communities.js → features/communities.js} +18 -90
  119. package/src/{complexity.js → features/complexity.js} +13 -13
  120. package/src/{dataflow.js → features/dataflow.js} +12 -13
  121. package/src/features/export.js +378 -0
  122. package/src/{flow.js → features/flow.js} +4 -4
  123. package/src/features/graph-enrichment.js +327 -0
  124. package/src/{manifesto.js → features/manifesto.js} +6 -6
  125. package/src/{owners.js → features/owners.js} +2 -2
  126. package/src/{sequence.js → features/sequence.js} +16 -52
  127. package/src/{snapshot.js → features/snapshot.js} +8 -7
  128. package/src/{structure.js → features/structure.js} +20 -45
  129. package/src/{triage.js → features/triage.js} +27 -79
  130. package/src/graph/algorithms/bfs.js +49 -0
  131. package/src/graph/algorithms/centrality.js +16 -0
  132. package/src/graph/algorithms/index.js +5 -0
  133. package/src/graph/algorithms/louvain.js +26 -0
  134. package/src/graph/algorithms/shortest-path.js +41 -0
  135. package/src/graph/algorithms/tarjan.js +49 -0
  136. package/src/graph/builders/dependency.js +110 -0
  137. package/src/graph/builders/index.js +3 -0
  138. package/src/graph/builders/structure.js +40 -0
  139. package/src/graph/builders/temporal.js +33 -0
  140. package/src/graph/classifiers/index.js +2 -0
  141. package/src/graph/classifiers/risk.js +85 -0
  142. package/src/graph/classifiers/roles.js +64 -0
  143. package/src/graph/index.js +13 -0
  144. package/src/graph/model.js +230 -0
  145. package/src/index.cjs +16 -0
  146. package/src/index.js +42 -219
  147. package/src/{native.js → infrastructure/native.js} +3 -1
  148. package/src/infrastructure/result-formatter.js +2 -21
  149. package/src/mcp/index.js +2 -0
  150. package/src/mcp/middleware.js +26 -0
  151. package/src/mcp/server.js +128 -0
  152. package/src/{mcp.js → mcp/tool-registry.js} +6 -675
  153. package/src/mcp/tools/ast-query.js +14 -0
  154. package/src/mcp/tools/audit.js +21 -0
  155. package/src/mcp/tools/batch-query.js +11 -0
  156. package/src/mcp/tools/branch-compare.js +12 -0
  157. package/src/mcp/tools/cfg.js +21 -0
  158. package/src/mcp/tools/check.js +43 -0
  159. package/src/mcp/tools/co-changes.js +20 -0
  160. package/src/mcp/tools/code-owners.js +12 -0
  161. package/src/mcp/tools/communities.js +15 -0
  162. package/src/mcp/tools/complexity.js +18 -0
  163. package/src/mcp/tools/context.js +17 -0
  164. package/src/mcp/tools/dataflow.js +26 -0
  165. package/src/mcp/tools/diff-impact.js +24 -0
  166. package/src/mcp/tools/execution-flow.js +26 -0
  167. package/src/mcp/tools/export-graph.js +57 -0
  168. package/src/mcp/tools/file-deps.js +12 -0
  169. package/src/mcp/tools/file-exports.js +13 -0
  170. package/src/mcp/tools/find-cycles.js +15 -0
  171. package/src/mcp/tools/fn-impact.js +15 -0
  172. package/src/mcp/tools/impact-analysis.js +12 -0
  173. package/src/mcp/tools/index.js +71 -0
  174. package/src/mcp/tools/list-functions.js +14 -0
  175. package/src/mcp/tools/list-repos.js +11 -0
  176. package/src/mcp/tools/module-map.js +6 -0
  177. package/src/mcp/tools/node-roles.js +14 -0
  178. package/src/mcp/tools/path.js +12 -0
  179. package/src/mcp/tools/query.js +30 -0
  180. package/src/mcp/tools/semantic-search.js +65 -0
  181. package/src/mcp/tools/sequence.js +17 -0
  182. package/src/mcp/tools/structure.js +15 -0
  183. package/src/mcp/tools/symbol-children.js +14 -0
  184. package/src/mcp/tools/triage.js +35 -0
  185. package/src/mcp/tools/where.js +13 -0
  186. package/src/{commands → presentation}/audit.js +2 -2
  187. package/src/{commands → presentation}/batch.js +1 -1
  188. package/src/{commands → presentation}/branch-compare.js +2 -2
  189. package/src/{commands → presentation}/cfg.js +1 -1
  190. package/src/{commands → presentation}/check.js +6 -6
  191. package/src/presentation/colors.js +44 -0
  192. package/src/{commands → presentation}/communities.js +1 -1
  193. package/src/{commands → presentation}/complexity.js +1 -1
  194. package/src/{commands → presentation}/dataflow.js +1 -1
  195. package/src/presentation/export.js +444 -0
  196. package/src/{commands → presentation}/flow.js +2 -2
  197. package/src/{commands → presentation}/manifesto.js +4 -4
  198. package/src/{commands → presentation}/owners.js +1 -1
  199. package/src/presentation/queries-cli/exports.js +46 -0
  200. package/src/presentation/queries-cli/impact.js +198 -0
  201. package/src/presentation/queries-cli/index.js +5 -0
  202. package/src/presentation/queries-cli/inspect.js +334 -0
  203. package/src/presentation/queries-cli/overview.js +197 -0
  204. package/src/presentation/queries-cli/path.js +58 -0
  205. package/src/presentation/queries-cli.js +27 -0
  206. package/src/{commands → presentation}/query.js +1 -1
  207. package/src/presentation/result-formatter.js +144 -0
  208. package/src/presentation/sequence-renderer.js +43 -0
  209. package/src/{commands → presentation}/sequence.js +2 -2
  210. package/src/{commands → presentation}/structure.js +2 -2
  211. package/src/presentation/table.js +47 -0
  212. package/src/{commands → presentation}/triage.js +1 -1
  213. package/src/{viewer.js → presentation/viewer.js} +68 -382
  214. package/src/{constants.js → shared/constants.js} +1 -1
  215. package/src/shared/errors.js +78 -0
  216. package/src/shared/file-utils.js +153 -0
  217. package/src/shared/generators.js +125 -0
  218. package/src/shared/hierarchy.js +27 -0
  219. package/src/shared/normalize.js +59 -0
  220. package/src/builder.js +0 -1486
  221. package/src/cycles.js +0 -137
  222. package/src/embedder.js +0 -1097
  223. package/src/export.js +0 -681
  224. package/src/queries-cli.js +0 -866
  225. package/src/queries.js +0 -2289
  226. /package/src/{config.js → infrastructure/config.js} +0 -0
  227. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  228. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  229. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  230. /package/src/{commands → presentation}/cochange.js +0 -0
  231. /package/src/{kinds.js → shared/kinds.js} +0 -0
  232. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -0,0 +1,43 @@
1
+ import { findCalleeNames, findCallerNames } from '../../../db/index.js';
2
+ import { extractLeadingComment, splitIdentifier } from './text-utils.js';
3
+
4
+ /**
5
+ * Build graph-enriched text for a symbol using dependency context.
6
+ * Produces compact, semantic text (~100 tokens) instead of full source code.
7
+ */
8
+ export function buildStructuredText(node, file, lines, db) {
9
+ const readable = splitIdentifier(node.name);
10
+ const parts = [`${node.kind} ${node.name} (${readable}) in ${file}`];
11
+ const startLine = Math.max(0, node.line - 1);
12
+
13
+ // Extract parameters from signature (best-effort, single-line)
14
+ const sigLine = lines[startLine] || '';
15
+ const paramMatch = sigLine.match(/\(([^)]*)\)/);
16
+ if (paramMatch?.[1]?.trim()) {
17
+ parts.push(`Parameters: ${paramMatch[1].trim()}`);
18
+ }
19
+
20
+ // Graph context: callees (capped at 10)
21
+ const callees = findCalleeNames(db, node.id);
22
+ if (callees.length > 0) {
23
+ parts.push(`Calls: ${callees.slice(0, 10).join(', ')}`);
24
+ }
25
+
26
+ // Graph context: callers (capped at 10)
27
+ const callers = findCallerNames(db, node.id);
28
+ if (callers.length > 0) {
29
+ parts.push(`Called by: ${callers.slice(0, 10).join(', ')}`);
30
+ }
31
+
32
+ // Leading comment (high semantic value) or first few lines of code
33
+ const comment = extractLeadingComment(lines, startLine);
34
+ if (comment) {
35
+ parts.push(comment);
36
+ } else {
37
+ const endLine = Math.min(lines.length, startLine + 4);
38
+ const snippet = lines.slice(startLine, endLine).join('\n').trim();
39
+ if (snippet) parts.push(snippet);
40
+ }
41
+
42
+ return parts.join('\n');
43
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Split an identifier into readable words.
3
+ * camelCase/PascalCase -> "camel Case", snake_case -> "snake case", kebab-case -> "kebab case"
4
+ */
5
+ export function splitIdentifier(name) {
6
+ return name
7
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
8
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
9
+ .replace(/[_-]+/g, ' ')
10
+ .trim();
11
+ }
12
+
13
+ /**
14
+ * Extract leading comment text (JSDoc, //, #, etc.) above a function line.
15
+ * Returns the cleaned comment text or null if none found.
16
+ */
17
+ export function extractLeadingComment(lines, fnLineIndex) {
18
+ if (fnLineIndex > lines.length) return null;
19
+ const raw = [];
20
+ for (let i = fnLineIndex - 1; i >= Math.max(0, fnLineIndex - 15); i--) {
21
+ if (i >= lines.length) continue;
22
+ const trimmed = lines[i].trim();
23
+ if (/^(\/\/|\/\*|\*\/|\*|#|\/\/\/)/.test(trimmed)) {
24
+ raw.unshift(trimmed);
25
+ } else if (trimmed === '') {
26
+ if (raw.length > 0) break;
27
+ } else {
28
+ break;
29
+ }
30
+ }
31
+ if (raw.length === 0) return null;
32
+ return raw
33
+ .map((line) =>
34
+ line
35
+ .replace(/^\/\*\*?\s?|\*\/$/g, '') // opening /** or /* and closing */
36
+ .replace(/^\*\s?/, '') // middle * lines
37
+ .replace(/^\/\/\/?\s?/, '') // // or ///
38
+ .replace(/^#\s?/, '') // # (Python/Ruby)
39
+ .trim(),
40
+ )
41
+ .filter((l) => l.length > 0)
42
+ .join(' ');
43
+ }
@@ -1,4 +1,4 @@
1
- import { findChild, nodeEndLine } from './helpers.js';
1
+ import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Extract symbols from C# files.
@@ -133,6 +133,7 @@ export function extractCSharpSymbols(tree, _filePath) {
133
133
  line: node.startPosition.row + 1,
134
134
  endLine: nodeEndLine(node),
135
135
  children: params.length > 0 ? params : undefined,
136
+ visibility: extractModifierVisibility(node),
136
137
  });
137
138
  }
138
139
  break;
@@ -150,6 +151,7 @@ export function extractCSharpSymbols(tree, _filePath) {
150
151
  line: node.startPosition.row + 1,
151
152
  endLine: nodeEndLine(node),
152
153
  children: params.length > 0 ? params : undefined,
154
+ visibility: extractModifierVisibility(node),
153
155
  });
154
156
  }
155
157
  break;
@@ -165,6 +167,7 @@ export function extractCSharpSymbols(tree, _filePath) {
165
167
  kind: 'property',
166
168
  line: node.startPosition.row + 1,
167
169
  endLine: nodeEndLine(node),
170
+ visibility: extractModifierVisibility(node),
168
171
  });
169
172
  }
170
173
  break;
@@ -260,7 +263,12 @@ function extractCSharpClassFields(classNode) {
260
263
  if (!child || child.type !== 'variable_declarator') continue;
261
264
  const nameNode = child.childForFieldName('name');
262
265
  if (nameNode) {
263
- fields.push({ name: nameNode.text, kind: 'property', line: member.startPosition.row + 1 });
266
+ fields.push({
267
+ name: nameNode.text,
268
+ kind: 'property',
269
+ line: member.startPosition.row + 1,
270
+ visibility: extractModifierVisibility(member),
271
+ });
264
272
  }
265
273
  }
266
274
  }
@@ -1,4 +1,4 @@
1
- import { findChild, nodeEndLine } from './helpers.js';
1
+ import { findChild, goVisibility, nodeEndLine } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Extract symbols from Go files.
@@ -22,6 +22,7 @@ export function extractGoSymbols(tree, _filePath) {
22
22
  line: node.startPosition.row + 1,
23
23
  endLine: nodeEndLine(node),
24
24
  children: params.length > 0 ? params : undefined,
25
+ visibility: goVisibility(nameNode.text),
25
26
  });
26
27
  }
27
28
  break;
@@ -55,6 +56,7 @@ export function extractGoSymbols(tree, _filePath) {
55
56
  line: node.startPosition.row + 1,
56
57
  endLine: nodeEndLine(node),
57
58
  children: params.length > 0 ? params : undefined,
59
+ visibility: goVisibility(nameNode.text),
58
60
  });
59
61
  }
60
62
  break;
@@ -9,3 +9,74 @@ export function findChild(node, type) {
9
9
  }
10
10
  return null;
11
11
  }
12
+
13
+ /**
14
+ * Extract visibility from a node by scanning its children for modifier keywords.
15
+ * Works for Java, C#, PHP, and similar languages where modifiers are child nodes.
16
+ * @param {object} node - tree-sitter node
17
+ * @param {Set<string>} [modifierTypes] - node types that indicate modifiers
18
+ * @returns {'public'|'private'|'protected'|undefined}
19
+ */
20
+ const DEFAULT_MODIFIER_TYPES = new Set([
21
+ 'modifiers',
22
+ 'modifier',
23
+ 'visibility_modifier',
24
+ 'accessibility_modifier',
25
+ ]);
26
+ const VISIBILITY_KEYWORDS = new Set(['public', 'private', 'protected']);
27
+
28
+ /**
29
+ * Python convention: __name → private, _name → protected, else undefined.
30
+ */
31
+ export function pythonVisibility(name) {
32
+ if (name.startsWith('__') && name.endsWith('__')) return undefined; // dunder — public
33
+ if (name.startsWith('__')) return 'private';
34
+ if (name.startsWith('_')) return 'protected';
35
+ return undefined;
36
+ }
37
+
38
+ /**
39
+ * Go convention: uppercase first letter → public, lowercase → private.
40
+ */
41
+ export function goVisibility(name) {
42
+ if (!name) return undefined;
43
+ // Strip receiver prefix (e.g., "Receiver.Method" → check "Method")
44
+ const bare = name.includes('.') ? name.split('.').pop() : name;
45
+ if (!bare) return undefined;
46
+ return bare[0] === bare[0].toUpperCase() && bare[0] !== bare[0].toLowerCase()
47
+ ? 'public'
48
+ : 'private';
49
+ }
50
+
51
+ /**
52
+ * Rust: check for `visibility_modifier` child (pub, pub(crate), etc.).
53
+ */
54
+ export function rustVisibility(node) {
55
+ for (let i = 0; i < node.childCount; i++) {
56
+ const child = node.child(i);
57
+ if (!child) continue;
58
+ if (child.type === 'visibility_modifier') {
59
+ return 'public'; // pub, pub(crate), pub(super) all mean "visible"
60
+ }
61
+ }
62
+ return 'private';
63
+ }
64
+
65
+ export function extractModifierVisibility(node, modifierTypes = DEFAULT_MODIFIER_TYPES) {
66
+ for (let i = 0; i < node.childCount; i++) {
67
+ const child = node.child(i);
68
+ if (!child) continue;
69
+ // Direct keyword match (e.g., PHP visibility_modifier = "public")
70
+ if (modifierTypes.has(child.type)) {
71
+ const text = child.text;
72
+ if (VISIBILITY_KEYWORDS.has(text)) return text;
73
+ // C# 'private protected' — accessible to derived types in same assembly → protected
74
+ if (text === 'private protected') return 'protected';
75
+ // Compound modifiers node (Java: "public static") — scan its text for a keyword
76
+ for (const kw of VISIBILITY_KEYWORDS) {
77
+ if (text.includes(kw)) return kw;
78
+ }
79
+ }
80
+ }
81
+ return undefined;
82
+ }
@@ -1,4 +1,4 @@
1
- import { findChild, nodeEndLine } from './helpers.js';
1
+ import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Extract symbols from Java files.
@@ -165,6 +165,7 @@ export function extractJavaSymbols(tree, _filePath) {
165
165
  line: node.startPosition.row + 1,
166
166
  endLine: nodeEndLine(node),
167
167
  children: params.length > 0 ? params : undefined,
168
+ visibility: extractModifierVisibility(node),
168
169
  });
169
170
  }
170
171
  break;
@@ -182,6 +183,7 @@ export function extractJavaSymbols(tree, _filePath) {
182
183
  line: node.startPosition.row + 1,
183
184
  endLine: nodeEndLine(node),
184
185
  children: params.length > 0 ? params : undefined,
186
+ visibility: extractModifierVisibility(node),
185
187
  });
186
188
  }
187
189
  break;
@@ -267,7 +269,12 @@ function extractClassFields(classNode) {
267
269
  if (!child || child.type !== 'variable_declarator') continue;
268
270
  const nameNode = child.childForFieldName('name');
269
271
  if (nameNode) {
270
- fields.push({ name: nameNode.text, kind: 'property', line: member.startPosition.row + 1 });
272
+ fields.push({
273
+ name: nameNode.text,
274
+ kind: 'property',
275
+ line: member.startPosition.row + 1,
276
+ visibility: extractModifierVisibility(member),
277
+ });
271
278
  }
272
279
  }
273
280
  }
@@ -1,4 +1,4 @@
1
- import { debug } from '../logger.js';
1
+ import { debug } from '../infrastructure/logger.js';
2
2
  import { findChild, nodeEndLine } from './helpers.js';
3
3
 
4
4
  /**
@@ -77,12 +77,14 @@ function extractSymbolsQuery(tree, query) {
77
77
  const parentClass = findParentClass(c.meth_node);
78
78
  const fullName = parentClass ? `${parentClass}.${methName}` : methName;
79
79
  const methChildren = extractParameters(c.meth_node);
80
+ const methVis = extractVisibility(c.meth_node);
80
81
  definitions.push({
81
82
  name: fullName,
82
83
  kind: 'method',
83
84
  line: c.meth_node.startPosition.row + 1,
84
85
  endLine: nodeEndLine(c.meth_node),
85
86
  children: methChildren.length > 0 ? methChildren : undefined,
87
+ visibility: methVis,
86
88
  });
87
89
  } else if (c.iface_node) {
88
90
  // interface_declaration (TS/TSX only)
@@ -375,12 +377,14 @@ function extractSymbolsWalk(tree) {
375
377
  const parentClass = findParentClass(node);
376
378
  const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
377
379
  const methChildren = extractParameters(node);
380
+ const methVis = extractVisibility(node);
378
381
  definitions.push({
379
382
  name: fullName,
380
383
  kind: 'method',
381
384
  line: node.startPosition.row + 1,
382
385
  endLine: nodeEndLine(node),
383
386
  children: methChildren.length > 0 ? methChildren : undefined,
387
+ visibility: methVis,
384
388
  });
385
389
  }
386
390
  break;
@@ -701,13 +705,46 @@ function extractClassProperties(classNode) {
701
705
  nameNode.type === 'identifier' ||
702
706
  nameNode.type === 'private_property_identifier')
703
707
  ) {
704
- props.push({ name: nameNode.text, kind: 'property', line: child.startPosition.row + 1 });
708
+ // Private # fields: nameNode.type is 'private_property_identifier'
709
+ // TS modifiers: accessibility_modifier child on the field_definition
710
+ const vis =
711
+ nameNode.type === 'private_property_identifier' ? 'private' : extractVisibility(child);
712
+ props.push({
713
+ name: nameNode.text,
714
+ kind: 'property',
715
+ line: child.startPosition.row + 1,
716
+ visibility: vis,
717
+ });
705
718
  }
706
719
  }
707
720
  }
708
721
  return props;
709
722
  }
710
723
 
724
+ /**
725
+ * Extract visibility modifier from a class member node.
726
+ * Checks for TS access modifiers (public/private/protected) and JS private (#) fields.
727
+ * Returns 'public' | 'private' | 'protected' | undefined.
728
+ */
729
+ function extractVisibility(node) {
730
+ // Check for TS accessibility modifiers (accessibility_modifier child)
731
+ for (let i = 0; i < node.childCount; i++) {
732
+ const child = node.child(i);
733
+ if (!child) continue;
734
+ if (child.type === 'accessibility_modifier') {
735
+ const text = child.text;
736
+ if (text === 'private' || text === 'protected' || text === 'public') return text;
737
+ }
738
+ }
739
+ // Check for JS private name (# prefix) — try multiple field names
740
+ const nameNode =
741
+ node.childForFieldName('name') || node.childForFieldName('property') || node.child(0);
742
+ if (nameNode && nameNode.type === 'private_property_identifier') {
743
+ return 'private';
744
+ }
745
+ return undefined;
746
+ }
747
+
711
748
  function isConstantValue(valueNode) {
712
749
  if (!valueNode) return false;
713
750
  const t = valueNode.type;
@@ -1,4 +1,4 @@
1
- import { findChild, nodeEndLine } from './helpers.js';
1
+ import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
2
2
 
3
3
  function extractPhpParameters(fnNode) {
4
4
  const params = [];
@@ -35,6 +35,7 @@ function extractPhpClassChildren(classNode) {
35
35
  name: varNode.text,
36
36
  kind: 'property',
37
37
  line: member.startPosition.row + 1,
38
+ visibility: extractModifierVisibility(member),
38
39
  });
39
40
  }
40
41
  }
@@ -231,6 +232,7 @@ export function extractPHPSymbols(tree, _filePath) {
231
232
  line: node.startPosition.row + 1,
232
233
  endLine: nodeEndLine(node),
233
234
  children: params.length > 0 ? params : undefined,
235
+ visibility: extractModifierVisibility(node),
234
236
  });
235
237
  }
236
238
  break;
@@ -1,4 +1,4 @@
1
- import { findChild, nodeEndLine } from './helpers.js';
1
+ import { findChild, nodeEndLine, pythonVisibility } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Extract symbols from Python files.
@@ -30,6 +30,7 @@ export function extractPythonSymbols(tree, _filePath) {
30
30
  endLine: nodeEndLine(node),
31
31
  decorators,
32
32
  children: fnChildren.length > 0 ? fnChildren : undefined,
33
+ visibility: pythonVisibility(nameNode.text),
33
34
  });
34
35
  }
35
36
  break;
@@ -209,7 +210,12 @@ export function extractPythonSymbols(tree, _filePath) {
209
210
  const left = assignment.childForFieldName('left');
210
211
  if (left && left.type === 'identifier' && !seen.has(left.text)) {
211
212
  seen.add(left.text);
212
- props.push({ name: left.text, kind: 'property', line: child.startPosition.row + 1 });
213
+ props.push({
214
+ name: left.text,
215
+ kind: 'property',
216
+ line: child.startPosition.row + 1,
217
+ visibility: pythonVisibility(left.text),
218
+ });
213
219
  }
214
220
  }
215
221
  }
@@ -262,7 +268,12 @@ export function extractPythonSymbols(tree, _filePath) {
262
268
  !seen.has(attr.text)
263
269
  ) {
264
270
  seen.add(attr.text);
265
- props.push({ name: attr.text, kind: 'property', line: stmt.startPosition.row + 1 });
271
+ props.push({
272
+ name: attr.text,
273
+ kind: 'property',
274
+ line: stmt.startPosition.row + 1,
275
+ visibility: pythonVisibility(attr.text),
276
+ });
266
277
  }
267
278
  }
268
279
  }
@@ -1,4 +1,4 @@
1
- import { findChild, nodeEndLine } from './helpers.js';
1
+ import { findChild, nodeEndLine, rustVisibility } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Extract symbols from Rust files.
@@ -37,6 +37,7 @@ export function extractRustSymbols(tree, _filePath) {
37
37
  line: node.startPosition.row + 1,
38
38
  endLine: nodeEndLine(node),
39
39
  children: params.length > 0 ? params : undefined,
40
+ visibility: rustVisibility(node),
40
41
  });
41
42
  }
42
43
  break;
@@ -52,6 +53,7 @@ export function extractRustSymbols(tree, _filePath) {
52
53
  line: node.startPosition.row + 1,
53
54
  endLine: nodeEndLine(node),
54
55
  children: fields.length > 0 ? fields : undefined,
56
+ visibility: rustVisibility(node),
55
57
  });
56
58
  }
57
59
  break;
@@ -7,14 +7,14 @@
7
7
  */
8
8
 
9
9
  import path from 'node:path';
10
- import { AST_TYPE_MAPS } from './ast-analysis/rules/index.js';
11
- import { buildExtensionSet } from './ast-analysis/shared.js';
12
- import { walkWithVisitors } from './ast-analysis/visitor.js';
13
- import { createAstStoreVisitor } from './ast-analysis/visitors/ast-store-visitor.js';
14
- import { bulkNodeIdsByFile, openReadonlyOrFail } from './db.js';
15
- import { outputResult } from './infrastructure/result-formatter.js';
16
- import { debug } from './logger.js';
17
- import { paginateResult } from './paginate.js';
10
+ import { AST_TYPE_MAPS } from '../ast-analysis/rules/index.js';
11
+ import { buildExtensionSet } from '../ast-analysis/shared.js';
12
+ import { walkWithVisitors } from '../ast-analysis/visitor.js';
13
+ import { createAstStoreVisitor } from '../ast-analysis/visitors/ast-store-visitor.js';
14
+ import { bulkNodeIdsByFile, openReadonlyOrFail } from '../db/index.js';
15
+ import { debug } from '../infrastructure/logger.js';
16
+ import { outputResult } from '../infrastructure/result-formatter.js';
17
+ import { paginateResult } from '../shared/paginate.js';
18
18
 
19
19
  // ─── Constants ────────────────────────────────────────────────────────
20
20
 
@@ -7,21 +7,26 @@
7
7
  */
8
8
 
9
9
  import path from 'node:path';
10
- import { loadConfig } from './config.js';
11
- import { openReadonlyOrFail } from './db.js';
12
- import { isTestFile } from './infrastructure/test-filter.js';
10
+ import { openReadonlyOrFail } from '../db/index.js';
11
+ import { bfsTransitiveCallers } from '../domain/analysis/impact.js';
12
+ import { explainData } from '../domain/queries.js';
13
+ import { loadConfig } from '../infrastructure/config.js';
14
+ import { isTestFile } from '../infrastructure/test-filter.js';
13
15
  import { RULE_DEFS } from './manifesto.js';
14
- import { explainData } from './queries.js';
15
16
 
16
17
  // ─── Threshold resolution ───────────────────────────────────────────
17
18
 
18
19
  const FUNCTION_RULES = RULE_DEFS.filter((d) => d.level === 'function');
19
20
 
20
- function resolveThresholds(customDbPath) {
21
+ function resolveThresholds(customDbPath, config) {
21
22
  try {
22
- const dbDir = path.dirname(customDbPath);
23
- const repoRoot = path.resolve(dbDir, '..');
24
- const cfg = loadConfig(repoRoot);
23
+ const cfg =
24
+ config ||
25
+ (() => {
26
+ const dbDir = path.dirname(customDbPath);
27
+ const repoRoot = path.resolve(dbDir, '..');
28
+ return loadConfig(repoRoot);
29
+ })();
25
30
  const userRules = cfg.manifesto || {};
26
31
  const resolved = {};
27
32
  for (const def of FUNCTION_RULES) {
@@ -70,39 +75,6 @@ function checkBreaches(row, thresholds) {
70
75
  return breaches;
71
76
  }
72
77
 
73
- // ─── BFS impact (inline, same algorithm as fnImpactData) ────────────
74
-
75
- function computeImpact(db, nodeId, noTests, maxDepth) {
76
- const visited = new Set([nodeId]);
77
- const levels = {};
78
- let frontier = [nodeId];
79
-
80
- for (let d = 1; d <= maxDepth; d++) {
81
- const nextFrontier = [];
82
- for (const fid of frontier) {
83
- const callers = db
84
- .prepare(
85
- `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
86
- FROM edges e JOIN nodes n ON e.source_id = n.id
87
- WHERE e.target_id = ? AND e.kind = 'calls'`,
88
- )
89
- .all(fid);
90
- for (const c of callers) {
91
- if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
92
- visited.add(c.id);
93
- nextFrontier.push(c.id);
94
- if (!levels[d]) levels[d] = [];
95
- levels[d].push({ name: c.name, kind: c.kind, file: c.file, line: c.line });
96
- }
97
- }
98
- }
99
- frontier = nextFrontier;
100
- if (frontier.length === 0) break;
101
- }
102
-
103
- return { totalDependents: visited.size - 1, levels };
104
- }
105
-
106
78
  // ─── Phase 4.4 fields (graceful null fallback) ─────────────────────
107
79
 
108
80
  function readPhase44(db, nodeId) {
@@ -147,7 +119,7 @@ export function auditData(target, customDbPath, opts = {}) {
147
119
 
148
120
  // 2. Open DB for enrichment
149
121
  const db = openReadonlyOrFail(customDbPath);
150
- const thresholds = resolveThresholds(customDbPath);
122
+ const thresholds = resolveThresholds(customDbPath, opts.config);
151
123
 
152
124
  let functions;
153
125
  try {
@@ -189,7 +161,7 @@ function enrichFunction(db, r, noTests, maxDepth, thresholds) {
189
161
  const nodeId = nodeRow?.id;
190
162
  const health = nodeId ? buildHealth(db, nodeId, thresholds) : defaultHealth();
191
163
  const impact = nodeId
192
- ? computeImpact(db, nodeId, noTests, maxDepth)
164
+ ? bfsTransitiveCallers(db, nodeId, { noTests, maxDepth })
193
165
  : { totalDependents: 0, levels: {} };
194
166
  const phase44 = nodeId
195
167
  ? readPhase44(db, nodeId)
@@ -260,7 +232,7 @@ function enrichSymbol(db, sym, file, noTests, maxDepth, thresholds) {
260
232
 
261
233
  const health = nodeId ? buildHealth(db, nodeId, thresholds) : defaultHealth();
262
234
  const impact = nodeId
263
- ? computeImpact(db, nodeId, noTests, maxDepth)
235
+ ? bfsTransitiveCallers(db, nodeId, { noTests, maxDepth })
264
236
  : { totalDependents: 0, levels: {} };
265
237
  const phase44 = nodeId
266
238
  ? readPhase44(db, nodeId)
@@ -5,9 +5,6 @@
5
5
  * Designed for multi-agent swarms that need to dispatch 20+ queries in one call.
6
6
  */
7
7
 
8
- import { complexityData } from './complexity.js';
9
- import { dataflowData } from './dataflow.js';
10
- import { flowData } from './flow.js';
11
8
  import {
12
9
  contextData,
13
10
  explainData,
@@ -17,7 +14,11 @@ import {
17
14
  fnImpactData,
18
15
  impactAnalysisData,
19
16
  whereData,
20
- } from './queries.js';
17
+ } from '../domain/queries.js';
18
+ import { ConfigError } from '../shared/errors.js';
19
+ import { complexityData } from './complexity.js';
20
+ import { dataflowData } from './dataflow.js';
21
+ import { flowData } from './flow.js';
21
22
 
22
23
  /**
23
24
  * Map of supported batch commands → their data function + first-arg semantics.
@@ -53,7 +54,7 @@ export const BATCH_COMMANDS = {
53
54
  export function batchData(command, targets, customDbPath, opts = {}) {
54
55
  const entry = BATCH_COMMANDS[command];
55
56
  if (!entry) {
56
- throw new Error(
57
+ throw new ConfigError(
57
58
  `Unknown batch command "${command}". Valid commands: ${Object.keys(BATCH_COMMANDS).join(', ')}`,
58
59
  );
59
60
  }
@@ -1,5 +1,5 @@
1
- import { isTestFile } from './infrastructure/test-filter.js';
2
- import { debug } from './logger.js';
1
+ import { debug } from '../infrastructure/logger.js';
2
+ import { isTestFile } from '../infrastructure/test-filter.js';
3
3
 
4
4
  // ─── Glob-to-Regex ───────────────────────────────────────────────────
5
5
 
@@ -11,9 +11,9 @@ import fs from 'node:fs';
11
11
  import os from 'node:os';
12
12
  import path from 'node:path';
13
13
  import Database from 'better-sqlite3';
14
- import { buildGraph } from './builder.js';
15
- import { isTestFile } from './infrastructure/test-filter.js';
16
- import { kindIcon } from './queries.js';
14
+ import { buildGraph } from '../domain/graph/builder.js';
15
+ import { kindIcon } from '../domain/queries.js';
16
+ import { isTestFile } from '../infrastructure/test-filter.js';
17
17
 
18
18
  // ─── Git Helpers ────────────────────────────────────────────────────────
19
19
 
@@ -7,14 +7,14 @@
7
7
 
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
- import { CFG_RULES } from './ast-analysis/rules/index.js';
10
+ import { CFG_RULES } from '../ast-analysis/rules/index.js';
11
11
  import {
12
12
  makeCfgRules as _makeCfgRules,
13
13
  buildExtensionSet,
14
14
  buildExtToLangMap,
15
- } from './ast-analysis/shared.js';
16
- import { walkWithVisitors } from './ast-analysis/visitor.js';
17
- import { createCfgVisitor } from './ast-analysis/visitors/cfg-visitor.js';
15
+ } from '../ast-analysis/shared.js';
16
+ import { walkWithVisitors } from '../ast-analysis/visitor.js';
17
+ import { createCfgVisitor } from '../ast-analysis/visitors/cfg-visitor.js';
18
18
  import {
19
19
  deleteCfgForNode,
20
20
  getCfgBlocks,
@@ -22,14 +22,13 @@ import {
22
22
  getFunctionNodeId,
23
23
  hasCfgTables,
24
24
  openReadonlyOrFail,
25
- } from './db.js';
26
- import { isTestFile } from './infrastructure/test-filter.js';
27
- import { info } from './logger.js';
28
- import { paginateResult } from './paginate.js';
25
+ } from '../db/index.js';
26
+ import { info } from '../infrastructure/logger.js';
27
+ import { isTestFile } from '../infrastructure/test-filter.js';
28
+ import { paginateResult } from '../shared/paginate.js';
29
29
 
30
30
  // Re-export for backward compatibility
31
- export { CFG_RULES };
32
- export { _makeCfgRules as makeCfgRules };
31
+ export { _makeCfgRules as makeCfgRules, CFG_RULES };
33
32
 
34
33
  const CFG_EXTENSIONS = buildExtensionSet(CFG_RULES);
35
34
 
@@ -105,13 +104,13 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
105
104
  }
106
105
 
107
106
  if (needsFallback) {
108
- const { createParsers } = await import('./parser.js');
107
+ const { createParsers } = await import('../domain/parser.js');
109
108
  parsers = await createParsers();
110
109
  }
111
110
 
112
111
  let getParserFn = null;
113
112
  if (parsers) {
114
- const mod = await import('./parser.js');
113
+ const mod = await import('../domain/parser.js');
115
114
  getParserFn = mod.getParser;
116
115
  }
117
116