cerber-core 1.0.4 → 1.1.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 (37) hide show
  1. package/CHANGELOG.md +158 -68
  2. package/README.md +75 -0
  3. package/USAGE_GUIDE.md +254 -0
  4. package/bin/cerber +121 -105
  5. package/dev/templates/cerber-guardian.mjs.tpl +44 -0
  6. package/dev/templates/cerber.yml.tpl +53 -0
  7. package/dev/templates/health-checks.ts.tpl +11 -0
  8. package/dev/templates/health-route.ts.tpl +50 -0
  9. package/dev/templates/pre-commit.tpl +4 -0
  10. package/dist/cli/contract-parser.d.ts +13 -0
  11. package/dist/cli/contract-parser.d.ts.map +1 -0
  12. package/dist/cli/contract-parser.js +241 -0
  13. package/dist/cli/contract-parser.js.map +1 -0
  14. package/dist/cli/init.d.ts +11 -0
  15. package/dist/cli/init.d.ts.map +1 -0
  16. package/dist/cli/init.js +241 -0
  17. package/dist/cli/init.js.map +1 -0
  18. package/dist/cli/template-generator.d.ts +28 -0
  19. package/dist/cli/template-generator.d.ts.map +1 -0
  20. package/dist/cli/template-generator.js +227 -0
  21. package/dist/cli/template-generator.js.map +1 -0
  22. package/dist/cli/types.d.ts +70 -0
  23. package/dist/cli/types.d.ts.map +1 -0
  24. package/dist/cli/types.js +8 -0
  25. package/dist/cli/types.js.map +1 -0
  26. package/package.json +106 -104
  27. package/solo/templates/cerber-guardian.mjs.tpl +44 -0
  28. package/solo/templates/cerber.yml.tpl +53 -0
  29. package/solo/templates/health-checks.ts.tpl +29 -0
  30. package/solo/templates/health-route.ts.tpl +50 -0
  31. package/solo/templates/pre-commit.tpl +4 -0
  32. package/team/templates/CODEOWNERS.tpl +6 -0
  33. package/team/templates/cerber-guardian.mjs.tpl +44 -0
  34. package/team/templates/cerber.yml.tpl +53 -0
  35. package/team/templates/health-checks.ts.tpl +10 -0
  36. package/team/templates/health-route.ts.tpl +50 -0
  37. package/team/templates/pre-commit.tpl +4 -0
