@optave/codegraph 3.1.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
package/src/sequence.js DELETED
@@ -1,289 +0,0 @@
1
- /**
2
- * Sequence diagram generation – Mermaid sequenceDiagram from call graph edges.
3
- *
4
- * Participants are files (not individual functions). Calls within the same file
5
- * become self-messages. This keeps diagrams readable and matches typical
6
- * sequence-diagram conventions.
7
- */
8
-
9
- import { findCallees, openReadonlyOrFail } from './db.js';
10
- import { isTestFile } from './infrastructure/test-filter.js';
11
- import { paginateResult } from './paginate.js';
12
- import { findMatchingNodes } from './queries.js';
13
- import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
14
-
15
- // ─── Alias generation ────────────────────────────────────────────────
16
-
17
- /**
18
- * Build short participant aliases from file paths with collision handling.
19
- * e.g. "src/builder.js" → "builder", but if two files share basename,
20
- * progressively add parent dirs: "src/builder" vs "lib/builder".
21
- */
22
- function buildAliases(files) {
23
- const aliases = new Map();
24
- const basenames = new Map();
25
-
26
- // Group by basename
27
- for (const file of files) {
28
- const base = file
29
- .split('/')
30
- .pop()
31
- .replace(/\.[^.]+$/, '');
32
- if (!basenames.has(base)) basenames.set(base, []);
33
- basenames.get(base).push(file);
34
- }
35
-
36
- for (const [base, paths] of basenames) {
37
- if (paths.length === 1) {
38
- aliases.set(paths[0], base);
39
- } else {
40
- // Collision — progressively add parent dirs until aliases are unique
41
- for (let depth = 2; depth <= 10; depth++) {
42
- const trial = new Map();
43
- let allUnique = true;
44
- const seen = new Set();
45
-
46
- for (const p of paths) {
47
- const parts = p.replace(/\.[^.]+$/, '').split('/');
48
- const alias = parts
49
- .slice(-depth)
50
- .join('_')
51
- .replace(/[^a-zA-Z0-9_-]/g, '_');
52
- trial.set(p, alias);
53
- if (seen.has(alias)) allUnique = false;
54
- seen.add(alias);
55
- }
56
-
57
- if (allUnique || depth === 10) {
58
- for (const [p, alias] of trial) {
59
- aliases.set(p, alias);
60
- }
61
- break;
62
- }
63
- }
64
- }
65
- }
66
-
67
- return aliases;
68
- }
69
-
70
- // ─── Core data function ──────────────────────────────────────────────
71
-
72
- /**
73
- * Build sequence diagram data by BFS-forward from an entry point.
74
- *
75
- * @param {string} name - Symbol name to trace from
76
- * @param {string} [dbPath]
77
- * @param {object} [opts]
78
- * @param {number} [opts.depth=10]
79
- * @param {boolean} [opts.noTests]
80
- * @param {string} [opts.file]
81
- * @param {string} [opts.kind]
82
- * @param {boolean} [opts.dataflow]
83
- * @param {number} [opts.limit]
84
- * @param {number} [opts.offset]
85
- * @returns {{ entry, participants, messages, depth, totalMessages, truncated }}
86
- */
87
- export function sequenceData(name, dbPath, opts = {}) {
88
- const db = openReadonlyOrFail(dbPath);
89
- try {
90
- const maxDepth = opts.depth || 10;
91
- const noTests = opts.noTests || false;
92
- const withDataflow = opts.dataflow || false;
93
-
94
- // Phase 1: Direct LIKE match
95
- let matchNode = findMatchingNodes(db, name, opts)[0] ?? null;
96
-
97
- // Phase 2: Prefix-stripped matching
98
- if (!matchNode) {
99
- for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
100
- matchNode = findMatchingNodes(db, `${prefix}${name}`, opts)[0] ?? null;
101
- if (matchNode) break;
102
- }
103
- }
104
-
105
- if (!matchNode) {
106
- return {
107
- entry: null,
108
- participants: [],
109
- messages: [],
110
- depth: maxDepth,
111
- totalMessages: 0,
112
- truncated: false,
113
- };
114
- }
115
-
116
- const entry = {
117
- name: matchNode.name,
118
- file: matchNode.file,
119
- kind: matchNode.kind,
120
- line: matchNode.line,
121
- };
122
-
123
- // BFS forward — track edges, not just nodes
124
- const visited = new Set([matchNode.id]);
125
- let frontier = [matchNode.id];
126
- const messages = [];
127
- const fileSet = new Set([matchNode.file]);
128
- const idToNode = new Map();
129
- idToNode.set(matchNode.id, matchNode);
130
- let truncated = false;
131
-
132
- for (let d = 1; d <= maxDepth; d++) {
133
- const nextFrontier = [];
134
-
135
- for (const fid of frontier) {
136
- const callees = findCallees(db, fid);
137
-
138
- const caller = idToNode.get(fid);
139
-
140
- for (const c of callees) {
141
- if (noTests && isTestFile(c.file)) continue;
142
-
143
- // Always record the message (even for visited nodes — different caller path)
144
- fileSet.add(c.file);
145
- messages.push({
146
- from: caller.file,
147
- to: c.file,
148
- label: c.name,
149
- type: 'call',
150
- depth: d,
151
- });
152
-
153
- if (visited.has(c.id)) continue;
154
-
155
- visited.add(c.id);
156
- nextFrontier.push(c.id);
157
- idToNode.set(c.id, c);
158
- }
159
- }
160
-
161
- frontier = nextFrontier;
162
- if (frontier.length === 0) break;
163
-
164
- if (d === maxDepth && frontier.length > 0) {
165
- // Only mark truncated if at least one frontier node has further callees
166
- const hasMoreCalls = frontier.some((fid) => findCallees(db, fid).length > 0);
167
- if (hasMoreCalls) truncated = true;
168
- }
169
- }
170
-
171
- // Dataflow annotations: add return arrows
172
- if (withDataflow && messages.length > 0) {
173
- const hasTable = db
174
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='dataflow'")
175
- .get();
176
-
177
- if (hasTable) {
178
- // Build name|file lookup for O(1) target node access
179
- const nodeByNameFile = new Map();
180
- for (const n of idToNode.values()) {
181
- nodeByNameFile.set(`${n.name}|${n.file}`, n);
182
- }
183
-
184
- const getReturns = db.prepare(
185
- `SELECT d.expression FROM dataflow d
186
- WHERE d.source_id = ? AND d.kind = 'returns'`,
187
- );
188
- const getFlowsTo = db.prepare(
189
- `SELECT d.expression FROM dataflow d
190
- WHERE d.target_id = ? AND d.kind = 'flows_to'
191
- ORDER BY d.param_index`,
192
- );
193
-
194
- // For each called function, check if it has return edges
195
- const seenReturns = new Set();
196
- for (const msg of [...messages]) {
197
- if (msg.type !== 'call') continue;
198
- const targetNode = nodeByNameFile.get(`${msg.label}|${msg.to}`);
199
- if (!targetNode) continue;
200
-
201
- const returnKey = `${msg.to}->${msg.from}:${msg.label}`;
202
- if (seenReturns.has(returnKey)) continue;
203
-
204
- const returns = getReturns.all(targetNode.id);
205
-
206
- if (returns.length > 0) {
207
- seenReturns.add(returnKey);
208
- const expr = returns[0].expression || 'result';
209
- messages.push({
210
- from: msg.to,
211
- to: msg.from,
212
- label: expr,
213
- type: 'return',
214
- depth: msg.depth,
215
- });
216
- }
217
- }
218
-
219
- // Annotate call messages with parameter names
220
- for (const msg of messages) {
221
- if (msg.type !== 'call') continue;
222
- const targetNode = nodeByNameFile.get(`${msg.label}|${msg.to}`);
223
- if (!targetNode) continue;
224
-
225
- const params = getFlowsTo.all(targetNode.id);
226
-
227
- if (params.length > 0) {
228
- const paramNames = params
229
- .map((p) => p.expression)
230
- .filter(Boolean)
231
- .slice(0, 3);
232
- if (paramNames.length > 0) {
233
- msg.label = `${msg.label}(${paramNames.join(', ')})`;
234
- }
235
- }
236
- }
237
- }
238
- }
239
-
240
- // Sort messages by depth, then call before return
241
- messages.sort((a, b) => {
242
- if (a.depth !== b.depth) return a.depth - b.depth;
243
- if (a.type === 'call' && b.type === 'return') return -1;
244
- if (a.type === 'return' && b.type === 'call') return 1;
245
- return 0;
246
- });
247
-
248
- // Build participant list from files
249
- const aliases = buildAliases([...fileSet]);
250
- const participants = [...fileSet].map((file) => ({
251
- id: aliases.get(file),
252
- label: file.split('/').pop(),
253
- file,
254
- }));
255
-
256
- // Sort participants: entry file first, then alphabetically
257
- participants.sort((a, b) => {
258
- if (a.file === entry.file) return -1;
259
- if (b.file === entry.file) return 1;
260
- return a.file.localeCompare(b.file);
261
- });
262
-
263
- // Replace file paths with alias IDs in messages
264
- for (const msg of messages) {
265
- msg.from = aliases.get(msg.from);
266
- msg.to = aliases.get(msg.to);
267
- }
268
-
269
- const base = {
270
- entry,
271
- participants,
272
- messages,
273
- depth: maxDepth,
274
- totalMessages: messages.length,
275
- truncated,
276
- };
277
- const result = paginateResult(base, 'messages', { limit: opts.limit, offset: opts.offset });
278
- if (opts.limit !== undefined || opts.offset !== undefined) {
279
- const activeFiles = new Set(result.messages.flatMap((m) => [m.from, m.to]));
280
- result.participants = result.participants.filter((p) => activeFiles.has(p.id));
281
- }
282
- return result;
283
- } finally {
284
- db.close();
285
- }
286
- }
287
-
288
- // Re-export Mermaid renderer from presentation layer
289
- export { sequenceToMermaid } from './presentation/sequence-renderer.js';
package/src/triage.js DELETED
@@ -1,126 +0,0 @@
1
- import { findNodesForTriage, openReadonlyOrFail } from './db.js';
2
- import { DEFAULT_WEIGHTS, scoreRisk } from './graph/classifiers/risk.js';
3
- import { isTestFile } from './infrastructure/test-filter.js';
4
- import { warn } from './logger.js';
5
- import { paginateResult } from './paginate.js';
6
-
7
- // ─── Data Function ────────────────────────────────────────────────────
8
-
9
- /**
10
- * Compute composite risk scores for all symbols.
11
- *
12
- * @param {string} [customDbPath] - Path to graph.db
13
- * @param {object} [opts]
14
- * @returns {{ items: object[], summary: object, _pagination?: object }}
15
- */
16
- export function triageData(customDbPath, opts = {}) {
17
- const db = openReadonlyOrFail(customDbPath);
18
- try {
19
- const noTests = opts.noTests || false;
20
- const fileFilter = opts.file || null;
21
- const kindFilter = opts.kind || null;
22
- const roleFilter = opts.role || null;
23
- const minScore = opts.minScore != null ? Number(opts.minScore) : null;
24
- const sort = opts.sort || 'risk';
25
- const weights = { ...DEFAULT_WEIGHTS, ...(opts.weights || {}) };
26
-
27
- let rows;
28
- try {
29
- rows = findNodesForTriage(db, {
30
- noTests,
31
- file: fileFilter,
32
- kind: kindFilter,
33
- role: roleFilter,
34
- });
35
- } catch (err) {
36
- warn(`triage query failed: ${err.message}`);
37
- return {
38
- items: [],
39
- summary: { total: 0, analyzed: 0, avgScore: 0, maxScore: 0, weights, signalCoverage: {} },
40
- };
41
- }
42
-
43
- // Post-filter test files (belt-and-suspenders)
44
- const filtered = noTests ? rows.filter((r) => !isTestFile(r.file)) : rows;
45
-
46
- if (filtered.length === 0) {
47
- return {
48
- items: [],
49
- summary: { total: 0, analyzed: 0, avgScore: 0, maxScore: 0, weights, signalCoverage: {} },
50
- };
51
- }
52
-
53
- // Delegate scoring to classifier
54
- const riskMetrics = scoreRisk(filtered, weights);
55
-
56
- // Compute risk scores
57
- const items = filtered.map((r, i) => ({
58
- name: r.name,
59
- kind: r.kind,
60
- file: r.file,
61
- line: r.line,
62
- role: r.role || null,
63
- fanIn: r.fan_in,
64
- cognitive: r.cognitive,
65
- churn: r.churn,
66
- maintainabilityIndex: r.mi,
67
- normFanIn: riskMetrics[i].normFanIn,
68
- normComplexity: riskMetrics[i].normComplexity,
69
- normChurn: riskMetrics[i].normChurn,
70
- normMI: riskMetrics[i].normMI,
71
- roleWeight: riskMetrics[i].roleWeight,
72
- riskScore: riskMetrics[i].riskScore,
73
- }));
74
-
75
- // Apply minScore filter
76
- const scored = minScore != null ? items.filter((it) => it.riskScore >= minScore) : items;
77
-
78
- // Sort
79
- const sortFns = {
80
- risk: (a, b) => b.riskScore - a.riskScore,
81
- complexity: (a, b) => b.cognitive - a.cognitive,
82
- churn: (a, b) => b.churn - a.churn,
83
- 'fan-in': (a, b) => b.fanIn - a.fanIn,
84
- mi: (a, b) => a.maintainabilityIndex - b.maintainabilityIndex,
85
- };
86
- scored.sort(sortFns[sort] || sortFns.risk);
87
-
88
- // Signal coverage: % of items with non-zero signal
89
- const signalCoverage = {
90
- complexity: round4(filtered.filter((r) => r.cognitive > 0).length / filtered.length),
91
- churn: round4(filtered.filter((r) => r.churn > 0).length / filtered.length),
92
- fanIn: round4(filtered.filter((r) => r.fan_in > 0).length / filtered.length),
93
- mi: round4(filtered.filter((r) => r.mi > 0).length / filtered.length),
94
- };
95
-
96
- const scores = scored.map((it) => it.riskScore);
97
- const avgScore =
98
- scores.length > 0 ? round4(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
99
- const maxScore = scores.length > 0 ? round4(Math.max(...scores)) : 0;
100
-
101
- const result = {
102
- items: scored,
103
- summary: {
104
- total: filtered.length,
105
- analyzed: scored.length,
106
- avgScore,
107
- maxScore,
108
- weights,
109
- signalCoverage,
110
- },
111
- };
112
-
113
- return paginateResult(result, 'items', {
114
- limit: opts.limit,
115
- offset: opts.offset,
116
- });
117
- } finally {
118
- db.close();
119
- }
120
- }
121
-
122
- // ─── Utilities ────────────────────────────────────────────────────────
123
-
124
- function round4(n) {
125
- return Math.round(n * 10000) / 10000;
126
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes