cerber-core 1.1.6 → 1.1.8
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/CHANGELOG.md +111 -0
- package/README.md +157 -1
- package/bin/cerber +14 -1
- package/dev/templates/BACKEND_SCHEMA.ts.tpl +87 -50
- package/dev/templates/cerber-guardian.mjs.tpl +255 -148
- package/dev/templates/cerber.yml.tpl +96 -53
- package/dev/templates/pre-commit.tpl +4 -4
- package/dist/cli/contract-parser.d.ts.map +1 -1
- package/dist/cli/contract-parser.js +47 -20
- package/dist/cli/contract-parser.js.map +1 -1
- package/dist/cli/doctor.d.ts +16 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +140 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +98 -8
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/override-validator.d.ts +27 -0
- package/dist/cli/override-validator.d.ts.map +1 -0
- package/dist/cli/override-validator.js +100 -0
- package/dist/cli/override-validator.js.map +1 -0
- package/dist/cli/template-generator.d.ts.map +1 -1
- package/dist/cli/template-generator.js +71 -16
- package/dist/cli/template-generator.js.map +1 -1
- package/dist/cli/types.d.ts +7 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +117 -117
- package/solo/templates/BACKEND_SCHEMA.ts.tpl +87 -50
- package/solo/templates/cerber-guardian.mjs.tpl +255 -148
- package/solo/templates/cerber.yml.tpl +96 -53
- package/solo/templates/pre-commit.tpl +4 -4
- package/team/templates/BACKEND_SCHEMA.ts.tpl +87 -50
- package/team/templates/CODEOWNERS.tpl +18 -6
- package/team/templates/cerber-guardian.mjs.tpl +255 -148
- package/team/templates/cerber.yml.tpl +96 -53
- package/team/templates/pre-commit.tpl +4 -4
|
@@ -1,50 +1,87 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ⚠️ CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
-
*
|
|
4
|
-
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
-
* The source of truth is CERBER.md.
|
|
6
|
-
*
|
|
7
|
-
* Generated by: npx cerber init
|
|
8
|
-
* Project: {{PROJECT_NAME}}
|
|
9
|
-
*
|
|
10
|
-
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export const BACKEND_SCHEMA = {
|
|
14
|
-
version: "1.0.0",
|
|
15
|
-
|
|
16
|
-
// Your architecture rules (customize)
|
|
17
|
-
rules: [],
|
|
18
|
-
|
|
19
|
-
// Patterns that should never appear in your code
|
|
20
|
-
forbiddenPatterns: [
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ⚠️ CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
+
*
|
|
4
|
+
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
+
* The source of truth is CERBER.md.
|
|
6
|
+
*
|
|
7
|
+
* Generated by: npx cerber init
|
|
8
|
+
* Project: {{PROJECT_NAME}}
|
|
9
|
+
*
|
|
10
|
+
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const BACKEND_SCHEMA = {
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
|
|
16
|
+
// Your architecture rules (customize)
|
|
17
|
+
rules: [],
|
|
18
|
+
|
|
19
|
+
// Patterns that should never appear in your code
|
|
20
|
+
forbiddenPatterns: [
|
|
21
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
22
|
+
// 🔐 SECURITY BASELINE (Uncomment and adapt to your CERBER.md contract)
|
|
23
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
// Hardcoded secrets (common examples - adapt to your stack):
|
|
26
|
+
// {
|
|
27
|
+
// pattern: "password\\s*=\\s*['\"][^'\"]+['\"]" ,
|
|
28
|
+
// flags: "i",
|
|
29
|
+
// name: "Hardcoded passwords (e.g., password='admin123')",
|
|
30
|
+
// severity: "error"
|
|
31
|
+
// },
|
|
32
|
+
// {
|
|
33
|
+
// pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]" ,
|
|
34
|
+
// flags: "i",
|
|
35
|
+
// name: "Hardcoded API keys (e.g., API_KEY='xyz')",
|
|
36
|
+
// severity: "error"
|
|
37
|
+
// },
|
|
38
|
+
// {
|
|
39
|
+
// pattern: "JWT_SECRET\\s*=\\s*['\"][^'\"]+['\"]" ,
|
|
40
|
+
// flags: "i",
|
|
41
|
+
// name: "Hardcoded JWT secrets",
|
|
42
|
+
// severity: "error"
|
|
43
|
+
// },
|
|
44
|
+
|
|
45
|
+
// Development artifacts (should not reach production):
|
|
46
|
+
// {
|
|
47
|
+
// pattern: "console\\.log\\(",
|
|
48
|
+
// flags: "g",
|
|
49
|
+
// name: "console.log (use proper logging)",
|
|
50
|
+
// severity: "warning"
|
|
51
|
+
// },
|
|
52
|
+
// {
|
|
53
|
+
// pattern: "debugger;",
|
|
54
|
+
// flags: "g",
|
|
55
|
+
// name: "debugger statement",
|
|
56
|
+
// severity: "warning"
|
|
57
|
+
// },
|
|
58
|
+
|
|
59
|
+
// TODOs that should be resolved before commit:
|
|
60
|
+
// {
|
|
61
|
+
// pattern: "TODO_REMOVE|FIXME_REMOVE",
|
|
62
|
+
// flags: "i",
|
|
63
|
+
// name: "Unresolved critical TODOs",
|
|
64
|
+
// severity: "error"
|
|
65
|
+
// },
|
|
66
|
+
|
|
67
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
68
|
+
// ⚠️ IMPORTANT: Uncomment patterns that match rules in your CERBER.md
|
|
69
|
+
// Never invent rules. Only translate what's documented in your contract.
|
|
70
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default BACKEND_SCHEMA;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 💡 Next Steps:
|
|
78
|
+
*
|
|
79
|
+
* 1. Define your project structure in CERBER.md (single source of truth)
|
|
80
|
+
* 2. Add forbiddenPatterns for your tech stack
|
|
81
|
+
* 3. Add requiredImports rules for your architecture layers
|
|
82
|
+
* 4. Test with: npx cerber-guardian
|
|
83
|
+
*
|
|
84
|
+
* Full examples:
|
|
85
|
+
* - node_modules/cerber-core/examples/backend-schema.ts
|
|
86
|
+
* - node_modules/cerber-core/examples/frontend-schema.ts
|
|
87
|
+
*/
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
# Generated by Cerber init
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/
|
|
1
|
+
# Generated by Cerber init
|
|
2
|
+
# Protected Cerber assets - require owner approval for changes
|
|
3
|
+
|
|
4
|
+
# Core contract and schema
|
|
5
|
+
/CERBER.md {{OWNERS}}
|
|
6
|
+
/{{SCHEMA_FILE}} {{OWNERS}}
|
|
7
|
+
/BACKEND_SCHEMA.* {{OWNERS}}
|
|
8
|
+
|
|
9
|
+
# CI/CD enforcement
|
|
10
|
+
/.github/workflows/cerber.yml {{OWNERS}}
|
|
11
|
+
|
|
12
|
+
# Pre-commit guardian
|
|
13
|
+
/scripts/cerber-guardian.mjs {{OWNERS}}
|
|
14
|
+
/.husky/pre-commit {{OWNERS}}
|
|
15
|
+
|
|
16
|
+
# Ownership meta-file (self-protection)
|
|
17
|
+
/.github/CODEOWNERS {{OWNERS}}
|
|
18
|
+
|
|
@@ -1,148 +1,255 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Generated by Cerber init - DO NOT EDIT MANUALLY
|
|
3
|
-
// To regenerate: npx cerber init --force
|
|
4
|
-
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Generated by Cerber init - DO NOT EDIT MANUALLY
|
|
3
|
+
// To regenerate: npx cerber init --force
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
const APPROVALS_TAG = '{{APPROVALS_TAG}}';
|
|
10
|
+
|
|
11
|
+
function readYamlValue(text, key) {
|
|
12
|
+
const re = new RegExp(`^\\s*${key}\\s*:\\s*(.*)\\s*$`, "m");
|
|
13
|
+
const m = text.match(re);
|
|
14
|
+
if (!m) return "";
|
|
15
|
+
let v = (m[1] ?? "").trim();
|
|
16
|
+
|
|
17
|
+
// Remove quotes
|
|
18
|
+
v = v.replace(/^["']/,"").replace(/["']$/,"");
|
|
19
|
+
return v.trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseOverride(cerberContent) {
|
|
23
|
+
const enabledRaw = readYamlValue(cerberContent, "enabled");
|
|
24
|
+
const enabled = enabledRaw.toLowerCase() === "true";
|
|
25
|
+
|
|
26
|
+
const reason = readYamlValue(cerberContent, "reason");
|
|
27
|
+
const expiresRaw = readYamlValue(cerberContent, "expires");
|
|
28
|
+
const approvedBy = readYamlValue(cerberContent, "approvedBy");
|
|
29
|
+
|
|
30
|
+
if (!enabled) return { state: "DISABLED", reason, expires: expiresRaw, approvedBy };
|
|
31
|
+
|
|
32
|
+
if (!expiresRaw) return { state: "ACTIVE", reason, expires: "", approvedBy };
|
|
33
|
+
|
|
34
|
+
const expiresDate = new Date(expiresRaw);
|
|
35
|
+
if (Number.isNaN(expiresDate.getTime())) {
|
|
36
|
+
return { state: "INVALID", reason, expires: expiresRaw, approvedBy };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (expiresDate.getTime() < Date.now()) {
|
|
40
|
+
return { state: "EXPIRED", reason, expires: expiresRaw, approvedBy };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { state: "ACTIVE", reason, expires: expiresRaw, approvedBy };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main() {
|
|
47
|
+
console.log('🛡️ Cerber Guardian: Validating staged files...');
|
|
48
|
+
|
|
49
|
+
// Check for emergency override
|
|
50
|
+
const cerberMdPath = join(process.cwd(), 'CERBER.md');
|
|
51
|
+
if (fs.existsSync(cerberMdPath)) {
|
|
52
|
+
const cerberContent = fs.readFileSync(cerberMdPath, 'utf-8');
|
|
53
|
+
const overrideMatch = cerberContent.match(/CERBER_OVERRIDE:\s*\n\s*enabled:\s*(true|false)/);
|
|
54
|
+
|
|
55
|
+
if (overrideMatch && overrideMatch[1] === 'true') {
|
|
56
|
+
const override = parseOverride(cerberContent);
|
|
57
|
+
const { state, reason, expires, approvedBy } = override;
|
|
58
|
+
|
|
59
|
+
if (state === 'ACTIVE') {
|
|
60
|
+
// Override ACTIVE - allow commit with warning
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
63
|
+
console.log('⚠️ CERBER EMERGENCY OVERRIDE ACTIVE');
|
|
64
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(`Status: ACTIVE`);
|
|
67
|
+
console.log(`Reason: ${reason}`);
|
|
68
|
+
console.log(`Expires: ${expires}`);
|
|
69
|
+
console.log(`Approved By: ${approvedBy}`);
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log('Guardian checks: BYPASSED WITH WARNING');
|
|
72
|
+
console.log('Self-protection: STILL ACTIVE (cerber-integrity runs)');
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log('⚠️ Create follow-up PR to fix properly + disable override');
|
|
75
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
76
|
+
console.log('');
|
|
77
|
+
process.exit(0); // Allow commit
|
|
78
|
+
} else if (state === 'EXPIRED') {
|
|
79
|
+
console.log('⚠️ Override expired - proceeding with normal validation');
|
|
80
|
+
} else {
|
|
81
|
+
console.log('⚠️ Override invalid (missing required fields) - proceeding with normal validation');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Load schema from CERBER.md
|
|
87
|
+
if (!fs.existsSync(cerberMdPath)) {
|
|
88
|
+
console.error('❌ CERBER.md not found');
|
|
89
|
+
console.error('Run: npx cerber init');
|
|
90
|
+
process.exit(5);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const cerberContent = fs.readFileSync(cerberMdPath, 'utf-8');
|
|
94
|
+
|
|
95
|
+
// Parse SCHEMA section
|
|
96
|
+
const schemaMatch = cerberContent.match(/SCHEMA:\s*\n\s*mode:\s*(\w+)/);
|
|
97
|
+
if (!schemaMatch) {
|
|
98
|
+
console.error('❌ SCHEMA section not found in CERBER.md');
|
|
99
|
+
console.error('Add SCHEMA section with mode: required or mode: disabled');
|
|
100
|
+
process.exit(5);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const schemaMode = schemaMatch[1];
|
|
104
|
+
if (schemaMode === 'disabled') {
|
|
105
|
+
console.log('⚠️ Schema validation disabled (SCHEMA.mode: disabled)');
|
|
106
|
+
console.log('✅ Bypassing validation');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Extract forbidden patterns
|
|
111
|
+
const forbiddenPatterns = [];
|
|
112
|
+
const patternsMatch = cerberContent.match(/forbiddenPatterns:\s*\n([\s\S]*?)(?=\n\w|\nSCHEMA:|$)/);
|
|
113
|
+
if (patternsMatch) {
|
|
114
|
+
const patternLines = patternsMatch[1].match(/- "([^"]+)"/g);
|
|
115
|
+
if (patternLines) {
|
|
116
|
+
patternLines.forEach(line => {
|
|
117
|
+
const pattern = line.match(/- "([^"]+)"/)?.[1];
|
|
118
|
+
if (pattern) {
|
|
119
|
+
forbiddenPatterns.push({
|
|
120
|
+
pattern: pattern,
|
|
121
|
+
name: `Forbidden: ${pattern}`,
|
|
122
|
+
severity: 'error'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (schemaMode === 'required' && forbiddenPatterns.length === 0) {
|
|
130
|
+
console.error('❌ SCHEMA.mode: required but no rules defined');
|
|
131
|
+
console.error('Add forbiddenPatterns to SCHEMA section in CERBER.md');
|
|
132
|
+
process.exit(5);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const schema = {
|
|
136
|
+
forbiddenPatterns: forbiddenPatterns,
|
|
137
|
+
rules: []
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Get staged files
|
|
141
|
+
let stagedFiles;
|
|
142
|
+
try {
|
|
143
|
+
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
|
|
144
|
+
.trim()
|
|
145
|
+
.split('\n')
|
|
146
|
+
.filter(Boolean);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error('❌ Failed to get staged files');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (stagedFiles.length === 0) {
|
|
153
|
+
console.log('✅ No files staged for commit');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`Checking ${stagedFiles.length} file(s)...`);
|
|
158
|
+
|
|
159
|
+
const violations = [];
|
|
160
|
+
|
|
161
|
+
// Validate each staged file
|
|
162
|
+
for (const file of stagedFiles) {
|
|
163
|
+
if (!fs.existsSync(file)) continue;
|
|
164
|
+
|
|
165
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
166
|
+
|
|
167
|
+
// Check forbidden patterns
|
|
168
|
+
if (schema.forbiddenPatterns && Array.isArray(schema.forbiddenPatterns)) {
|
|
169
|
+
for (const rule of schema.forbiddenPatterns) {
|
|
170
|
+
if (!rule.pattern) continue;
|
|
171
|
+
|
|
172
|
+
const pattern = typeof rule.pattern === 'string'
|
|
173
|
+
? new RegExp(rule.pattern, rule.flags || 'i')
|
|
174
|
+
: rule.pattern;
|
|
175
|
+
|
|
176
|
+
// Check if file is in exceptions
|
|
177
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (pattern.test(content)) {
|
|
182
|
+
// Check for architect approval
|
|
183
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
184
|
+
|
|
185
|
+
if (!hasApproval) {
|
|
186
|
+
violations.push({
|
|
187
|
+
file,
|
|
188
|
+
rule: rule.name || 'Unnamed rule',
|
|
189
|
+
severity: rule.severity || 'error'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check required imports (rules)
|
|
197
|
+
if (schema.rules && Array.isArray(schema.rules)) {
|
|
198
|
+
for (const rule of schema.rules) {
|
|
199
|
+
if (!rule.pattern || !rule.requiredImports) continue;
|
|
200
|
+
|
|
201
|
+
const filePattern = typeof rule.pattern === 'string' ? new RegExp(rule.pattern) : rule.pattern;
|
|
202
|
+
|
|
203
|
+
if (!filePattern.test(file)) continue;
|
|
204
|
+
|
|
205
|
+
// Check if file is in exceptions
|
|
206
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check each required import
|
|
211
|
+
for (const requiredImport of rule.requiredImports) {
|
|
212
|
+
const importPattern = new RegExp(`import.*${requiredImport}`, 'i');
|
|
213
|
+
|
|
214
|
+
if (!importPattern.test(content)) {
|
|
215
|
+
// Check for architect approval
|
|
216
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
217
|
+
|
|
218
|
+
if (!hasApproval) {
|
|
219
|
+
violations.push({
|
|
220
|
+
file,
|
|
221
|
+
rule: `${rule.name || 'Unnamed rule'}: missing import '${requiredImport}'`,
|
|
222
|
+
severity: rule.severity || 'error'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Report violations
|
|
232
|
+
if (violations.length > 0) {
|
|
233
|
+
console.error('\n❌ Architecture violations detected:\n');
|
|
234
|
+
|
|
235
|
+
for (const v of violations) {
|
|
236
|
+
const icon = v.severity === 'error' ? '🔴' : '⚠️';
|
|
237
|
+
console.error(`${icon} [${v.severity.toUpperCase()}] ${v.file}`);
|
|
238
|
+
console.error(` ${v.rule}`);
|
|
239
|
+
console.error(` Add ${APPROVALS_TAG} comment to override\n`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const errorCount = violations.filter(v => v.severity === 'error').length;
|
|
243
|
+
if (errorCount > 0) {
|
|
244
|
+
console.error(`\n❌ Commit blocked: ${errorCount} error(s) found`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log('✅ All checks passed');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
main().catch(err => {
|
|
253
|
+
console.error('❌ Guardian check failed:', err?.message || err);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
});
|