package/bin/cerber CHANGED
@@ -1,105 +1,121 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Cerber Core - Unified CLI
5
- *
6
- * Main entry point for all Cerber commands
7
- *
8
- * @author Stefan Pitek
9
- * @license MIT
10
- */
11
-
12
- import chalk from 'chalk';
13
- import { program } from 'commander';
14
-
15
- program
16
- .name('cerber')
17
- .description('Cerber Core - Code quality & health monitoring')
18
- .version('1.0.0');
19
-
20
- program
21
- .command('guardian')
22
- .description('Run Guardian pre-commit validation')
23
- .option('-s, --schema <file>', 'Schema file path')
24
- .option('-v, --verbose', 'Verbose output')
25
- .option('--fail-on-warning', 'Exit with error on warnings')
26
- .action(async (options) => {
27
- const { Guardian } = await import('../dist/guardian/index.js');
28
- const schema = options.schema ? await import(options.schema) : null;
29
-
30
- const guardian = new Guardian(schema?.default || {});
31
- const result = await guardian.validate();
32
-
33
- if (!result.success) {
34
- console.error(chalk.red('❌ Guardian validation failed'));
35
- process.exit(1);
36
- }
37
-
38
- console.log(chalk.green(' Guardian validation passed'));
39
- });
40
-
41
- program
42
- .command('health')
43
- .description('Run Cerber health checks')
44
- .option('-c, --checks <file>', 'Health checks file path')
45
- .option('-u, --url <url>', 'Fetch health from URL')
46
- .option('-p, --parallel', 'Run checks in parallel')
47
- .action(async (options) => {
48
- const { Cerber } = await import('../dist/cerber/index.js');
49
-
50
- if (options.url) {
51
- const response = await fetch(options.url);
52
- const result = await response.json();
53
- console.log(JSON.stringify(result, null, 2));
54
- process.exit(result.status === 'healthy' ? 0 : 1);
55
- }
56
-
57
- const checksModule = options.checks ? await import(options.checks) : null;
58
- const checks = checksModule ? Object.values(checksModule).filter(v => typeof v === 'function') : [];
59
-
60
- const cerber = new Cerber(checks);
61
- const result = await cerber.runChecks({ parallel: options.parallel });
62
-
63
- process.exit(result.status === 'healthy' ? 0 : 1);
64
- });
65
-
66
- program
67
- .command('morning')
68
- .description('Run morning dashboard (SOLO)')
69
- .action(async () => {
70
- const { execSync } = await import('child_process');
71
- execSync('node solo/scripts/cerber-daily-check.js', { stdio: 'inherit' });
72
- });
73
-
74
- program
75
- .command('repair')
76
- .description('Auto-repair common issues (SOLO)')
77
- .option('--dry-run', 'Show what would be fixed without making changes')
78
- .option('--approve', 'Require approval for each fix')
79
- .action(async (options) => {
80
- const { execSync } = await import('child_process');
81
- const args = [
82
- options.dryRun && '--dry-run',
83
- options.approve && '--approve'
84
- ].filter(Boolean).join(' ');
85
-
86
- execSync(`node solo/scripts/cerber-auto-repair.js ${args}`, { stdio: 'inherit' });
87
- });
88
-
89
- program
90
- .command('focus <module>')
91
- .description('Generate focus context for module (TEAM)')
92
- .action(async (module) => {
93
- const { execSync } = await import('child_process');
94
- execSync(`bash team/scripts/cerber-focus.sh ${module}`, { stdio: 'inherit' });
95
- });
96
-
97
- program
98
- .command('dashboard')
99
- .description('Show system dashboard (SOLO)')
100
- .action(async () => {
101
- const { execSync } = await import('child_process');
102
- execSync('node solo/scripts/cerber-dashboard.js', { stdio: 'inherit' });
103
- });
104
-
105
- program.parse();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cerber Core - Unified CLI
5
+ *
6
+ * Main entry point for all Cerber commands
7
+ *
8
+ * @author Stefan Pitek
9
+ * @license MIT
10
+ */
11
+
12
+ import chalk from 'chalk';
13
+ import { program } from 'commander';
14
+
15
+ program
16
+ .name('cerber')
17
+ .description('Cerber Core - Code quality & health monitoring')
18
+ .version('1.1.0');
19
+
20
+ program
21
+ .command('init')
22
+ .description('Initialize Cerber in your project')
23
+ .option('--mode <mode>', 'Override mode: solo | dev | team')
24
+ .option('--force', 'Overwrite existing files')
25
+ .option('--dry-run', 'Show what would be generated without creating files')
26
+ .option('--print-template', 'Print CERBER.md template contract to stdout')
27
+ .option('--no-husky', 'Skip Husky hook generation')
28
+ .option('--no-workflow', 'Skip GitHub Actions workflow generation')
29
+ .option('--no-health', 'Skip health check template generation')
30
+ .option('--write-contract', 'Update CERBER.md contract with CLI options')
31
+ .action(async (options) => {
32
+ const { initCommand } = await import('../dist/cli/init.js');
33
+ await initCommand(options);
34
+ });
35
+
36
+ program
37
+ .command('guardian')
38
+ .description('Run Guardian pre-commit validation')
39
+ .option('-s, --schema <file>', 'Schema file path')
40
+ .option('-v, --verbose', 'Verbose output')
41
+ .option('--fail-on-warning', 'Exit with error on warnings')
42
+ .action(async (options) => {
43
+ const { Guardian } = await import('../dist/guardian/index.js');
44
+ const schema = options.schema ? await import(options.schema) : null;
45
+
46
+ const guardian = new Guardian(schema?.default || {});
47
+ const result = await guardian.validate();
48
+
49
+ if (!result.success) {
50
+ console.error(chalk.red('❌ Guardian validation failed'));
51
+ process.exit(1);
52
+ }
53
+
54
+ console.log(chalk.green(' Guardian validation passed'));
55
+ });
56
+
57
+ program
58
+ .command('health')
59
+ .description('Run Cerber health checks')
60
+ .option('-c, --checks <file>', 'Health checks file path')
61
+ .option('-u, --url <url>', 'Fetch health from URL')
62
+ .option('-p, --parallel', 'Run checks in parallel')
63
+ .action(async (options) => {
64
+ const { Cerber } = await import('../dist/cerber/index.js');
65
+
66
+ if (options.url) {
67
+ const response = await fetch(options.url);
68
+ const result = await response.json();
69
+ console.log(JSON.stringify(result, null, 2));
70
+ process.exit(result.status === 'healthy' ? 0 : 1);
71
+ }
72
+
73
+ const checksModule = options.checks ? await import(options.checks) : null;
74
+ const checks = checksModule ? Object.values(checksModule).filter(v => typeof v === 'function') : [];
75
+
76
+ const cerber = new Cerber(checks);
77
+ const result = await cerber.runChecks({ parallel: options.parallel });
78
+
79
+ process.exit(result.status === 'healthy' ? 0 : 1);
80
+ });
81
+
82
+ program
83
+ .command('morning')
84
+ .description('Run morning dashboard (SOLO)')
85
+ .action(async () => {
86
+ const { execSync } = await import('child_process');
87
+ execSync('node solo/scripts/cerber-daily-check.js', { stdio: 'inherit' });
88
+ });
89
+
90
+ program
91
+ .command('repair')
92
+ .description('Auto-repair common issues (SOLO)')
93
+ .option('--dry-run', 'Show what would be fixed without making changes')
94
+ .option('--approve', 'Require approval for each fix')
95
+ .action(async (options) => {
96
+ const { execSync } = await import('child_process');
97
+ const args = [
98
+ options.dryRun && '--dry-run',
99
+ options.approve && '--approve'
100
+ ].filter(Boolean).join(' ');
101
+
102
+ execSync(`node solo/scripts/cerber-auto-repair.js ${args}`, { stdio: 'inherit' });
103
+ });
104
+
105
+ program
106
+ .command('focus <module>')
107
+ .description('Generate focus context for module (TEAM)')
108
+ .action(async (module) => {
109
+ const { execSync } = await import('child_process');
110
+ execSync(`bash team/scripts/cerber-focus.sh ${module}`, { stdio: 'inherit' });
111
+ });
112
+
113
+ program
114
+ .command('dashboard')
115
+ .description('Show system dashboard (SOLO)')
116
+ .action(async () => {
117
+ const { execSync } = await import('child_process');
118
+ execSync('node solo/scripts/cerber-dashboard.js', { stdio: 'inherit' });
119
+ });
120
+
121
+ program.parse();
@@ -0,0 +1,44 @@
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
+
8
+ const SCHEMA_FILE = '{{SCHEMA_FILE}}';
9
+ const APPROVALS_TAG = '{{APPROVALS_TAG}}';
10
+
11
+ async function main() {
12
+ console.log('🛡️ Cerber Guardian: Validating staged files...');
13
+
14
+ if (!fs.existsSync(SCHEMA_FILE)) {
15
+ console.error(`❌ Schema file not found: ${SCHEMA_FILE}`);
16
+ console.error('Guardian MVP: schema missing. Add your rules to proceed.');
17
+ process.exit(1);
18
+ }
19
+
20
+ let stagedFiles;
21
+ try {
22
+ stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
23
+ .trim()
24
+ .split('\n')
25
+ .filter(Boolean);
26
+ } catch (err) {
27
+ console.error('❌ Failed to get staged files');
28
+ process.exit(1);
29
+ }
30
+
31
+ if (stagedFiles.length === 0) {
32
+ console.log('✅ No files staged for commit');
33
+ return;
34
+ }
35
+
36
+ console.log(`Checking ${stagedFiles.length} file(s)...`);
37
+ console.log('Guardian MVP: schema detected. Add validation rules to enforce imports/forbidden patterns.');
38
+ console.log('✅ All checks passed');
39
+ }
40
+
41
+ main().catch(err => {
42
+ console.error('❌ Guardian check failed:', err?.message || err);
43
+ process.exit(1);
44
+ });
@@ -0,0 +1,53 @@
1
+ # Generated by Cerber init
2
+ name: Cerber CI
3
+
4
+ on:
5
+ push:
6
+ branches: {{CI_BRANCHES}}
7
+ pull_request:
8
+ branches: {{CI_BRANCHES}}
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ cerber-ci:
13
+ name: Cerber CI
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Setup Node.js
21
+ uses: actions/setup-node@v4
22
+ with:
23
+ node-version: '20'
24
+ cache: 'npm'
25
+
26
+ - name: Install dependencies
27
+ run: npm ci
28
+
29
+ - name: Build (if script exists)
30
+ run: |
31
+ if npm run | grep -q "build"; then
32
+ npm run build
33
+ else
34
+ echo "No build script defined; skipping"
35
+ fi
36
+
37
+ - name: Test (if script exists)
38
+ run: |
39
+ if npm run | grep -q "test"; then
40
+ npm test
41
+ else
42
+ echo "No test script defined; skipping"
43
+ fi
44
+
45
+ - name: Run Guardian
46
+ run: |
47
+ if npm run | grep -q "cerber:guardian"; then
48
+ npm run cerber:guardian
49
+ else
50
+ node scripts/cerber-guardian.mjs
51
+ fi
52
+
53
+ {{POST_DEPLOY_BLOCK}}
@@ -0,0 +1,11 @@
1
+ // Generated by Cerber init - CUSTOMIZE THIS FILE
2
+ // To regenerate template: npx cerber init --force
3
+
4
+ import { CerberCheck, makeIssue } from 'cerber-core';
5
+
6
+ export const checks: Record<string, CerberCheck> = {
7
+ database: async () => {
8
+ // TODO: Implement your database health check
9
+ return [];
10
+ },
11
+ };
@@ -0,0 +1,50 @@
1
+ // Generated by Cerber init - CUSTOMIZE THIS FILE
2
+
3
+ import { checks } from './health-checks.js';
4
+
5
+ export async function healthHandler(req: any, res: any) {
6
+ const startTime = Date.now();
7
+
8
+ try {
9
+ const results = await Promise.all(
10
+ Object.entries(checks).map(async ([name, check]) => ({
11
+ name,
12
+ issues: await check()
13
+ }))
14
+ );
15
+
16
+ const allIssues = results.flatMap(r => r.issues);
17
+ const critical = allIssues.filter(i => i.severity === 'critical').length;
18
+ const errors = allIssues.filter(i => i.severity === 'error').length;
19
+ const warnings = allIssues.filter(i => i.severity === 'warning').length;
20
+
21
+ const status = critical > 0 ? 'unhealthy' :
22
+ errors > 0 ? 'degraded' : 'healthy';
23
+
24
+ const statusCode = status === 'healthy' ? 200 : 503;
25
+
26
+ res.status(statusCode).json({
27
+ status,
28
+ timestamp: new Date().toISOString(),
29
+ durationMs: Date.now() - startTime,
30
+ summary: {
31
+ totalChecks: results.length,
32
+ failedChecks: results.filter(r => r.issues.length > 0).length,
33
+ criticalIssues: critical,
34
+ errorIssues: errors,
35
+ warningIssues: warnings
36
+ },
37
+ components: allIssues
38
+ });
39
+ } catch (err: any) {
40
+ res.status(503).json({
41
+ status: 'error',
42
+ message: 'Health check failed',
43
+ error: err.message
44
+ });
45
+ }
46
+ }
47
+
48
+ // Usage in your server:
49
+ // import { healthHandler } from './cerber/health-route.js';
50
+ // app.get('{{HEALTH_ENDPOINT}}', healthHandler);
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ # Generated by Cerber init
3
+
4
+ npm run cerber:guardian
@@ -0,0 +1,13 @@
1
+ /**
2
+ * CERBER_CONTRACT parser
3
+ *
4
+ * Extracts and validates YAML contract from CERBER.md
5
+ *
6
+ * @author Stefan Pitek
7
+ * @license MIT
8
+ */
9
+ import { CerberContract, ContractParseResult } from './types.js';
10
+ export declare function parseCerberContract(projectRoot: string): Promise<ContractParseResult>;
11
+ export declare function extractContract(content: string): ContractParseResult;
12
+ export declare function getDefaultContract(mode?: 'solo' | 'dev' | 'team'): CerberContract;
13
+ //# sourceMappingURL=contract-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-parser.d.ts","sourceRoot":"","sources":["../../src/cli/contract-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAMjE,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAU3F;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CA8EpE;AAsHD,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAM,GAAG,KAAK,GAAG,MAAc,GAAG,cAAc,CA+BxF"}
@@ -0,0 +1,241 @@
1
+ /**
2
+ * CERBER_CONTRACT parser
3
+ *
4
+ * Extracts and validates YAML contract from CERBER.md
5
+ *
6
+ * @author Stefan Pitek
7
+ * @license MIT
8
+ */
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+ const YAML_START_MARKER = '## CERBER_CONTRACT';
12
+ const YAML_CODE_BLOCK_START = '```yaml';
13
+ const YAML_CODE_BLOCK_END = '```';
14
+ export async function parseCerberContract(projectRoot) {
15
+ const cerberPath = path.join(projectRoot, 'CERBER.md');
16
+ try {
17
+ const content = await fs.readFile(cerberPath, 'utf-8');
18
+ return extractContract(content);
19
+ }
20
+ catch (err) {
21
+ // CERBER.md doesn't exist
22
+ return { success: false, error: { message: 'CERBER.md not found' } };
23
+ }
24
+ }
25
+ export function extractContract(content) {
26
+ const lines = content.split('\n');
27
+ // Find CERBER_CONTRACT section
28
+ const contractStartIndex = lines.findIndex(line => line.trim() === YAML_START_MARKER);
29
+ if (contractStartIndex === -1) {
30
+ return {
31
+ success: false,
32
+ error: {
33
+ message: `Missing "${YAML_START_MARKER}" section header`,
34
+ context: 'Expected format:\n\n## CERBER_CONTRACT\n\`\`\`yaml\n...\n\`\`\`'
35
+ }
36
+ };
37
+ }
38
+ // Find yaml code block
39
+ let yamlStartIndex = -1;
40
+ let yamlEndIndex = -1;
41
+ for (let i = contractStartIndex; i < lines.length; i++) {
42
+ if (lines[i].trim().startsWith(YAML_CODE_BLOCK_START)) {
43
+ yamlStartIndex = i + 1;
44
+ }
45
+ else if (yamlStartIndex !== -1 && lines[i].trim() === YAML_CODE_BLOCK_END) {
46
+ yamlEndIndex = i;
47
+ break;
48
+ }
49
+ }
50
+ if (yamlStartIndex === -1) {
51
+ return {
52
+ success: false,
53
+ error: {
54
+ message: 'Missing YAML code block after CERBER_CONTRACT header',
55
+ line: contractStartIndex + 1,
56
+ context: `Expected \`\`\`yaml after line ${contractStartIndex + 1}`
57
+ }
58
+ };
59
+ }
60
+ if (yamlEndIndex === -1) {
61
+ return {
62
+ success: false,
63
+ error: {
64
+ message: 'Unclosed YAML code block',
65
+ line: yamlStartIndex,
66
+ context: 'Missing closing \`\`\` for YAML block'
67
+ }
68
+ };
69
+ }
70
+ const yamlContent = lines.slice(yamlStartIndex, yamlEndIndex).join('\n');
71
+ // Simple YAML parser (for our specific structure)
72
+ try {
73
+ const contract = parseSimpleYaml(yamlContent);
74
+ // Validate required fields
75
+ const validation = validateContract(contract);
76
+ if (!validation.valid) {
77
+ return {
78
+ success: false,
79
+ error: {
80
+ message: 'Invalid contract structure',
81
+ context: validation.errors.join('\n')
82
+ }
83
+ };
84
+ }
85
+ return { success: true, contract };
86
+ }
87
+ catch (err) {
88
+ return {
89
+ success: false,
90
+ error: {
91
+ message: 'Failed to parse YAML contract',
92
+ context: err.message || 'Invalid YAML structure'
93
+ }
94
+ };
95
+ }
96
+ }
97
+ function parseSimpleYaml(yamlContent) {
98
+ const lines = yamlContent.split('\n').filter(line => line.trim() && !line.trim().startsWith('#'));
99
+ const contract = {
100
+ version: 1,
101
+ mode: 'dev',
102
+ guardian: {},
103
+ health: {},
104
+ ci: {},
105
+ team: {}
106
+ };
107
+ let currentSection = null;
108
+ let currentSubsection = null;
109
+ for (const line of lines) {
110
+ const trimmed = line.trim();
111
+ const indent = line.length - line.trimStart().length;
112
+ if (indent === 0 && trimmed.endsWith(':')) {
113
+ // Top-level key
114
+ const key = trimmed.slice(0, -1);
115
+ currentSection = key;
116
+ currentSubsection = null;
117
+ }
118
+ else if (indent === 2 && trimmed.endsWith(':')) {
119
+ // Second-level key
120
+ const key = trimmed.slice(0, -1);
121
+ currentSubsection = key;
122
+ if (currentSection && !contract[currentSection][key]) {
123
+ contract[currentSection][key] = {};
124
+ }
125
+ }
126
+ else if (trimmed.includes(':')) {
127
+ // Key-value pair
128
+ const [key, ...valueParts] = trimmed.split(':');
129
+ let value = valueParts.join(':').trim();
130
+ // Strip inline comments (# after value)
131
+ const commentIndex = value.indexOf('#');
132
+ if (commentIndex !== -1) {
133
+ value = value.substring(0, commentIndex).trim();
134
+ }
135
+ // Parse value type
136
+ if (value === 'true')
137
+ value = true;
138
+ else if (value === 'false')
139
+ value = false;
140
+ else if (!isNaN(Number(value)))
141
+ value = Number(value);
142
+ else if (value.startsWith('[') && value.endsWith(']')) {
143
+ value = value.slice(1, -1).split(',').map((v) => {
144
+ const trimmed = v.trim();
145
+ // Strip quotes from array values
146
+ if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
147
+ (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
148
+ return trimmed.slice(1, -1);
149
+ }
150
+ return trimmed;
151
+ });
152
+ }
153
+ if (currentSubsection && currentSection) {
154
+ contract[currentSection][currentSubsection][key.trim()] = value;
155
+ }
156
+ else if (currentSection) {
157
+ contract[currentSection][key.trim()] = value;
158
+ }
159
+ else {
160
+ contract[key.trim()] = value;
161
+ }
162
+ }
163
+ }
164
+ return contract;
165
+ }
166
+ function validateContract(contract) {
167
+ const errors = [];
168
+ // Check guardian section
169
+ if (!contract.guardian) {
170
+ errors.push('Missing "guardian" section');
171
+ }
172
+ else {
173
+ if (typeof contract.guardian.enabled !== 'boolean') {
174
+ errors.push('guardian.enabled must be true or false');
175
+ }
176
+ if (contract.guardian.enabled && !contract.guardian.schemaFile) {
177
+ errors.push('guardian.schemaFile is required when guardian is enabled');
178
+ }
179
+ }
180
+ // Check health section
181
+ if (!contract.health) {
182
+ errors.push('Missing "health" section');
183
+ }
184
+ else {
185
+ if (typeof contract.health.enabled !== 'boolean') {
186
+ errors.push('health.enabled must be true or false');
187
+ }
188
+ if (contract.health.enabled && !contract.health.endpoint) {
189
+ errors.push('health.endpoint is required when health is enabled');
190
+ }
191
+ }
192
+ // Check ci section
193
+ if (!contract.ci) {
194
+ errors.push('Missing "ci" section');
195
+ }
196
+ else {
197
+ if (!contract.ci.provider) {
198
+ errors.push('ci.provider is required (e.g., "github")');
199
+ }
200
+ if (!contract.ci.postDeploy) {
201
+ contract.ci.postDeploy = { enabled: false };
202
+ }
203
+ }
204
+ return {
205
+ valid: errors.length === 0,
206
+ errors
207
+ };
208
+ }
209
+ export function getDefaultContract(mode = 'dev') {
210
+ return {
211
+ version: 1,
212
+ mode,
213
+ guardian: {
214
+ enabled: true,
215
+ schemaFile: 'BACKEND_SCHEMA.ts',
216
+ hook: 'husky',
217
+ approvalsTag: 'ARCHITECT_APPROVED'
218
+ },
219
+ health: {
220
+ enabled: mode !== 'solo',
221
+ endpoint: '/api/health',
222
+ failOn: {
223
+ critical: true,
224
+ error: true,
225
+ warning: false
226
+ }
227
+ },
228
+ ci: {
229
+ provider: 'github',
230
+ branches: ['main'],
231
+ requiredOnPR: true,
232
+ postDeploy: {
233
+ enabled: mode === 'team',
234
+ waitSeconds: 90,
235
+ healthUrlVar: 'CERBER_HEALTH_URL',
236
+ authHeaderSecret: 'CERBER_HEALTH_AUTH_HEADER'
237
+ }
238
+ }
239
+ };
240
+ }
241
+ //# sourceMappingURL=contract-parser.js.map