@optave/codegraph 2.5.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -89
- package/package.json +8 -7
- package/src/ast.js +392 -0
- package/src/audit.js +423 -0
- package/src/batch.js +180 -0
- package/src/boundaries.js +346 -0
- package/src/builder.js +375 -92
- package/src/cfg.js +1451 -0
- package/src/change-journal.js +130 -0
- package/src/check.js +432 -0
- package/src/cli.js +734 -107
- package/src/cochange.js +5 -2
- package/src/communities.js +7 -1
- package/src/complexity.js +124 -17
- package/src/config.js +10 -0
- package/src/dataflow.js +1187 -0
- package/src/db.js +96 -0
- package/src/embedder.js +359 -47
- package/src/export.js +305 -0
- package/src/extractors/csharp.js +64 -1
- package/src/extractors/go.js +66 -1
- package/src/extractors/hcl.js +22 -0
- package/src/extractors/java.js +61 -1
- package/src/extractors/javascript.js +142 -0
- package/src/extractors/php.js +79 -0
- package/src/extractors/python.js +134 -0
- package/src/extractors/ruby.js +89 -0
- package/src/extractors/rust.js +71 -1
- package/src/flow.js +4 -4
- package/src/index.js +78 -3
- package/src/manifesto.js +69 -1
- package/src/mcp.js +702 -193
- package/src/owners.js +359 -0
- package/src/paginate.js +37 -2
- package/src/parser.js +8 -0
- package/src/queries.js +590 -50
- package/src/snapshot.js +149 -0
- package/src/structure.js +9 -3
- package/src/triage.js +273 -0
- package/src/viewer.js +948 -0
- package/src/watcher.js +36 -1
package/src/extractors/ruby.js
CHANGED
|
@@ -31,11 +31,13 @@ export function extractRubySymbols(tree, _filePath) {
|
|
|
31
31
|
case 'class': {
|
|
32
32
|
const nameNode = node.childForFieldName('name');
|
|
33
33
|
if (nameNode) {
|
|
34
|
+
const classChildren = extractRubyClassChildren(node);
|
|
34
35
|
definitions.push({
|
|
35
36
|
name: nameNode.text,
|
|
36
37
|
kind: 'class',
|
|
37
38
|
line: node.startPosition.row + 1,
|
|
38
39
|
endLine: nodeEndLine(node),
|
|
40
|
+
children: classChildren.length > 0 ? classChildren : undefined,
|
|
39
41
|
});
|
|
40
42
|
const superclass = node.childForFieldName('superclass');
|
|
41
43
|
if (superclass) {
|
|
@@ -73,11 +75,13 @@ export function extractRubySymbols(tree, _filePath) {
|
|
|
73
75
|
case 'module': {
|
|
74
76
|
const nameNode = node.childForFieldName('name');
|
|
75
77
|
if (nameNode) {
|
|
78
|
+
const moduleChildren = extractRubyBodyConstants(node);
|
|
76
79
|
definitions.push({
|
|
77
80
|
name: nameNode.text,
|
|
78
81
|
kind: 'module',
|
|
79
82
|
line: node.startPosition.row + 1,
|
|
80
83
|
endLine: nodeEndLine(node),
|
|
84
|
+
children: moduleChildren.length > 0 ? moduleChildren : undefined,
|
|
81
85
|
});
|
|
82
86
|
}
|
|
83
87
|
break;
|
|
@@ -88,11 +92,13 @@ export function extractRubySymbols(tree, _filePath) {
|
|
|
88
92
|
if (nameNode) {
|
|
89
93
|
const parentClass = findRubyParentClass(node);
|
|
90
94
|
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
95
|
+
const params = extractRubyParameters(node);
|
|
91
96
|
definitions.push({
|
|
92
97
|
name: fullName,
|
|
93
98
|
kind: 'method',
|
|
94
99
|
line: node.startPosition.row + 1,
|
|
95
100
|
endLine: nodeEndLine(node),
|
|
101
|
+
children: params.length > 0 ? params : undefined,
|
|
96
102
|
});
|
|
97
103
|
}
|
|
98
104
|
break;
|
|
@@ -103,16 +109,34 @@ export function extractRubySymbols(tree, _filePath) {
|
|
|
103
109
|
if (nameNode) {
|
|
104
110
|
const parentClass = findRubyParentClass(node);
|
|
105
111
|
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
112
|
+
const params = extractRubyParameters(node);
|
|
106
113
|
definitions.push({
|
|
107
114
|
name: fullName,
|
|
108
115
|
kind: 'function',
|
|
109
116
|
line: node.startPosition.row + 1,
|
|
110
117
|
endLine: nodeEndLine(node),
|
|
118
|
+
children: params.length > 0 ? params : undefined,
|
|
111
119
|
});
|
|
112
120
|
}
|
|
113
121
|
break;
|
|
114
122
|
}
|
|
115
123
|
|
|
124
|
+
case 'assignment': {
|
|
125
|
+
// Top-level constant assignments (parent is program)
|
|
126
|
+
if (node.parent && node.parent.type === 'program') {
|
|
127
|
+
const left = node.childForFieldName('left');
|
|
128
|
+
if (left && left.type === 'constant') {
|
|
129
|
+
definitions.push({
|
|
130
|
+
name: left.text,
|
|
131
|
+
kind: 'constant',
|
|
132
|
+
line: node.startPosition.row + 1,
|
|
133
|
+
endLine: nodeEndLine(node),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
116
140
|
case 'call': {
|
|
117
141
|
const methodNode = node.childForFieldName('method');
|
|
118
142
|
if (methodNode) {
|
|
@@ -186,3 +210,68 @@ export function extractRubySymbols(tree, _filePath) {
|
|
|
186
210
|
walkRubyNode(tree.rootNode);
|
|
187
211
|
return { definitions, calls, imports, classes, exports };
|
|
188
212
|
}
|
|
213
|
+
|
|
214
|
+
// ── Child extraction helpers ────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
const RUBY_PARAM_TYPES = new Set([
|
|
217
|
+
'identifier',
|
|
218
|
+
'optional_parameter',
|
|
219
|
+
'splat_parameter',
|
|
220
|
+
'hash_splat_parameter',
|
|
221
|
+
'block_parameter',
|
|
222
|
+
'keyword_parameter',
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
function extractRubyParameters(methodNode) {
|
|
226
|
+
const params = [];
|
|
227
|
+
const paramList =
|
|
228
|
+
methodNode.childForFieldName('parameters') || findChild(methodNode, 'method_parameters');
|
|
229
|
+
if (!paramList) return params;
|
|
230
|
+
for (let i = 0; i < paramList.childCount; i++) {
|
|
231
|
+
const param = paramList.child(i);
|
|
232
|
+
if (!param || !RUBY_PARAM_TYPES.has(param.type)) continue;
|
|
233
|
+
let name;
|
|
234
|
+
if (param.type === 'identifier') {
|
|
235
|
+
name = param.text;
|
|
236
|
+
} else {
|
|
237
|
+
// Compound parameter types have an identifier child for the name
|
|
238
|
+
const id = findChild(param, 'identifier');
|
|
239
|
+
name = id ? id.text : param.text;
|
|
240
|
+
}
|
|
241
|
+
params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
242
|
+
}
|
|
243
|
+
return params;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function extractRubyBodyConstants(containerNode) {
|
|
247
|
+
const children = [];
|
|
248
|
+
const body = containerNode.childForFieldName('body') || findChild(containerNode, 'body');
|
|
249
|
+
if (!body) return children;
|
|
250
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
251
|
+
const child = body.child(i);
|
|
252
|
+
if (!child || child.type !== 'assignment') continue;
|
|
253
|
+
const left = child.childForFieldName('left');
|
|
254
|
+
if (left && left.type === 'constant') {
|
|
255
|
+
children.push({ name: left.text, kind: 'constant', line: child.startPosition.row + 1 });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return children;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function extractRubyClassChildren(classNode) {
|
|
262
|
+
const children = [];
|
|
263
|
+
const body = classNode.childForFieldName('body') || findChild(classNode, 'body');
|
|
264
|
+
if (!body) return children;
|
|
265
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
266
|
+
const child = body.child(i);
|
|
267
|
+
if (!child || child.type !== 'assignment') continue;
|
|
268
|
+
const left = child.childForFieldName('left');
|
|
269
|
+
if (!left) continue;
|
|
270
|
+
if (left.type === 'instance_variable') {
|
|
271
|
+
children.push({ name: left.text, kind: 'property', line: child.startPosition.row + 1 });
|
|
272
|
+
} else if (left.type === 'constant') {
|
|
273
|
+
children.push({ name: left.text, kind: 'constant', line: child.startPosition.row + 1 });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return children;
|
|
277
|
+
}
|
package/src/extractors/rust.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nodeEndLine } from './helpers.js';
|
|
1
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extract symbols from Rust files.
|
|
@@ -30,11 +30,13 @@ export function extractRustSymbols(tree, _filePath) {
|
|
|
30
30
|
const implType = findCurrentImpl(node);
|
|
31
31
|
const fullName = implType ? `${implType}.${nameNode.text}` : nameNode.text;
|
|
32
32
|
const kind = implType ? 'method' : 'function';
|
|
33
|
+
const params = extractRustParameters(node.childForFieldName('parameters'));
|
|
33
34
|
definitions.push({
|
|
34
35
|
name: fullName,
|
|
35
36
|
kind,
|
|
36
37
|
line: node.startPosition.row + 1,
|
|
37
38
|
endLine: nodeEndLine(node),
|
|
39
|
+
children: params.length > 0 ? params : undefined,
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
break;
|
|
@@ -43,11 +45,13 @@ export function extractRustSymbols(tree, _filePath) {
|
|
|
43
45
|
case 'struct_item': {
|
|
44
46
|
const nameNode = node.childForFieldName('name');
|
|
45
47
|
if (nameNode) {
|
|
48
|
+
const fields = extractStructFields(node);
|
|
46
49
|
definitions.push({
|
|
47
50
|
name: nameNode.text,
|
|
48
51
|
kind: 'struct',
|
|
49
52
|
line: node.startPosition.row + 1,
|
|
50
53
|
endLine: nodeEndLine(node),
|
|
54
|
+
children: fields.length > 0 ? fields : undefined,
|
|
51
55
|
});
|
|
52
56
|
}
|
|
53
57
|
break;
|
|
@@ -56,11 +60,26 @@ export function extractRustSymbols(tree, _filePath) {
|
|
|
56
60
|
case 'enum_item': {
|
|
57
61
|
const nameNode = node.childForFieldName('name');
|
|
58
62
|
if (nameNode) {
|
|
63
|
+
const variants = extractEnumVariants(node);
|
|
59
64
|
definitions.push({
|
|
60
65
|
name: nameNode.text,
|
|
61
66
|
kind: 'enum',
|
|
62
67
|
line: node.startPosition.row + 1,
|
|
63
68
|
endLine: nodeEndLine(node),
|
|
69
|
+
children: variants.length > 0 ? variants : undefined,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
case 'const_item': {
|
|
76
|
+
const nameNode = node.childForFieldName('name');
|
|
77
|
+
if (nameNode) {
|
|
78
|
+
definitions.push({
|
|
79
|
+
name: nameNode.text,
|
|
80
|
+
kind: 'constant',
|
|
81
|
+
line: node.startPosition.row + 1,
|
|
82
|
+
endLine: nodeEndLine(node),
|
|
64
83
|
});
|
|
65
84
|
}
|
|
66
85
|
break;
|
|
@@ -170,6 +189,57 @@ export function extractRustSymbols(tree, _filePath) {
|
|
|
170
189
|
return { definitions, calls, imports, classes, exports };
|
|
171
190
|
}
|
|
172
191
|
|
|
192
|
+
// ── Child extraction helpers ────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
function extractRustParameters(paramListNode) {
|
|
195
|
+
const params = [];
|
|
196
|
+
if (!paramListNode) return params;
|
|
197
|
+
for (let i = 0; i < paramListNode.childCount; i++) {
|
|
198
|
+
const param = paramListNode.child(i);
|
|
199
|
+
if (!param) continue;
|
|
200
|
+
if (param.type === 'self_parameter') {
|
|
201
|
+
params.push({ name: 'self', kind: 'parameter', line: param.startPosition.row + 1 });
|
|
202
|
+
} else if (param.type === 'parameter') {
|
|
203
|
+
const pattern = param.childForFieldName('pattern');
|
|
204
|
+
if (pattern) {
|
|
205
|
+
params.push({ name: pattern.text, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return params;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function extractStructFields(structNode) {
|
|
213
|
+
const fields = [];
|
|
214
|
+
const fieldList =
|
|
215
|
+
structNode.childForFieldName('body') || findChild(structNode, 'field_declaration_list');
|
|
216
|
+
if (!fieldList) return fields;
|
|
217
|
+
for (let i = 0; i < fieldList.childCount; i++) {
|
|
218
|
+
const field = fieldList.child(i);
|
|
219
|
+
if (!field || field.type !== 'field_declaration') continue;
|
|
220
|
+
const nameNode = field.childForFieldName('name');
|
|
221
|
+
if (nameNode) {
|
|
222
|
+
fields.push({ name: nameNode.text, kind: 'property', line: field.startPosition.row + 1 });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return fields;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function extractEnumVariants(enumNode) {
|
|
229
|
+
const variants = [];
|
|
230
|
+
const body = enumNode.childForFieldName('body') || findChild(enumNode, 'enum_variant_list');
|
|
231
|
+
if (!body) return variants;
|
|
232
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
233
|
+
const variant = body.child(i);
|
|
234
|
+
if (!variant || variant.type !== 'enum_variant') continue;
|
|
235
|
+
const nameNode = variant.childForFieldName('name');
|
|
236
|
+
if (nameNode) {
|
|
237
|
+
variants.push({ name: nameNode.text, kind: 'constant', line: variant.startPosition.row + 1 });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return variants;
|
|
241
|
+
}
|
|
242
|
+
|
|
173
243
|
function extractRustUsePath(node) {
|
|
174
244
|
if (!node) return [];
|
|
175
245
|
|
package/src/flow.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { openReadonlyOrFail } from './db.js';
|
|
9
|
-
import { paginateResult } from './paginate.js';
|
|
9
|
+
import { paginateResult, printNdjson } from './paginate.js';
|
|
10
10
|
import { isTestFile, kindIcon } from './queries.js';
|
|
11
11
|
import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
|
|
12
12
|
|
|
@@ -204,7 +204,7 @@ export function flowData(name, dbPath, opts = {}) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
db.close();
|
|
207
|
-
|
|
207
|
+
const base = {
|
|
208
208
|
entry,
|
|
209
209
|
depth: maxDepth,
|
|
210
210
|
steps,
|
|
@@ -213,6 +213,7 @@ export function flowData(name, dbPath, opts = {}) {
|
|
|
213
213
|
totalReached: visited.size - 1, // exclude the entry node itself
|
|
214
214
|
truncated,
|
|
215
215
|
};
|
|
216
|
+
return paginateResult(base, 'steps', { limit: opts.limit, offset: opts.offset });
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
/**
|
|
@@ -293,8 +294,7 @@ export function flow(name, dbPath, opts = {}) {
|
|
|
293
294
|
offset: opts.offset,
|
|
294
295
|
});
|
|
295
296
|
if (opts.ndjson) {
|
|
296
|
-
|
|
297
|
-
for (const e of data.entries) console.log(JSON.stringify(e));
|
|
297
|
+
printNdjson(data, 'entries');
|
|
298
298
|
return;
|
|
299
299
|
}
|
|
300
300
|
if (opts.json) {
|
package/src/index.js
CHANGED
|
@@ -5,10 +5,37 @@
|
|
|
5
5
|
* import { buildGraph, queryNameData, findCycles, exportDOT } from 'codegraph';
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
// AST node queries
|
|
9
|
+
export { AST_NODE_KINDS, astQuery, astQueryData } from './ast.js';
|
|
10
|
+
// Audit (composite report)
|
|
11
|
+
export { audit, auditData } from './audit.js';
|
|
12
|
+
// Batch querying
|
|
13
|
+
export {
|
|
14
|
+
BATCH_COMMANDS,
|
|
15
|
+
batch,
|
|
16
|
+
batchData,
|
|
17
|
+
batchQuery,
|
|
18
|
+
multiBatchData,
|
|
19
|
+
splitTargets,
|
|
20
|
+
} from './batch.js';
|
|
21
|
+
// Architecture boundary rules
|
|
22
|
+
export { evaluateBoundaries, PRESETS, validateBoundaryConfig } from './boundaries.js';
|
|
8
23
|
// Branch comparison
|
|
9
24
|
export { branchCompareData, branchCompareMermaid } from './branch-compare.js';
|
|
10
25
|
// Graph building
|
|
11
26
|
export { buildGraph, collectFiles, loadPathAliases, resolveImportPath } from './builder.js';
|
|
27
|
+
// Control flow graph (intraprocedural)
|
|
28
|
+
export {
|
|
29
|
+
buildCFGData,
|
|
30
|
+
buildFunctionCFG,
|
|
31
|
+
CFG_RULES,
|
|
32
|
+
cfg,
|
|
33
|
+
cfgData,
|
|
34
|
+
cfgToDOT,
|
|
35
|
+
cfgToMermaid,
|
|
36
|
+
} from './cfg.js';
|
|
37
|
+
// Check (CI validation predicates)
|
|
38
|
+
export { check, checkData } from './check.js';
|
|
12
39
|
// Co-change analysis
|
|
13
40
|
export {
|
|
14
41
|
analyzeCoChanges,
|
|
@@ -29,7 +56,9 @@ export {
|
|
|
29
56
|
computeHalsteadMetrics,
|
|
30
57
|
computeLOCMetrics,
|
|
31
58
|
computeMaintainabilityIndex,
|
|
59
|
+
findFunctionNode,
|
|
32
60
|
HALSTEAD_RULES,
|
|
61
|
+
iterComplexity,
|
|
33
62
|
} from './complexity.js';
|
|
34
63
|
// Configuration
|
|
35
64
|
export { loadConfig } from './config.js';
|
|
@@ -37,6 +66,15 @@ export { loadConfig } from './config.js';
|
|
|
37
66
|
export { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
|
|
38
67
|
// Circular dependency detection
|
|
39
68
|
export { findCycles, formatCycles } from './cycles.js';
|
|
69
|
+
// Dataflow analysis
|
|
70
|
+
export {
|
|
71
|
+
buildDataflowEdges,
|
|
72
|
+
dataflow,
|
|
73
|
+
dataflowData,
|
|
74
|
+
dataflowImpactData,
|
|
75
|
+
dataflowPathData,
|
|
76
|
+
extractDataflow,
|
|
77
|
+
} from './dataflow.js';
|
|
40
78
|
// Database utilities
|
|
41
79
|
export {
|
|
42
80
|
findDbPath,
|
|
@@ -55,13 +93,22 @@ export {
|
|
|
55
93
|
EMBEDDING_STRATEGIES,
|
|
56
94
|
embed,
|
|
57
95
|
estimateTokens,
|
|
96
|
+
ftsSearchData,
|
|
97
|
+
hybridSearchData,
|
|
58
98
|
MODELS,
|
|
59
99
|
multiSearchData,
|
|
60
100
|
search,
|
|
61
101
|
searchData,
|
|
62
102
|
} from './embedder.js';
|
|
63
|
-
// Export (DOT/Mermaid/JSON)
|
|
64
|
-
export {
|
|
103
|
+
// Export (DOT/Mermaid/JSON/GraphML/GraphSON/Neo4j CSV)
|
|
104
|
+
export {
|
|
105
|
+
exportDOT,
|
|
106
|
+
exportGraphML,
|
|
107
|
+
exportGraphSON,
|
|
108
|
+
exportJSON,
|
|
109
|
+
exportMermaid,
|
|
110
|
+
exportNeo4jCSV,
|
|
111
|
+
} from './export.js';
|
|
65
112
|
// Execution flow tracing
|
|
66
113
|
export { entryPointType, flowData, listEntryPointsData } from './flow.js';
|
|
67
114
|
// Logger
|
|
@@ -70,29 +117,44 @@ export { setVerbose } from './logger.js';
|
|
|
70
117
|
export { manifesto, manifestoData, RULE_DEFS } from './manifesto.js';
|
|
71
118
|
// Native engine
|
|
72
119
|
export { isNativeAvailable } from './native.js';
|
|
120
|
+
// Ownership (CODEOWNERS)
|
|
121
|
+
export { matchOwners, owners, ownersData, ownersForFiles, parseCodeowners } from './owners.js';
|
|
73
122
|
// Pagination utilities
|
|
74
|
-
export { MCP_DEFAULTS, MCP_MAX_LIMIT, paginate, paginateResult } from './paginate.js';
|
|
123
|
+
export { MCP_DEFAULTS, MCP_MAX_LIMIT, paginate, paginateResult, printNdjson } from './paginate.js';
|
|
75
124
|
|
|
76
125
|
// Unified parser API
|
|
77
126
|
export { getActiveEngine, parseFileAuto, parseFilesAuto } from './parser.js';
|
|
78
127
|
// Query functions (data-returning)
|
|
79
128
|
export {
|
|
80
129
|
ALL_SYMBOL_KINDS,
|
|
130
|
+
CORE_EDGE_KINDS,
|
|
131
|
+
CORE_SYMBOL_KINDS,
|
|
132
|
+
childrenData,
|
|
81
133
|
contextData,
|
|
82
134
|
diffImpactData,
|
|
83
135
|
diffImpactMermaid,
|
|
136
|
+
EVERY_EDGE_KIND,
|
|
137
|
+
EVERY_SYMBOL_KIND,
|
|
138
|
+
EXTENDED_SYMBOL_KINDS,
|
|
84
139
|
explainData,
|
|
140
|
+
exportsData,
|
|
85
141
|
FALSE_POSITIVE_CALLER_THRESHOLD,
|
|
86
142
|
FALSE_POSITIVE_NAMES,
|
|
87
143
|
fileDepsData,
|
|
144
|
+
fileExports,
|
|
88
145
|
fnDepsData,
|
|
89
146
|
fnImpactData,
|
|
90
147
|
impactAnalysisData,
|
|
148
|
+
iterListFunctions,
|
|
149
|
+
iterRoles,
|
|
150
|
+
iterWhere,
|
|
91
151
|
kindIcon,
|
|
92
152
|
moduleMapData,
|
|
153
|
+
normalizeSymbol,
|
|
93
154
|
pathData,
|
|
94
155
|
queryNameData,
|
|
95
156
|
rolesData,
|
|
157
|
+
STRUCTURAL_EDGE_KINDS,
|
|
96
158
|
statsData,
|
|
97
159
|
VALID_ROLES,
|
|
98
160
|
whereData,
|
|
@@ -108,6 +170,15 @@ export {
|
|
|
108
170
|
saveRegistry,
|
|
109
171
|
unregisterRepo,
|
|
110
172
|
} from './registry.js';
|
|
173
|
+
// Snapshot management
|
|
174
|
+
export {
|
|
175
|
+
snapshotDelete,
|
|
176
|
+
snapshotList,
|
|
177
|
+
snapshotRestore,
|
|
178
|
+
snapshotSave,
|
|
179
|
+
snapshotsDir,
|
|
180
|
+
validateSnapshotName,
|
|
181
|
+
} from './snapshot.js';
|
|
111
182
|
// Structure analysis
|
|
112
183
|
export {
|
|
113
184
|
buildStructure,
|
|
@@ -120,5 +191,9 @@ export {
|
|
|
120
191
|
moduleBoundariesData,
|
|
121
192
|
structureData,
|
|
122
193
|
} from './structure.js';
|
|
194
|
+
// Triage — composite risk audit
|
|
195
|
+
export { triage, triageData } from './triage.js';
|
|
196
|
+
// Interactive HTML viewer
|
|
197
|
+
export { generatePlotHTML, loadPlotConfig } from './viewer.js';
|
|
123
198
|
// Watch mode
|
|
124
199
|
export { watchProject } from './watcher.js';
|
package/src/manifesto.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { evaluateBoundaries } from './boundaries.js';
|
|
1
2
|
import { loadConfig } from './config.js';
|
|
2
3
|
import { findCycles } from './cycles.js';
|
|
3
4
|
import { openReadonlyOrFail } from './db.js';
|
|
4
5
|
import { debug } from './logger.js';
|
|
6
|
+
import { paginateResult, printNdjson } from './paginate.js';
|
|
5
7
|
|
|
6
8
|
// ─── Rule Definitions ─────────────────────────────────────────────────
|
|
7
9
|
|
|
@@ -54,6 +56,12 @@ export const RULE_DEFS = [
|
|
|
54
56
|
{ name: 'fanIn', level: 'file', metric: 'fan_in', defaults: { warn: null, fail: null } },
|
|
55
57
|
{ name: 'fanOut', level: 'file', metric: 'fan_out', defaults: { warn: null, fail: null } },
|
|
56
58
|
{ name: 'noCycles', level: 'graph', metric: 'noCycles', defaults: { warn: null, fail: null } },
|
|
59
|
+
{
|
|
60
|
+
name: 'boundaries',
|
|
61
|
+
level: 'graph',
|
|
62
|
+
metric: 'boundaries',
|
|
63
|
+
defaults: { warn: null, fail: null },
|
|
64
|
+
},
|
|
57
65
|
];
|
|
58
66
|
|
|
59
67
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
@@ -318,6 +326,59 @@ function evaluateGraphRules(db, rules, opts, violations, ruleResults) {
|
|
|
318
326
|
});
|
|
319
327
|
}
|
|
320
328
|
|
|
329
|
+
function evaluateBoundaryRules(db, rules, config, opts, violations, ruleResults) {
|
|
330
|
+
const thresholds = rules.boundaries;
|
|
331
|
+
const boundaryConfig = config.manifesto?.boundaries;
|
|
332
|
+
|
|
333
|
+
// Auto-enable at warn level when boundary config exists but threshold not set
|
|
334
|
+
const effectiveThresholds = { ...thresholds };
|
|
335
|
+
if (boundaryConfig && !isEnabled(thresholds)) {
|
|
336
|
+
effectiveThresholds.warn = true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!isEnabled(effectiveThresholds) || !boundaryConfig) {
|
|
340
|
+
ruleResults.push({
|
|
341
|
+
name: 'boundaries',
|
|
342
|
+
level: 'graph',
|
|
343
|
+
status: 'pass',
|
|
344
|
+
thresholds: effectiveThresholds,
|
|
345
|
+
violationCount: 0,
|
|
346
|
+
});
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const result = evaluateBoundaries(db, boundaryConfig, { noTests: opts.noTests || false });
|
|
351
|
+
const hasBoundaryViolations = result.violationCount > 0;
|
|
352
|
+
|
|
353
|
+
if (!hasBoundaryViolations) {
|
|
354
|
+
ruleResults.push({
|
|
355
|
+
name: 'boundaries',
|
|
356
|
+
level: 'graph',
|
|
357
|
+
status: 'pass',
|
|
358
|
+
thresholds: effectiveThresholds,
|
|
359
|
+
violationCount: 0,
|
|
360
|
+
});
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const level = effectiveThresholds.fail != null ? 'fail' : 'warn';
|
|
365
|
+
|
|
366
|
+
for (const v of result.violations) {
|
|
367
|
+
violations.push({
|
|
368
|
+
...v,
|
|
369
|
+
level,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
ruleResults.push({
|
|
374
|
+
name: 'boundaries',
|
|
375
|
+
level: 'graph',
|
|
376
|
+
status: level,
|
|
377
|
+
thresholds: effectiveThresholds,
|
|
378
|
+
violationCount: result.violationCount,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
321
382
|
// ─── Public API ───────────────────────────────────────────────────────
|
|
322
383
|
|
|
323
384
|
/**
|
|
@@ -343,6 +404,7 @@ export function manifestoData(customDbPath, opts = {}) {
|
|
|
343
404
|
evaluateFunctionRules(db, rules, opts, violations, ruleResults);
|
|
344
405
|
evaluateFileRules(db, rules, opts, violations, ruleResults);
|
|
345
406
|
evaluateGraphRules(db, rules, opts, violations, ruleResults);
|
|
407
|
+
evaluateBoundaryRules(db, rules, config, opts, violations, ruleResults);
|
|
346
408
|
|
|
347
409
|
const failViolations = violations.filter((v) => v.level === 'fail');
|
|
348
410
|
|
|
@@ -354,12 +416,13 @@ export function manifestoData(customDbPath, opts = {}) {
|
|
|
354
416
|
violationCount: violations.length,
|
|
355
417
|
};
|
|
356
418
|
|
|
357
|
-
|
|
419
|
+
const base = {
|
|
358
420
|
rules: ruleResults,
|
|
359
421
|
violations,
|
|
360
422
|
summary,
|
|
361
423
|
passed: failViolations.length === 0,
|
|
362
424
|
};
|
|
425
|
+
return paginateResult(base, 'violations', { limit: opts.limit, offset: opts.offset });
|
|
363
426
|
} finally {
|
|
364
427
|
db.close();
|
|
365
428
|
}
|
|
@@ -371,6 +434,11 @@ export function manifestoData(customDbPath, opts = {}) {
|
|
|
371
434
|
export function manifesto(customDbPath, opts = {}) {
|
|
372
435
|
const data = manifestoData(customDbPath, opts);
|
|
373
436
|
|
|
437
|
+
if (opts.ndjson) {
|
|
438
|
+
printNdjson(data, 'violations');
|
|
439
|
+
if (!data.passed) process.exit(1);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
374
442
|
if (opts.json) {
|
|
375
443
|
console.log(JSON.stringify(data, null, 2));
|
|
376
444
|
if (!data.passed) process.exit(1);
|