arcvision 0.2.25 → 0.2.26
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 +59 -0
- package/package.json +1 -1
- package/src/core/authority-ledger.js +18 -20
- package/src/core/change-evaluator.js +56 -53
- package/src/core/cli-validator.js +59 -61
- package/src/core/invariant-detector.js +123 -103
- package/src/core/override-handler.js +28 -23
- package/src/core/scanner.js +114 -74
- package/src/engine/pass4_signals.js +12 -40
- package/src/index.js +38 -91
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ArcVision CLI
|
|
2
|
+
|
|
3
|
+
**Architectural Governance and Invariant Detection Tool**
|
|
4
|
+
|
|
5
|
+
ArcVision is a powerful 4-pass structural analysis engine designed to map, monitor, and enforce architectural integrity in modern codebases.
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
- **4-Pass Structural Analysis**: Deeply analyzes facts, semantics, structural roles, and signals.
|
|
10
|
+
- **Invariant Detection**: Automatically identifies architectural constraints and assertions.
|
|
11
|
+
- **Change Impact Gating**: Evaluates the blast radius and architectural risk of proposed code changes.
|
|
12
|
+
- **Authority Ledger**: Maintains a verifiable audit trail of architectural decisions and overrides.
|
|
13
|
+
- **Dashboard Integration**: Syncs architectural maps and health metrics to the ArcVision Dashboard.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g arcvision
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Scan a repository
|
|
24
|
+
Generate a comprehensive architectural context and visualize your system structure.
|
|
25
|
+
```bash
|
|
26
|
+
arcvision scan .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Evaluate changes
|
|
30
|
+
Assess the impact of changes against established invariants.
|
|
31
|
+
```bash
|
|
32
|
+
arcvision evaluate . --simulate-changes src/critical-module.js
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. Record decisions
|
|
36
|
+
Manage architectural decisions through the Authority Ledger.
|
|
37
|
+
```bash
|
|
38
|
+
arcvision override --event-id <event-id> --reason "Intentional architectural shift" --owner "arch-lead"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
- `scan <directory>`: Analyze repository and generate `arcvision.context.json`.
|
|
44
|
+
- `evaluate <directory>`: Run Authoritative Change Impact Gate (ACIG).
|
|
45
|
+
- `diff <old> <new>`: Compare two architectural snapshots.
|
|
46
|
+
- `ledger`: Manage and audit architectural overrides.
|
|
47
|
+
- `link <token>`: Connect your local environment to the ArcVision Dashboard.
|
|
48
|
+
|
|
49
|
+
## Architectural Health Assessment
|
|
50
|
+
|
|
51
|
+
ArcVision computes an architectural health score based on:
|
|
52
|
+
- Invariant coverage
|
|
53
|
+
- Coupling degree
|
|
54
|
+
- Criticality signals
|
|
55
|
+
- Structural complexity
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
package/package.json
CHANGED
|
@@ -9,9 +9,10 @@ const crypto = require('crypto');
|
|
|
9
9
|
|
|
10
10
|
class AuthorityLedger {
|
|
11
11
|
constructor(ledgerPath) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (!ledgerPath) {
|
|
13
|
+
throw new Error('AuthorityLedger requires an explicit ledgerPath');
|
|
14
|
+
}
|
|
15
|
+
this.ledgerPath = ledgerPath;
|
|
15
16
|
this.ensureLedgerExists();
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -26,7 +27,7 @@ class AuthorityLedger {
|
|
|
26
27
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
27
28
|
console.log(`📁 Created directory for authority ledger: ${dirPath}`);
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
if (!fs.existsSync(this.ledgerPath)) {
|
|
31
32
|
const initialLedger = {
|
|
32
33
|
schema_version: process.env.ARCHITECTURE_LEDGER_SCHEMA_VERSION || "1.0",
|
|
@@ -48,7 +49,7 @@ class AuthorityLedger {
|
|
|
48
49
|
*/
|
|
49
50
|
getSystemId() {
|
|
50
51
|
try {
|
|
51
|
-
const cwd =
|
|
52
|
+
const cwd = path.dirname(path.dirname(this.ledgerPath)); // One level up from arcvision_context
|
|
52
53
|
const projectName = path.basename(cwd);
|
|
53
54
|
return projectName;
|
|
54
55
|
} catch (error) {
|
|
@@ -115,8 +116,8 @@ class AuthorityLedger {
|
|
|
115
116
|
isOverridden(commitHash) {
|
|
116
117
|
try {
|
|
117
118
|
const ledger = this.readLedger();
|
|
118
|
-
return ledger.ledger.some(event =>
|
|
119
|
-
event.decision === 'OVERRIDDEN' &&
|
|
119
|
+
return ledger.ledger.some(event =>
|
|
120
|
+
event.decision === 'OVERRIDDEN' &&
|
|
120
121
|
event.commit === commitHash
|
|
121
122
|
);
|
|
122
123
|
} catch (error) {
|
|
@@ -150,7 +151,7 @@ class AuthorityLedger {
|
|
|
150
151
|
|
|
151
152
|
// Filter out events that have been overridden
|
|
152
153
|
return blockedEvents.filter(blockedEvent => {
|
|
153
|
-
return !ledger.ledger.some(overrideEvent =>
|
|
154
|
+
return !ledger.ledger.some(overrideEvent =>
|
|
154
155
|
overrideEvent.decision === 'OVERRIDDEN' &&
|
|
155
156
|
overrideEvent.original_blocked_event === blockedEvent.event_id
|
|
156
157
|
);
|
|
@@ -174,7 +175,7 @@ class AuthorityLedger {
|
|
|
174
175
|
appendEvent(event) {
|
|
175
176
|
try {
|
|
176
177
|
const ledger = this.readLedger();
|
|
177
|
-
|
|
178
|
+
|
|
178
179
|
// Validate event structure
|
|
179
180
|
if (!event.event_id || !event.timestamp || !event.decision) {
|
|
180
181
|
throw new Error('Invalid event structure');
|
|
@@ -182,7 +183,7 @@ class AuthorityLedger {
|
|
|
182
183
|
|
|
183
184
|
ledger.ledger.push(event);
|
|
184
185
|
this.writeLedger(ledger);
|
|
185
|
-
|
|
186
|
+
|
|
186
187
|
console.log(`Recorded ${event.decision} decision in authority ledger`);
|
|
187
188
|
} catch (error) {
|
|
188
189
|
console.error('Failed to append to authority ledger:', error.message);
|
|
@@ -197,12 +198,12 @@ class AuthorityLedger {
|
|
|
197
198
|
try {
|
|
198
199
|
const content = fs.readFileSync(this.ledgerPath, 'utf8');
|
|
199
200
|
const ledger = JSON.parse(content);
|
|
200
|
-
|
|
201
|
+
|
|
201
202
|
// Validate structure
|
|
202
203
|
if (!ledger.schema_version || !Array.isArray(ledger.ledger)) {
|
|
203
204
|
throw new Error('Invalid ledger structure');
|
|
204
205
|
}
|
|
205
|
-
|
|
206
|
+
|
|
206
207
|
return ledger;
|
|
207
208
|
} catch (error) {
|
|
208
209
|
console.error('Failed to read authority ledger:', error.message);
|
|
@@ -232,7 +233,7 @@ class AuthorityLedger {
|
|
|
232
233
|
const ledger = this.readLedger();
|
|
233
234
|
const blockedCount = ledger.ledger.filter(e => e.decision === 'BLOCKED').length;
|
|
234
235
|
const overriddenCount = ledger.ledger.filter(e => e.decision === 'OVERRIDDEN').length;
|
|
235
|
-
|
|
236
|
+
|
|
236
237
|
return {
|
|
237
238
|
total_events: ledger.ledger.length,
|
|
238
239
|
blocked_decisions: blockedCount,
|
|
@@ -256,7 +257,7 @@ class AuthorityLedger {
|
|
|
256
257
|
exportLedger(format = 'json') {
|
|
257
258
|
try {
|
|
258
259
|
const ledger = this.readLedger();
|
|
259
|
-
|
|
260
|
+
|
|
260
261
|
switch (format.toLowerCase()) {
|
|
261
262
|
case 'json':
|
|
262
263
|
return JSON.stringify(ledger, null, 2);
|
|
@@ -284,17 +285,14 @@ class AuthorityLedger {
|
|
|
284
285
|
event.author,
|
|
285
286
|
event.violations ? event.violations.length : 0
|
|
286
287
|
]);
|
|
287
|
-
|
|
288
|
+
|
|
288
289
|
const csvContent = [
|
|
289
290
|
headers.join(','),
|
|
290
291
|
...rows.map(row => row.join(','))
|
|
291
292
|
].join('\n');
|
|
292
|
-
|
|
293
|
+
|
|
293
294
|
return csvContent;
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
|
|
297
|
-
|
|
298
|
-
const authorityLedger = new AuthorityLedger();
|
|
299
|
-
|
|
300
|
-
module.exports = { AuthorityLedger, authorityLedger };
|
|
298
|
+
module.exports = { AuthorityLedger };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* Change Impact Evaluator
|
|
3
3
|
* Connects existing ArcVision data to invariants
|
|
4
4
|
*/
|
|
@@ -16,16 +16,16 @@ function evaluateChange(input) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const { changedFiles, dependencyGraph, invariants, context } = input;
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
// Validate required parameters with enhanced checks
|
|
21
21
|
if (!Array.isArray(changedFiles)) {
|
|
22
22
|
return createEnhancedErrorResult('Invalid input: changedFiles must be an array');
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
if (!dependencyGraph) {
|
|
26
26
|
return createEnhancedErrorResult('Invalid input: dependencyGraph is required');
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
if (!Array.isArray(invariants)) {
|
|
30
30
|
return createEnhancedErrorResult('Invalid input: invariants must be an array');
|
|
31
31
|
}
|
|
@@ -48,10 +48,10 @@ function evaluateChange(input) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const matchesScope = matchesInvariantScope(sanitizedChangedFiles, invariant);
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
if (matchesScope) {
|
|
53
53
|
const isViolated = checkInvariantRule(invariant, dependencyGraph, sanitizedChangedFiles);
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
if (isViolated) {
|
|
56
56
|
violations.push({
|
|
57
57
|
...invariant,
|
|
@@ -60,7 +60,7 @@ function evaluateChange(input) {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
processedInvariants.push(invariant.id);
|
|
65
65
|
} catch (ruleError) {
|
|
66
66
|
console.warn(`Warning: Error evaluating invariant ${invariant?.id || 'unknown'}:`, ruleError.message);
|
|
@@ -75,7 +75,7 @@ function evaluateChange(input) {
|
|
|
75
75
|
// Determine decision based on violations with enhanced severity handling
|
|
76
76
|
const blockingViolations = violations.filter(v => v.severity === 'block' || v.severity === 'BLOCK');
|
|
77
77
|
const riskyViolations = violations.filter(v => v.severity === 'risk' || v.severity === 'RISK');
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
if (blockingViolations.length > 0) {
|
|
80
80
|
return formatEnhancedResult('BLOCKED', violations, input);
|
|
81
81
|
}
|
|
@@ -116,7 +116,7 @@ function matchesInvariantScope(changedFiles, invariant) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const scope = invariant.scope;
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
// If no scope restrictions, always matches
|
|
121
121
|
if (!scope.files && !scope.components && !scope.flows) {
|
|
122
122
|
return true;
|
|
@@ -210,25 +210,25 @@ function checkInvariantRule(invariant, dependencyGraph, changedFiles) {
|
|
|
210
210
|
*/
|
|
211
211
|
function checkGenericRule(condition, dependencyGraph, changedFiles) {
|
|
212
212
|
if (!condition) return false;
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
try {
|
|
215
215
|
// Handle different condition types dynamically
|
|
216
216
|
if (condition.forbiddenDependency && typeof condition.forbiddenDependency === 'object') {
|
|
217
217
|
return checkForbiddenDependency(condition.forbiddenDependency, dependencyGraph, changedFiles);
|
|
218
218
|
}
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
if (condition.mustExist) {
|
|
221
221
|
return checkRequirement(condition.mustExist, dependencyGraph, changedFiles);
|
|
222
222
|
}
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
if (condition.mustNotExist) {
|
|
225
225
|
return checkProhibition(condition.mustNotExist, dependencyGraph, changedFiles);
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
if (condition.pattern) {
|
|
229
229
|
return checkPatternMatch(condition.pattern, dependencyGraph, changedFiles);
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
// Default: if condition exists and has properties, consider it potentially violated
|
|
233
233
|
return typeof condition === 'object' && Object.keys(condition).length > 0;
|
|
234
234
|
} catch (error) {
|
|
@@ -252,38 +252,38 @@ function checkForbiddenDependency(forbiddenDep, dependencyGraph, changedFiles) {
|
|
|
252
252
|
if (!forbiddenDep || typeof forbiddenDep !== 'object') {
|
|
253
253
|
return false;
|
|
254
254
|
}
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
if (!forbiddenDep.from || !forbiddenDep.to) {
|
|
257
257
|
return false;
|
|
258
258
|
}
|
|
259
|
-
|
|
259
|
+
|
|
260
260
|
try {
|
|
261
261
|
// Use enhanced circular dependency detector
|
|
262
262
|
const circularDetector = new CircularDependencyDetector();
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
// Check if the forbidden dependency pattern is violated
|
|
265
265
|
const result = circularDetector.checkEnhancedForbiddenDependency(
|
|
266
|
-
forbiddenDep,
|
|
267
|
-
dependencyGraph.nodes || [],
|
|
266
|
+
forbiddenDep,
|
|
267
|
+
dependencyGraph.nodes || [],
|
|
268
268
|
dependencyGraph.edges || []
|
|
269
269
|
);
|
|
270
|
-
|
|
270
|
+
|
|
271
271
|
if (result.violated) {
|
|
272
272
|
console.log(`🚨 Forbidden dependency violation detected: ${result.description}`);
|
|
273
273
|
return true;
|
|
274
274
|
}
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
// Fallback to original method for backward compatibility
|
|
277
277
|
const { patternMatcher } = require('./pattern-matcher');
|
|
278
|
-
|
|
278
|
+
|
|
279
279
|
return changedFiles.some(file => {
|
|
280
280
|
if (typeof file !== 'string') return false;
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
// Check if file matches the 'from' scope using robust pattern matching
|
|
283
283
|
const matchesFrom = patternMatcher.match(file, forbiddenDep.from, { matchBase: true }) ||
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
284
|
+
patternMatcher.match(file, `**/${forbiddenDep.from}/**`, { matchBase: false }) ||
|
|
285
|
+
file.toLowerCase().includes(forbiddenDep.from.toLowerCase());
|
|
286
|
+
|
|
287
287
|
if (matchesFrom) {
|
|
288
288
|
// Check if this file now depends on the 'to' component
|
|
289
289
|
return hasDependencyTo(dependencyGraph, file, forbiddenDep.to);
|
|
@@ -333,20 +333,20 @@ function checkPatternRule(invariant, dependencyGraph, changedFiles) {
|
|
|
333
333
|
const { patternMatcher } = require('./pattern-matcher');
|
|
334
334
|
const fs = require('fs');
|
|
335
335
|
const path = require('path');
|
|
336
|
-
|
|
336
|
+
|
|
337
337
|
// Convert pattern to regex for proper matching
|
|
338
338
|
let regexPattern;
|
|
339
339
|
try {
|
|
340
340
|
// Handle the pipe (OR) operator and other regex constructs
|
|
341
341
|
const escapedPattern = pattern.replace(/\./g, '\\.')
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
.replace(/\*/g, '.*')
|
|
343
|
+
.replace(/\+/g, '\\+');
|
|
344
344
|
regexPattern = new RegExp(escapedPattern, 'gi');
|
|
345
345
|
} catch (regexError) {
|
|
346
346
|
console.warn(`Warning: Could not create regex from pattern "${pattern}": ${regexError.message}`);
|
|
347
347
|
return false;
|
|
348
348
|
}
|
|
349
|
-
|
|
349
|
+
|
|
350
350
|
// Check if any node content matches the pattern by reading actual files
|
|
351
351
|
if (dependencyGraph.nodes && Array.isArray(dependencyGraph.nodes)) {
|
|
352
352
|
return dependencyGraph.nodes.some(node => {
|
|
@@ -356,20 +356,23 @@ function checkPatternRule(invariant, dependencyGraph, changedFiles) {
|
|
|
356
356
|
console.log(`🚨 Pattern violation detected in ${node.path}: matches pattern "${pattern}"`);
|
|
357
357
|
return true;
|
|
358
358
|
}
|
|
359
|
-
|
|
359
|
+
|
|
360
360
|
// Read actual file content to check for pattern violations
|
|
361
361
|
try {
|
|
362
|
-
// Resolve the full file path
|
|
363
|
-
const
|
|
362
|
+
// Resolve the full file path relative to system root if available
|
|
363
|
+
const rootPath = dependencyGraph.system && dependencyGraph.system.root_path ?
|
|
364
|
+
dependencyGraph.system.root_path :
|
|
365
|
+
process.cwd();
|
|
366
|
+
const fullPath = path.resolve(rootPath, node.path);
|
|
364
367
|
if (fs.existsSync(fullPath)) {
|
|
365
368
|
const fileContent = fs.readFileSync(fullPath, 'utf8');
|
|
366
|
-
|
|
369
|
+
|
|
367
370
|
// Check if file content matches the regex pattern
|
|
368
371
|
if (regexPattern.test(fileContent)) {
|
|
369
372
|
console.log(`🚨 Pattern violation detected in ${node.path}: content matches pattern "${pattern}"`);
|
|
370
373
|
return true;
|
|
371
374
|
}
|
|
372
|
-
|
|
375
|
+
|
|
373
376
|
// Also check with pattern matcher for glob patterns
|
|
374
377
|
if (patternMatcher.match(fileContent, `*${pattern}*`, { matchBase: false })) {
|
|
375
378
|
console.log(`🚨 Pattern violation detected in ${node.path}: content matches pattern "${pattern}"`);
|
|
@@ -380,7 +383,7 @@ function checkPatternRule(invariant, dependencyGraph, changedFiles) {
|
|
|
380
383
|
// Silently continue if file can't be read
|
|
381
384
|
}
|
|
382
385
|
}
|
|
383
|
-
|
|
386
|
+
|
|
384
387
|
return false;
|
|
385
388
|
});
|
|
386
389
|
}
|
|
@@ -388,7 +391,7 @@ function checkPatternRule(invariant, dependencyGraph, changedFiles) {
|
|
|
388
391
|
} catch (error) {
|
|
389
392
|
console.warn(`Warning: Error in pattern rule check:`, error.message);
|
|
390
393
|
}
|
|
391
|
-
|
|
394
|
+
|
|
392
395
|
return false;
|
|
393
396
|
}
|
|
394
397
|
|
|
@@ -400,7 +403,7 @@ function checkExistenceRule(invariant, dependencyGraph, changedFiles) {
|
|
|
400
403
|
if (invariant.rule.condition && invariant.rule.condition.mustExist) {
|
|
401
404
|
const requiredElement = invariant.rule.condition.mustExist;
|
|
402
405
|
const { patternMatcher } = require('./pattern-matcher');
|
|
403
|
-
|
|
406
|
+
|
|
404
407
|
// Check if required element exists
|
|
405
408
|
if (dependencyGraph.nodes && Array.isArray(dependencyGraph.nodes)) {
|
|
406
409
|
const exists = dependencyGraph.nodes.some(node => {
|
|
@@ -409,18 +412,18 @@ function checkExistenceRule(invariant, dependencyGraph, changedFiles) {
|
|
|
409
412
|
}
|
|
410
413
|
return false;
|
|
411
414
|
});
|
|
412
|
-
|
|
415
|
+
|
|
413
416
|
if (!exists) {
|
|
414
417
|
console.log(`🚨 Existence violation: Required element "${requiredElement}" not found`);
|
|
415
418
|
return true; // Violation - required element doesn't exist
|
|
416
419
|
}
|
|
417
420
|
}
|
|
418
421
|
}
|
|
419
|
-
|
|
422
|
+
|
|
420
423
|
if (invariant.rule.condition && invariant.rule.condition.mustNotExist) {
|
|
421
424
|
const forbiddenElement = invariant.rule.condition.mustNotExist;
|
|
422
425
|
const { patternMatcher } = require('./pattern-matcher');
|
|
423
|
-
|
|
426
|
+
|
|
424
427
|
// Check if forbidden element exists
|
|
425
428
|
if (dependencyGraph.nodes && Array.isArray(dependencyGraph.nodes)) {
|
|
426
429
|
const exists = dependencyGraph.nodes.some(node => {
|
|
@@ -429,7 +432,7 @@ function checkExistenceRule(invariant, dependencyGraph, changedFiles) {
|
|
|
429
432
|
}
|
|
430
433
|
return false;
|
|
431
434
|
});
|
|
432
|
-
|
|
435
|
+
|
|
433
436
|
if (exists) {
|
|
434
437
|
console.log(`🚨 Existence violation: Forbidden element "${forbiddenElement}" found`);
|
|
435
438
|
return true; // Violation - forbidden element exists
|
|
@@ -439,7 +442,7 @@ function checkExistenceRule(invariant, dependencyGraph, changedFiles) {
|
|
|
439
442
|
} catch (error) {
|
|
440
443
|
console.warn(`Warning: Error in existence rule check:`, error.message);
|
|
441
444
|
}
|
|
442
|
-
|
|
445
|
+
|
|
443
446
|
return false;
|
|
444
447
|
}
|
|
445
448
|
|
|
@@ -459,11 +462,11 @@ function checkOwnershipRule(invariant, dependencyGraph, changedFiles) {
|
|
|
459
462
|
// Check if changed files fall under this invariant's scope and if ownership is violated
|
|
460
463
|
if (invariant.rule.condition && invariant.scope.files) {
|
|
461
464
|
const { authorizedOwners } = invariant.rule.condition;
|
|
462
|
-
|
|
465
|
+
|
|
463
466
|
if (authorizedOwners && Array.isArray(authorizedOwners)) {
|
|
464
467
|
return changedFiles.some(file => {
|
|
465
468
|
if (typeof file !== 'string') return false;
|
|
466
|
-
|
|
469
|
+
|
|
467
470
|
// Check if file matches the scope
|
|
468
471
|
const matchesScope = invariant.scope.files.some(pattern => {
|
|
469
472
|
if (typeof pattern !== 'string') return false;
|
|
@@ -475,7 +478,7 @@ function checkOwnershipRule(invariant, dependencyGraph, changedFiles) {
|
|
|
475
478
|
return false;
|
|
476
479
|
}
|
|
477
480
|
});
|
|
478
|
-
|
|
481
|
+
|
|
479
482
|
if (matchesScope) {
|
|
480
483
|
// Check if current owner is authorized (this would require additional context about who made the change)
|
|
481
484
|
// For now, we'll just check if the invariant has an owner specified and it's different
|
|
@@ -489,7 +492,7 @@ function checkOwnershipRule(invariant, dependencyGraph, changedFiles) {
|
|
|
489
492
|
console.warn(`Warning: Error in ownership rule check:`, error.message);
|
|
490
493
|
return false;
|
|
491
494
|
}
|
|
492
|
-
|
|
495
|
+
|
|
493
496
|
return false;
|
|
494
497
|
}
|
|
495
498
|
|
|
@@ -516,22 +519,22 @@ function hasDependencyTo(dependencyGraph, fromFile, toComponent) {
|
|
|
516
519
|
if (typeof fromFile !== 'string' || typeof toComponent !== 'string') {
|
|
517
520
|
return false;
|
|
518
521
|
}
|
|
519
|
-
|
|
522
|
+
|
|
520
523
|
try {
|
|
521
524
|
// Simplified implementation - would need to analyze actual dependency graph
|
|
522
525
|
if (dependencyGraph && dependencyGraph.edges && Array.isArray(dependencyGraph.edges)) {
|
|
523
526
|
return dependencyGraph.edges.some((edge) => {
|
|
524
527
|
if (!edge || !edge.source || !edge.target) return false;
|
|
525
|
-
|
|
526
|
-
return edge.source === fromFile &&
|
|
527
|
-
|
|
528
|
-
|
|
528
|
+
|
|
529
|
+
return edge.source === fromFile &&
|
|
530
|
+
(edge.target.includes(toComponent) ||
|
|
531
|
+
edge.target.toLowerCase().includes(toComponent.toLowerCase()));
|
|
529
532
|
});
|
|
530
533
|
}
|
|
531
534
|
} catch (error) {
|
|
532
535
|
console.warn(`Warning: Error checking dependency graph:`, error.message);
|
|
533
536
|
}
|
|
534
|
-
|
|
537
|
+
|
|
535
538
|
return false;
|
|
536
539
|
}
|
|
537
540
|
|