s9n-devops-agent 2.0.10 → 2.0.11-dev.1
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/bin/cs-devops-agent +28 -0
- package/docs/RELEASE_NOTES.md +7 -0
- package/package.json +8 -3
- package/scripts/contract-automation/README.md +515 -0
- package/scripts/contract-automation/analyze-with-llm.js +497 -0
- package/scripts/contract-automation/check-compliance.js +567 -0
- package/scripts/contract-automation/generate-contracts.js +592 -0
- package/scripts/contract-automation/validate-commit.js +422 -0
- package/src/credentials-manager.js +108 -0
- package/src/session-coordinator.js +7 -3
- package/src/setup-cs-devops-agent.js +219 -3
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* CONTRACT COMPLIANCE CHECKER
|
|
6
|
+
* ============================================================================
|
|
7
|
+
*
|
|
8
|
+
* This script checks if the codebase is in sync with contract files.
|
|
9
|
+
* It detects:
|
|
10
|
+
* - Features in code but not in FEATURES_CONTRACT.md
|
|
11
|
+
* - API endpoints in code but not in API_CONTRACT.md
|
|
12
|
+
* - Database tables in migrations but not in DATABASE_SCHEMA_CONTRACT.md
|
|
13
|
+
* - SQL queries in code but not in SQL_CONTRACT.json
|
|
14
|
+
* - Third-party services in package.json but not in THIRD_PARTY_INTEGRATIONS.md
|
|
15
|
+
* - Environment variables in code but not in INFRA_CONTRACT.md
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* node scripts/contract-automation/check-compliance.js
|
|
19
|
+
* node scripts/contract-automation/check-compliance.js --fix
|
|
20
|
+
* node scripts/contract-automation/check-compliance.js --report=json
|
|
21
|
+
*
|
|
22
|
+
* Options:
|
|
23
|
+
* --fix Generate missing contract entries
|
|
24
|
+
* --report=<format> Output format (text|json|html)
|
|
25
|
+
* --strict Exit with error if any discrepancies found
|
|
26
|
+
*
|
|
27
|
+
* ============================================================================
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import fs from 'fs';
|
|
31
|
+
import path from 'path';
|
|
32
|
+
import { execSync } from 'child_process';
|
|
33
|
+
import { fileURLToPath } from 'url';
|
|
34
|
+
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const __dirname = path.dirname(__filename);
|
|
37
|
+
|
|
38
|
+
// Configuration
|
|
39
|
+
const CONFIG = {
|
|
40
|
+
rootDir: process.cwd(),
|
|
41
|
+
contractsDir: path.join(process.cwd(), 'House_Rules_Contracts'),
|
|
42
|
+
fix: process.argv.includes('--fix'),
|
|
43
|
+
report: getArgValue('--report') || 'text',
|
|
44
|
+
strict: process.argv.includes('--strict')
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Helper: Get command line argument value
|
|
48
|
+
function getArgValue(argName) {
|
|
49
|
+
const arg = process.argv.find(a => a.startsWith(argName + '='));
|
|
50
|
+
return arg ? arg.split('=')[1] : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Helper: Log
|
|
54
|
+
function log(message, level = 'info') {
|
|
55
|
+
const colors = {
|
|
56
|
+
info: '\x1b[36m',
|
|
57
|
+
warn: '\x1b[33m',
|
|
58
|
+
error: '\x1b[31m',
|
|
59
|
+
success: '\x1b[32m',
|
|
60
|
+
reset: '\x1b[0m'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const prefix = {
|
|
64
|
+
info: '[INFO]',
|
|
65
|
+
warn: '[WARN]',
|
|
66
|
+
error: '[ERROR]',
|
|
67
|
+
success: '[SUCCESS]'
|
|
68
|
+
}[level];
|
|
69
|
+
|
|
70
|
+
const color = colors[level] || colors.reset;
|
|
71
|
+
console.log(`${color}${prefix} ${message}${colors.reset}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Helper: Read file safely
|
|
75
|
+
function readFileSafe(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Helper: Find files
|
|
84
|
+
function findFiles(pattern, dir = CONFIG.rootDir) {
|
|
85
|
+
try {
|
|
86
|
+
const result = execSync(`find ${dir} -type f ${pattern}`, { encoding: 'utf8' });
|
|
87
|
+
return result.trim().split('\n').filter(Boolean);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// CONTRACT READERS
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
function readFeaturesContract() {
|
|
98
|
+
const filePath = path.join(CONFIG.contractsDir, 'FEATURES_CONTRACT.md');
|
|
99
|
+
if (!fs.existsSync(filePath)) return [];
|
|
100
|
+
|
|
101
|
+
const content = readFileSafe(filePath);
|
|
102
|
+
const features = [];
|
|
103
|
+
|
|
104
|
+
// Extract feature IDs and names
|
|
105
|
+
const matches = content.matchAll(/Feature ID:\s*\[?(F-\d+)\]?\s*-\s*(.+)/gi);
|
|
106
|
+
for (const match of matches) {
|
|
107
|
+
features.push({
|
|
108
|
+
id: match[1],
|
|
109
|
+
name: match[2].trim()
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return features;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readAPIContract() {
|
|
117
|
+
const filePath = path.join(CONFIG.contractsDir, 'API_CONTRACT.md');
|
|
118
|
+
if (!fs.existsSync(filePath)) return [];
|
|
119
|
+
|
|
120
|
+
const content = readFileSafe(filePath);
|
|
121
|
+
const endpoints = [];
|
|
122
|
+
|
|
123
|
+
// Extract endpoints
|
|
124
|
+
const matches = content.matchAll(/####?\s*`(GET|POST|PUT|DELETE|PATCH)\s+(.+?)`/gi);
|
|
125
|
+
for (const match of matches) {
|
|
126
|
+
endpoints.push({
|
|
127
|
+
method: match[1].toUpperCase(),
|
|
128
|
+
path: match[2].trim()
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return endpoints;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readDatabaseContract() {
|
|
136
|
+
const filePath = path.join(CONFIG.contractsDir, 'DATABASE_SCHEMA_CONTRACT.md');
|
|
137
|
+
if (!fs.existsSync(filePath)) return [];
|
|
138
|
+
|
|
139
|
+
const content = readFileSafe(filePath);
|
|
140
|
+
const tables = [];
|
|
141
|
+
|
|
142
|
+
// Extract table names
|
|
143
|
+
const matches = content.matchAll(/###\s+Table:\s+(\w+)/gi);
|
|
144
|
+
for (const match of matches) {
|
|
145
|
+
tables.push(match[1]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return tables;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function readSQLContract() {
|
|
152
|
+
const filePath = path.join(CONFIG.contractsDir, 'SQL_CONTRACT.json');
|
|
153
|
+
if (!fs.existsSync(filePath)) return [];
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const content = readFileSafe(filePath);
|
|
157
|
+
const data = JSON.parse(content);
|
|
158
|
+
return Object.keys(data.queries || {});
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readIntegrationsContract() {
|
|
165
|
+
const filePath = path.join(CONFIG.contractsDir, 'THIRD_PARTY_INTEGRATIONS.md');
|
|
166
|
+
if (!fs.existsSync(filePath)) return [];
|
|
167
|
+
|
|
168
|
+
const content = readFileSafe(filePath);
|
|
169
|
+
const integrations = [];
|
|
170
|
+
|
|
171
|
+
// Extract service names
|
|
172
|
+
const matches = content.matchAll(/###\s+(.+?)\s+\(/gi);
|
|
173
|
+
for (const match of matches) {
|
|
174
|
+
integrations.push(match[1].trim());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return integrations;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function readInfraContract() {
|
|
181
|
+
const filePath = path.join(CONFIG.contractsDir, 'INFRA_CONTRACT.md');
|
|
182
|
+
if (!fs.existsSync(filePath)) return [];
|
|
183
|
+
|
|
184
|
+
const content = readFileSafe(filePath);
|
|
185
|
+
const envVars = [];
|
|
186
|
+
|
|
187
|
+
// Extract environment variables
|
|
188
|
+
const matches = content.matchAll(/`([A-Z_][A-Z0-9_]*)`/g);
|
|
189
|
+
for (const match of matches) {
|
|
190
|
+
if (!envVars.includes(match[1])) {
|
|
191
|
+
envVars.push(match[1]);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return envVars;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// CODE SCANNERS (reuse from generate-contracts.js logic)
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
function scanCodeForFeatures() {
|
|
203
|
+
const featureDirs = [
|
|
204
|
+
...findFiles('-path "*/src/features/*" -type d'),
|
|
205
|
+
...findFiles('-path "*/src/modules/*" -type d')
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
const features = [];
|
|
209
|
+
for (const dir of featureDirs) {
|
|
210
|
+
const featureName = path.basename(dir);
|
|
211
|
+
if (featureName !== 'features' && featureName !== 'modules') {
|
|
212
|
+
features.push(featureName);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return features;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function scanCodeForEndpoints() {
|
|
220
|
+
const routeFiles = [
|
|
221
|
+
...findFiles('-path "*/routes/*.js"'),
|
|
222
|
+
...findFiles('-path "*/api/*.js"'),
|
|
223
|
+
...findFiles('-path "*/controllers/*.js"')
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const endpoints = [];
|
|
227
|
+
for (const file of routeFiles) {
|
|
228
|
+
const content = readFileSafe(file);
|
|
229
|
+
const matches = content.matchAll(/(app|router)\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi);
|
|
230
|
+
|
|
231
|
+
for (const match of matches) {
|
|
232
|
+
endpoints.push({
|
|
233
|
+
method: match[2].toUpperCase(),
|
|
234
|
+
path: match[3]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return endpoints;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function scanCodeForTables() {
|
|
243
|
+
const migrationFiles = [
|
|
244
|
+
...findFiles('-path "*/migrations/*.sql"'),
|
|
245
|
+
...findFiles('-name "schema.prisma"')
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
const tables = [];
|
|
249
|
+
for (const file of migrationFiles) {
|
|
250
|
+
const content = readFileSafe(file);
|
|
251
|
+
|
|
252
|
+
if (file.endsWith('.sql')) {
|
|
253
|
+
const matches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/gi);
|
|
254
|
+
for (const match of matches) {
|
|
255
|
+
if (!tables.includes(match[1])) {
|
|
256
|
+
tables.push(match[1]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else if (file.endsWith('.prisma')) {
|
|
260
|
+
const matches = content.matchAll(/model\s+(\w+)\s*{/gi);
|
|
261
|
+
for (const match of matches) {
|
|
262
|
+
const tableName = match[1].toLowerCase();
|
|
263
|
+
if (!tables.includes(tableName)) {
|
|
264
|
+
tables.push(tableName);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return tables;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function scanCodeForIntegrations() {
|
|
274
|
+
const packageJsonPath = path.join(CONFIG.rootDir, 'package.json');
|
|
275
|
+
if (!fs.existsSync(packageJsonPath)) return [];
|
|
276
|
+
|
|
277
|
+
const packageJson = JSON.parse(readFileSafe(packageJsonPath));
|
|
278
|
+
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
279
|
+
|
|
280
|
+
const knownServices = {
|
|
281
|
+
'stripe': 'Stripe',
|
|
282
|
+
'@sendgrid/mail': 'SendGrid',
|
|
283
|
+
'aws-sdk': 'AWS SDK',
|
|
284
|
+
'@aws-sdk/client-s3': 'AWS S3',
|
|
285
|
+
'twilio': 'Twilio',
|
|
286
|
+
'mailgun-js': 'Mailgun'
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const integrations = [];
|
|
290
|
+
for (const [pkg, name] of Object.entries(knownServices)) {
|
|
291
|
+
if (dependencies[pkg]) {
|
|
292
|
+
integrations.push(name);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return integrations;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function scanCodeForEnvVars() {
|
|
300
|
+
const codeFiles = findFiles('-path "*/src/*.js" -o -path "*/src/*.ts"');
|
|
301
|
+
const envVars = new Set();
|
|
302
|
+
|
|
303
|
+
for (const file of codeFiles) {
|
|
304
|
+
const content = readFileSafe(file);
|
|
305
|
+
const matches = content.matchAll(/process\.env\.([A-Z_][A-Z0-9_]*)/g);
|
|
306
|
+
for (const match of matches) {
|
|
307
|
+
envVars.add(match[1]);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return Array.from(envVars);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ============================================================================
|
|
315
|
+
// COMPLIANCE CHECKING
|
|
316
|
+
// ============================================================================
|
|
317
|
+
|
|
318
|
+
function checkCompliance() {
|
|
319
|
+
log('Checking contract compliance...');
|
|
320
|
+
|
|
321
|
+
const results = {
|
|
322
|
+
features: { missing: [], extra: [] },
|
|
323
|
+
api: { missing: [], extra: [] },
|
|
324
|
+
database: { missing: [], extra: [] },
|
|
325
|
+
integrations: { missing: [], extra: [] },
|
|
326
|
+
envVars: { missing: [], extra: [] }
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Features
|
|
330
|
+
const contractFeatures = readFeaturesContract().map(f => f.name.toLowerCase());
|
|
331
|
+
const codeFeatures = scanCodeForFeatures().map(f => f.toLowerCase());
|
|
332
|
+
|
|
333
|
+
results.features.missing = codeFeatures.filter(f => !contractFeatures.some(cf => cf.includes(f) || f.includes(cf)));
|
|
334
|
+
results.features.extra = contractFeatures.filter(cf => !codeFeatures.some(f => cf.includes(f) || f.includes(cf)));
|
|
335
|
+
|
|
336
|
+
// API Endpoints
|
|
337
|
+
const contractEndpoints = readAPIContract();
|
|
338
|
+
const codeEndpoints = scanCodeForEndpoints();
|
|
339
|
+
|
|
340
|
+
for (const endpoint of codeEndpoints) {
|
|
341
|
+
const found = contractEndpoints.some(ce =>
|
|
342
|
+
ce.method === endpoint.method && ce.path === endpoint.path
|
|
343
|
+
);
|
|
344
|
+
if (!found) {
|
|
345
|
+
results.api.missing.push(`${endpoint.method} ${endpoint.path}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
for (const endpoint of contractEndpoints) {
|
|
350
|
+
const found = codeEndpoints.some(ce =>
|
|
351
|
+
ce.method === endpoint.method && ce.path === endpoint.path
|
|
352
|
+
);
|
|
353
|
+
if (!found) {
|
|
354
|
+
results.api.extra.push(`${endpoint.method} ${endpoint.path}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Database Tables
|
|
359
|
+
const contractTables = readDatabaseContract().map(t => t.toLowerCase());
|
|
360
|
+
const codeTables = scanCodeForTables().map(t => t.toLowerCase());
|
|
361
|
+
|
|
362
|
+
results.database.missing = codeTables.filter(t => !contractTables.includes(t));
|
|
363
|
+
results.database.extra = contractTables.filter(t => !codeTables.includes(t));
|
|
364
|
+
|
|
365
|
+
// Third-party Integrations
|
|
366
|
+
const contractIntegrations = readIntegrationsContract().map(i => i.toLowerCase());
|
|
367
|
+
const codeIntegrations = scanCodeForIntegrations().map(i => i.toLowerCase());
|
|
368
|
+
|
|
369
|
+
results.integrations.missing = codeIntegrations.filter(i => !contractIntegrations.some(ci => ci.includes(i) || i.includes(ci)));
|
|
370
|
+
results.integrations.extra = contractIntegrations.filter(ci => !codeIntegrations.some(i => ci.includes(i) || i.includes(ci)));
|
|
371
|
+
|
|
372
|
+
// Environment Variables
|
|
373
|
+
const contractEnvVars = readInfraContract();
|
|
374
|
+
const codeEnvVars = scanCodeForEnvVars();
|
|
375
|
+
|
|
376
|
+
results.envVars.missing = codeEnvVars.filter(v => !contractEnvVars.includes(v));
|
|
377
|
+
results.envVars.extra = contractEnvVars.filter(v => !codeEnvVars.includes(v));
|
|
378
|
+
|
|
379
|
+
return results;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ============================================================================
|
|
383
|
+
// REPORTING
|
|
384
|
+
// ============================================================================
|
|
385
|
+
|
|
386
|
+
function generateTextReport(results) {
|
|
387
|
+
log('='.repeat(80));
|
|
388
|
+
log('CONTRACT COMPLIANCE REPORT');
|
|
389
|
+
log('='.repeat(80));
|
|
390
|
+
log('');
|
|
391
|
+
|
|
392
|
+
let totalIssues = 0;
|
|
393
|
+
|
|
394
|
+
// Features
|
|
395
|
+
log('FEATURES:');
|
|
396
|
+
if (results.features.missing.length > 0) {
|
|
397
|
+
log(` Missing in contract (${results.features.missing.length}):`, 'warn');
|
|
398
|
+
results.features.missing.forEach(f => log(` - ${f}`, 'warn'));
|
|
399
|
+
totalIssues += results.features.missing.length;
|
|
400
|
+
}
|
|
401
|
+
if (results.features.extra.length > 0) {
|
|
402
|
+
log(` In contract but not in code (${results.features.extra.length}):`, 'info');
|
|
403
|
+
results.features.extra.forEach(f => log(` - ${f}`, 'info'));
|
|
404
|
+
}
|
|
405
|
+
if (results.features.missing.length === 0 && results.features.extra.length === 0) {
|
|
406
|
+
log(' ✅ All features documented', 'success');
|
|
407
|
+
}
|
|
408
|
+
log('');
|
|
409
|
+
|
|
410
|
+
// API Endpoints
|
|
411
|
+
log('API ENDPOINTS:');
|
|
412
|
+
if (results.api.missing.length > 0) {
|
|
413
|
+
log(` Missing in contract (${results.api.missing.length}):`, 'warn');
|
|
414
|
+
results.api.missing.slice(0, 10).forEach(e => log(` - ${e}`, 'warn'));
|
|
415
|
+
if (results.api.missing.length > 10) {
|
|
416
|
+
log(` ... and ${results.api.missing.length - 10} more`, 'warn');
|
|
417
|
+
}
|
|
418
|
+
totalIssues += results.api.missing.length;
|
|
419
|
+
}
|
|
420
|
+
if (results.api.extra.length > 0) {
|
|
421
|
+
log(` In contract but not in code (${results.api.extra.length}):`, 'info');
|
|
422
|
+
results.api.extra.slice(0, 10).forEach(e => log(` - ${e}`, 'info'));
|
|
423
|
+
if (results.api.extra.length > 10) {
|
|
424
|
+
log(` ... and ${results.api.extra.length - 10} more`, 'info');
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (results.api.missing.length === 0 && results.api.extra.length === 0) {
|
|
428
|
+
log(' ✅ All endpoints documented', 'success');
|
|
429
|
+
}
|
|
430
|
+
log('');
|
|
431
|
+
|
|
432
|
+
// Database Tables
|
|
433
|
+
log('DATABASE TABLES:');
|
|
434
|
+
if (results.database.missing.length > 0) {
|
|
435
|
+
log(` Missing in contract (${results.database.missing.length}):`, 'warn');
|
|
436
|
+
results.database.missing.forEach(t => log(` - ${t}`, 'warn'));
|
|
437
|
+
totalIssues += results.database.missing.length;
|
|
438
|
+
}
|
|
439
|
+
if (results.database.extra.length > 0) {
|
|
440
|
+
log(` In contract but not in migrations (${results.database.extra.length}):`, 'info');
|
|
441
|
+
results.database.extra.forEach(t => log(` - ${t}`, 'info'));
|
|
442
|
+
}
|
|
443
|
+
if (results.database.missing.length === 0 && results.database.extra.length === 0) {
|
|
444
|
+
log(' ✅ All tables documented', 'success');
|
|
445
|
+
}
|
|
446
|
+
log('');
|
|
447
|
+
|
|
448
|
+
// Third-party Integrations
|
|
449
|
+
log('THIRD-PARTY INTEGRATIONS:');
|
|
450
|
+
if (results.integrations.missing.length > 0) {
|
|
451
|
+
log(` Missing in contract (${results.integrations.missing.length}):`, 'warn');
|
|
452
|
+
results.integrations.missing.forEach(i => log(` - ${i}`, 'warn'));
|
|
453
|
+
totalIssues += results.integrations.missing.length;
|
|
454
|
+
}
|
|
455
|
+
if (results.integrations.extra.length > 0) {
|
|
456
|
+
log(` In contract but not in package.json (${results.integrations.extra.length}):`, 'info');
|
|
457
|
+
results.integrations.extra.forEach(i => log(` - ${i}`, 'info'));
|
|
458
|
+
}
|
|
459
|
+
if (results.integrations.missing.length === 0 && results.integrations.extra.length === 0) {
|
|
460
|
+
log(' ✅ All integrations documented', 'success');
|
|
461
|
+
}
|
|
462
|
+
log('');
|
|
463
|
+
|
|
464
|
+
// Environment Variables
|
|
465
|
+
log('ENVIRONMENT VARIABLES:');
|
|
466
|
+
if (results.envVars.missing.length > 0) {
|
|
467
|
+
log(` Missing in contract (${results.envVars.missing.length}):`, 'warn');
|
|
468
|
+
results.envVars.missing.slice(0, 10).forEach(v => log(` - ${v}`, 'warn'));
|
|
469
|
+
if (results.envVars.missing.length > 10) {
|
|
470
|
+
log(` ... and ${results.envVars.missing.length - 10} more`, 'warn');
|
|
471
|
+
}
|
|
472
|
+
totalIssues += results.envVars.missing.length;
|
|
473
|
+
}
|
|
474
|
+
if (results.envVars.extra.length > 0) {
|
|
475
|
+
log(` In contract but not in code (${results.envVars.extra.length}):`, 'info');
|
|
476
|
+
results.envVars.extra.slice(0, 10).forEach(v => log(` - ${v}`, 'info'));
|
|
477
|
+
if (results.envVars.extra.length > 10) {
|
|
478
|
+
log(` ... and ${results.envVars.extra.length - 10} more`, 'info');
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (results.envVars.missing.length === 0 && results.envVars.extra.length === 0) {
|
|
482
|
+
log(' ✅ All environment variables documented', 'success');
|
|
483
|
+
}
|
|
484
|
+
log('');
|
|
485
|
+
|
|
486
|
+
// Summary
|
|
487
|
+
log('='.repeat(80));
|
|
488
|
+
if (totalIssues === 0) {
|
|
489
|
+
log('COMPLIANCE CHECK PASSED ✅', 'success');
|
|
490
|
+
log('All contracts are in sync with the codebase.', 'success');
|
|
491
|
+
} else {
|
|
492
|
+
log(`COMPLIANCE CHECK FAILED ❌`, 'error');
|
|
493
|
+
log(`Found ${totalIssues} items missing from contracts.`, 'error');
|
|
494
|
+
log('Run with --fix to generate missing entries.', 'info');
|
|
495
|
+
}
|
|
496
|
+
log('='.repeat(80));
|
|
497
|
+
|
|
498
|
+
return totalIssues === 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function generateJSONReport(results) {
|
|
502
|
+
const report = {
|
|
503
|
+
timestamp: new Date().toISOString(),
|
|
504
|
+
summary: {
|
|
505
|
+
features: {
|
|
506
|
+
missing: results.features.missing.length,
|
|
507
|
+
extra: results.features.extra.length
|
|
508
|
+
},
|
|
509
|
+
api: {
|
|
510
|
+
missing: results.api.missing.length,
|
|
511
|
+
extra: results.api.extra.length
|
|
512
|
+
},
|
|
513
|
+
database: {
|
|
514
|
+
missing: results.database.missing.length,
|
|
515
|
+
extra: results.database.extra.length
|
|
516
|
+
},
|
|
517
|
+
integrations: {
|
|
518
|
+
missing: results.integrations.missing.length,
|
|
519
|
+
extra: results.integrations.extra.length
|
|
520
|
+
},
|
|
521
|
+
envVars: {
|
|
522
|
+
missing: results.envVars.missing.length,
|
|
523
|
+
extra: results.envVars.extra.length
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
details: results
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
console.log(JSON.stringify(report, null, 2));
|
|
530
|
+
|
|
531
|
+
const totalIssues = Object.values(report.summary).reduce((sum, cat) => sum + cat.missing, 0);
|
|
532
|
+
return totalIssues === 0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ============================================================================
|
|
536
|
+
// MAIN EXECUTION
|
|
537
|
+
// ============================================================================
|
|
538
|
+
|
|
539
|
+
function main() {
|
|
540
|
+
// Check if contracts directory exists
|
|
541
|
+
if (!fs.existsSync(CONFIG.contractsDir)) {
|
|
542
|
+
log(`Contracts directory not found: ${CONFIG.contractsDir}`, 'error');
|
|
543
|
+
log('Run contract generation first or merge the contract system branch.', 'info');
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Run compliance check
|
|
548
|
+
const results = checkCompliance();
|
|
549
|
+
|
|
550
|
+
// Generate report
|
|
551
|
+
let passed;
|
|
552
|
+
if (CONFIG.report === 'json') {
|
|
553
|
+
passed = generateJSONReport(results);
|
|
554
|
+
} else {
|
|
555
|
+
passed = generateTextReport(results);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Exit with appropriate code
|
|
559
|
+
if (CONFIG.strict && !passed) {
|
|
560
|
+
process.exit(1);
|
|
561
|
+
} else {
|
|
562
|
+
process.exit(0);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Run
|
|
567
|
+
main();
|