@paths.design/caws-cli 4.0.0 → 5.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/dist/commands/archive.js +353 -0
- package/dist/commands/iterate.js +12 -13
- package/dist/commands/mode.js +259 -0
- package/dist/commands/plan.js +448 -0
- package/dist/commands/quality-gates.js +490 -0
- package/dist/commands/specs.js +735 -0
- package/dist/commands/status.js +552 -22
- package/dist/commands/tutorial.js +481 -0
- package/dist/commands/validate.js +137 -54
- package/dist/commands/waivers.js +101 -26
- package/dist/config/modes.js +321 -0
- package/dist/constants/spec-types.js +42 -0
- package/dist/index.js +225 -10
- package/dist/scaffold/git-hooks.js +32 -44
- package/dist/scaffold/index.js +19 -0
- package/dist/utils/quality-gates-errors.js +520 -0
- package/dist/utils/quality-gates.js +361 -0
- package/dist/utils/spec-resolver.js +602 -0
- package/dist/waivers-manager.js +49 -4
- package/package.json +6 -5
- package/templates/.cursor/hooks/caws-scope-guard.sh +64 -8
- package/templates/.cursor/hooks/validate-spec.sh +22 -12
- package/templates/.cursor/rules/{01-claims-verification.mdc → 00-claims-verification.mdc} +1 -1
- package/templates/.cursor/rules/01-working-style.mdc +50 -0
- package/templates/.cursor/rules/{02-testing-standards.mdc → 02-quality-gates.mdc} +84 -29
- package/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
- package/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
- package/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
- package/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
- package/templates/.cursor/rules/07-process-ops.mdc +20 -0
- package/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
- package/templates/.cursor/rules/09-docstrings.mdc +89 -0
- package/templates/.cursor/rules/10-authorship-and-attribution.mdc +15 -0
- package/templates/.cursor/rules/11-documentation-quality-standards.mdc +390 -0
- package/templates/.cursor/rules/12-scope-management-waivers.mdc +385 -0
- package/templates/.cursor/rules/13-implementation-completeness.mdc +516 -0
- package/templates/.cursor/rules/14-language-agnostic-standards.mdc +588 -0
- package/templates/.cursor/rules/15-sophisticated-todo-detection.mdc +425 -0
- package/templates/.cursor/rules/README.md +93 -7
- package/templates/scripts/quality-gates/check-god-objects.js +146 -0
- package/templates/scripts/quality-gates/run-quality-gates.js +50 -0
- package/templates/scripts/v3/analysis/todo_analyzer.py +1950 -0
- package/dist/budget-derivation.d.ts +0 -74
- package/dist/budget-derivation.d.ts.map +0 -1
- package/dist/cicd-optimizer.d.ts +0 -142
- package/dist/cicd-optimizer.d.ts.map +0 -1
- package/dist/commands/burnup.d.ts +0 -6
- package/dist/commands/burnup.d.ts.map +0 -1
- package/dist/commands/diagnose.d.ts +0 -52
- package/dist/commands/diagnose.d.ts.map +0 -1
- package/dist/commands/evaluate.d.ts +0 -8
- package/dist/commands/evaluate.d.ts.map +0 -1
- package/dist/commands/init.d.ts +0 -5
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/iterate.d.ts +0 -8
- package/dist/commands/iterate.d.ts.map +0 -1
- package/dist/commands/provenance.d.ts +0 -32
- package/dist/commands/provenance.d.ts.map +0 -1
- package/dist/commands/quality-monitor.d.ts +0 -17
- package/dist/commands/quality-monitor.d.ts.map +0 -1
- package/dist/commands/status.d.ts +0 -43
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/templates.d.ts +0 -74
- package/dist/commands/templates.d.ts.map +0 -1
- package/dist/commands/tool.d.ts +0 -13
- package/dist/commands/tool.d.ts.map +0 -1
- package/dist/commands/troubleshoot.d.ts +0 -8
- package/dist/commands/troubleshoot.d.ts.map +0 -1
- package/dist/commands/validate.d.ts +0 -8
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/waivers.d.ts +0 -8
- package/dist/commands/waivers.d.ts.map +0 -1
- package/dist/commands/workflow.d.ts +0 -85
- package/dist/commands/workflow.d.ts.map +0 -1
- package/dist/config/index.d.ts +0 -29
- package/dist/config/index.d.ts.map +0 -1
- package/dist/error-handler.d.ts +0 -164
- package/dist/error-handler.d.ts.map +0 -1
- package/dist/generators/jest-config.d.ts +0 -32
- package/dist/generators/jest-config.d.ts.map +0 -1
- package/dist/generators/working-spec.d.ts +0 -13
- package/dist/generators/working-spec.d.ts.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/minimal-cli.d.ts +0 -3
- package/dist/minimal-cli.d.ts.map +0 -1
- package/dist/policy/PolicyManager.d.ts +0 -104
- package/dist/policy/PolicyManager.d.ts.map +0 -1
- package/dist/scaffold/cursor-hooks.d.ts +0 -7
- package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
- package/dist/scaffold/git-hooks.d.ts +0 -20
- package/dist/scaffold/git-hooks.d.ts.map +0 -1
- package/dist/scaffold/index.d.ts +0 -20
- package/dist/scaffold/index.d.ts.map +0 -1
- package/dist/spec/SpecFileManager.d.ts +0 -146
- package/dist/spec/SpecFileManager.d.ts.map +0 -1
- package/dist/test-analysis.d.ts +0 -182
- package/dist/test-analysis.d.ts.map +0 -1
- package/dist/tool-interface.d.ts +0 -236
- package/dist/tool-interface.d.ts.map +0 -1
- package/dist/tool-loader.d.ts +0 -77
- package/dist/tool-loader.d.ts.map +0 -1
- package/dist/tool-validator.d.ts +0 -72
- package/dist/tool-validator.d.ts.map +0 -1
- package/dist/utils/detection.d.ts +0 -7
- package/dist/utils/detection.d.ts.map +0 -1
- package/dist/utils/finalization.d.ts +0 -17
- package/dist/utils/finalization.d.ts.map +0 -1
- package/dist/utils/project-analysis.d.ts +0 -14
- package/dist/utils/project-analysis.d.ts.map +0 -1
- package/dist/utils/typescript-detector.d.ts +0 -63
- package/dist/utils/typescript-detector.d.ts.map +0 -1
- package/dist/validation/spec-validation.d.ts +0 -43
- package/dist/validation/spec-validation.d.ts.map +0 -1
- package/dist/waivers-manager.d.ts +0 -167
- package/dist/waivers-manager.d.ts.map +0 -1
- package/templates/.cursor/rules/03-infrastructure-standards.mdc +0 -251
- package/templates/.cursor/rules/04-documentation-integrity.mdc +0 -291
- package/templates/.cursor/rules/05-production-readiness-checklist.mdc +0 -214
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Validate Command Handler
|
|
3
|
-
* Handles validation commands for CAWS CLI
|
|
3
|
+
* Handles validation commands for CAWS CLI with multi-spec support
|
|
4
4
|
* @author @darianrosebrook
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const fs = require('fs-extra');
|
|
8
7
|
const path = require('path');
|
|
9
|
-
const yaml = require('js-yaml');
|
|
10
8
|
const chalk = require('chalk');
|
|
11
9
|
|
|
12
10
|
// Import validation functionality
|
|
@@ -15,47 +13,40 @@ const {
|
|
|
15
13
|
getComplianceGrade,
|
|
16
14
|
} = require('../validation/spec-validation');
|
|
17
15
|
|
|
16
|
+
// Import spec resolution system
|
|
17
|
+
const { resolveSpec, suggestMigration } = require('../utils/spec-resolver');
|
|
18
|
+
|
|
18
19
|
/**
|
|
19
20
|
* Validate command handler
|
|
20
|
-
* Enhanced with JSON output format
|
|
21
|
-
* @param {string} specFile - Path to spec file
|
|
21
|
+
* Enhanced with multi-spec support and JSON output format
|
|
22
|
+
* @param {string} specFile - Path to spec file (optional, uses spec resolution)
|
|
22
23
|
* @param {Object} options - Command options
|
|
24
|
+
* @param {string} [options.specId] - Feature-specific spec ID
|
|
25
|
+
* @param {boolean} [options.interactive] - Use interactive spec selection
|
|
26
|
+
* @param {boolean} [options.format] - Output format (json)
|
|
23
27
|
*/
|
|
24
|
-
async function validateCommand(specFile, options) {
|
|
28
|
+
async function validateCommand(specFile, options = {}) {
|
|
25
29
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
passed: false,
|
|
34
|
-
verdict: 'fail',
|
|
35
|
-
errors: [
|
|
36
|
-
{
|
|
37
|
-
field: 'spec_file',
|
|
38
|
-
message: `Spec file not found: ${specPath}`,
|
|
39
|
-
suggestion: 'Run "caws init" first to create a working spec',
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
null,
|
|
44
|
-
2
|
|
45
|
-
)
|
|
46
|
-
);
|
|
47
|
-
} else {
|
|
48
|
-
console.error(chalk.red(`❌ Spec file not found: ${specPath}`));
|
|
49
|
-
console.error(chalk.blue('💡 Run "caws init" first to create a working spec'));
|
|
50
|
-
}
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
30
|
+
// Resolve spec using priority system
|
|
31
|
+
const resolved = await resolveSpec({
|
|
32
|
+
specId: options.specId,
|
|
33
|
+
specFile,
|
|
34
|
+
warnLegacy: options.format !== 'json',
|
|
35
|
+
interactive: options.interactive || false,
|
|
36
|
+
});
|
|
53
37
|
|
|
54
|
-
const
|
|
55
|
-
|
|
38
|
+
const { path: specPath, type: specType, spec } = resolved;
|
|
39
|
+
|
|
40
|
+
// Suggest migration if using legacy spec
|
|
41
|
+
if (specType === 'legacy' && options.format !== 'json') {
|
|
42
|
+
await suggestMigration();
|
|
43
|
+
}
|
|
56
44
|
|
|
57
45
|
if (options.format !== 'json') {
|
|
58
|
-
console.log(
|
|
46
|
+
console.log(
|
|
47
|
+
chalk.cyan(`🔍 Validating ${specType === 'feature' ? 'feature' : 'working'} spec...`)
|
|
48
|
+
);
|
|
49
|
+
console.log(chalk.gray(` Spec: ${path.relative(process.cwd(), specPath)}`));
|
|
59
50
|
}
|
|
60
51
|
|
|
61
52
|
const result = validateWorkingSpecWithSuggestions(spec, {
|
|
@@ -64,16 +55,87 @@ async function validateCommand(specFile, options) {
|
|
|
64
55
|
suggestions: !options.quiet,
|
|
65
56
|
checkBudget: true,
|
|
66
57
|
projectRoot: path.dirname(specPath),
|
|
58
|
+
specType,
|
|
67
59
|
});
|
|
68
60
|
|
|
61
|
+
// Enhanced validation for multi-spec scenarios
|
|
62
|
+
const enhancedValidation = { ...result };
|
|
63
|
+
|
|
64
|
+
if (specType === 'feature') {
|
|
65
|
+
// Check for potential issues in feature specs
|
|
66
|
+
const featureIssues = [];
|
|
67
|
+
|
|
68
|
+
// Check scope conflicts (if multiple specs exist)
|
|
69
|
+
const { checkMultiSpecStatus } = require('../utils/spec-resolver');
|
|
70
|
+
const multiSpecStatus = await checkMultiSpecStatus();
|
|
71
|
+
|
|
72
|
+
if (multiSpecStatus.specCount > 1) {
|
|
73
|
+
const { checkScopeConflicts } = require('../utils/spec-resolver');
|
|
74
|
+
const conflicts = await checkScopeConflicts(
|
|
75
|
+
Object.keys(multiSpecStatus.registry?.specs || {})
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (conflicts.length > 0) {
|
|
79
|
+
const myConflicts = conflicts.filter((c) => c.spec1 === spec.id || c.spec2 === spec.id);
|
|
80
|
+
|
|
81
|
+
if (myConflicts.length > 0) {
|
|
82
|
+
featureIssues.push({
|
|
83
|
+
type: 'warning',
|
|
84
|
+
message: `Scope conflicts detected with other specs`,
|
|
85
|
+
details: myConflicts.map((c) => {
|
|
86
|
+
const otherSpec = c.spec1 === spec.id ? c.spec2 : c.spec1;
|
|
87
|
+
return `Conflict with ${otherSpec}: ${c.conflicts.join(', ')}`;
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check for missing contracts in feature specs
|
|
95
|
+
if (spec.contracts && spec.contracts.length === 0 && spec.mode === 'feature') {
|
|
96
|
+
featureIssues.push({
|
|
97
|
+
type: 'info',
|
|
98
|
+
message: 'Consider adding API contracts for better integration',
|
|
99
|
+
suggestion: 'Add contracts section to define API boundaries',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check for overly broad scopes
|
|
104
|
+
if (spec.scope && spec.scope.in) {
|
|
105
|
+
const broadPatterns = spec.scope.in.filter(
|
|
106
|
+
(pattern) => pattern === 'src/' || pattern === 'tests/' || pattern.includes('*')
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (broadPatterns.length > 0) {
|
|
110
|
+
featureIssues.push({
|
|
111
|
+
type: 'warning',
|
|
112
|
+
message: 'Broad scope patterns detected',
|
|
113
|
+
details: `Patterns like ${broadPatterns.join(', ')} may conflict with other features`,
|
|
114
|
+
suggestion: 'Use more specific scope.in paths',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add feature-specific issues to validation result
|
|
120
|
+
if (featureIssues.length > 0) {
|
|
121
|
+
enhancedValidation.issues = (enhancedValidation.issues || []).concat(featureIssues);
|
|
122
|
+
enhancedValidation.featureValidation = {
|
|
123
|
+
passed: featureIssues.filter((i) => i.type === 'error').length === 0,
|
|
124
|
+
issues: featureIssues,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const finalResult = enhancedValidation;
|
|
130
|
+
|
|
69
131
|
// Format output based on requested format
|
|
70
132
|
if (options.format === 'json') {
|
|
71
133
|
// Structured JSON output matching CAWSValidationResult
|
|
72
134
|
const jsonResult = {
|
|
73
|
-
passed:
|
|
135
|
+
passed: finalResult.valid,
|
|
74
136
|
cawsVersion: '3.4.0',
|
|
75
137
|
timestamp: new Date().toISOString(),
|
|
76
|
-
verdict:
|
|
138
|
+
verdict: finalResult.valid ? 'pass' : 'fail',
|
|
77
139
|
spec: {
|
|
78
140
|
id: spec.id,
|
|
79
141
|
title: spec.title,
|
|
@@ -81,21 +143,29 @@ async function validateCommand(specFile, options) {
|
|
|
81
143
|
mode: spec.mode,
|
|
82
144
|
},
|
|
83
145
|
validation: {
|
|
84
|
-
errors:
|
|
85
|
-
warnings:
|
|
86
|
-
fixes:
|
|
146
|
+
errors: finalResult.errors || [],
|
|
147
|
+
warnings: finalResult.warnings || [],
|
|
148
|
+
fixes: finalResult.fixes || [],
|
|
87
149
|
},
|
|
88
|
-
budgetCompliance:
|
|
150
|
+
budgetCompliance: finalResult.budget_check || null,
|
|
151
|
+
specType,
|
|
152
|
+
specPath: path.relative(process.cwd(), specPath),
|
|
153
|
+
featureValidation: finalResult.featureValidation,
|
|
89
154
|
};
|
|
90
155
|
|
|
91
156
|
console.log(JSON.stringify(jsonResult, null, 2));
|
|
92
157
|
|
|
93
|
-
if (!
|
|
94
|
-
process.exit
|
|
158
|
+
if (!finalResult.valid) {
|
|
159
|
+
// Don't call process.exit in test environment
|
|
160
|
+
if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
|
|
161
|
+
process.exit(1);
|
|
162
|
+
} else {
|
|
163
|
+
throw new Error('Validation failed');
|
|
164
|
+
}
|
|
95
165
|
}
|
|
96
166
|
} else {
|
|
97
167
|
// Human-readable text output
|
|
98
|
-
if (
|
|
168
|
+
if (finalResult.valid) {
|
|
99
169
|
console.log(chalk.green('✅ Working spec validation passed'));
|
|
100
170
|
if (!options.quiet) {
|
|
101
171
|
console.log(chalk.gray(` Risk tier: ${spec.risk_tier}`));
|
|
@@ -103,10 +173,15 @@ async function validateCommand(specFile, options) {
|
|
|
103
173
|
if (spec.title) {
|
|
104
174
|
console.log(chalk.gray(` Title: ${spec.title}`));
|
|
105
175
|
}
|
|
106
|
-
if (
|
|
107
|
-
const grade = getComplianceGrade(
|
|
108
|
-
const scorePercent = (
|
|
109
|
-
const scoreColor =
|
|
176
|
+
if (finalResult.complianceScore !== undefined) {
|
|
177
|
+
const grade = getComplianceGrade(finalResult.complianceScore);
|
|
178
|
+
const scorePercent = (finalResult.complianceScore * 100).toFixed(0);
|
|
179
|
+
const scoreColor =
|
|
180
|
+
finalResult.complianceScore >= 0.9
|
|
181
|
+
? 'green'
|
|
182
|
+
: finalResult.complianceScore >= 0.7
|
|
183
|
+
? 'yellow'
|
|
184
|
+
: 'red';
|
|
110
185
|
console.log(chalk[scoreColor](` Compliance: ${scorePercent}% (Grade ${grade})`));
|
|
111
186
|
}
|
|
112
187
|
}
|
|
@@ -114,7 +189,7 @@ async function validateCommand(specFile, options) {
|
|
|
114
189
|
console.log(chalk.red('❌ Working spec validation failed'));
|
|
115
190
|
|
|
116
191
|
// Show errors
|
|
117
|
-
|
|
192
|
+
finalResult.errors.forEach((error, index) => {
|
|
118
193
|
console.log(` ${index + 1}. ${chalk.red(error.message)}`);
|
|
119
194
|
if (error.suggestion) {
|
|
120
195
|
console.log(` ${chalk.blue('💡 ' + error.suggestion)}`);
|
|
@@ -122,14 +197,19 @@ async function validateCommand(specFile, options) {
|
|
|
122
197
|
});
|
|
123
198
|
|
|
124
199
|
// Show warnings
|
|
125
|
-
if (
|
|
200
|
+
if (finalResult.warnings && finalResult.warnings.length > 0) {
|
|
126
201
|
console.log(chalk.yellow('\n⚠️ Warnings:'));
|
|
127
|
-
|
|
202
|
+
finalResult.warnings.forEach((warning, index) => {
|
|
128
203
|
console.log(` ${index + 1}. ${chalk.yellow(warning.message)}`);
|
|
129
204
|
});
|
|
130
205
|
}
|
|
131
206
|
|
|
132
|
-
process.exit
|
|
207
|
+
// Don't call process.exit in test environment
|
|
208
|
+
if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
|
|
209
|
+
process.exit(1);
|
|
210
|
+
} else {
|
|
211
|
+
throw new Error('Validation failed');
|
|
212
|
+
}
|
|
133
213
|
}
|
|
134
214
|
}
|
|
135
215
|
} catch (error) {
|
|
@@ -148,7 +228,10 @@ async function validateCommand(specFile, options) {
|
|
|
148
228
|
} else {
|
|
149
229
|
console.error(chalk.red('❌ Error during validation:'), error.message);
|
|
150
230
|
}
|
|
151
|
-
process.exit
|
|
231
|
+
// Don't call process.exit in test environment
|
|
232
|
+
if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
152
235
|
}
|
|
153
236
|
}
|
|
154
237
|
|
package/dist/commands/waivers.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CAWS Waivers Command
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Manage quality gate waivers for exceptional circumstances.
|
|
5
5
|
* Waivers allow temporary exceptions to quality requirements
|
|
6
6
|
* with proper documentation and approval.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @author @darianrosebrook
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -13,12 +13,13 @@ const path = require('path');
|
|
|
13
13
|
const yaml = require('js-yaml');
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const { initializeGlobalSetup } = require('../config');
|
|
16
|
+
const WaiversManager = require('../waivers-manager');
|
|
16
17
|
|
|
17
18
|
const WAIVER_DIR = '.caws/waivers';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Waivers command handler
|
|
21
|
-
*
|
|
22
|
+
*
|
|
22
23
|
* @param {string} subcommand - create, list, show, revoke
|
|
23
24
|
* @param {object} options - Command options
|
|
24
25
|
*/
|
|
@@ -26,7 +27,7 @@ async function waiversCommand(subcommand = 'list', options = {}) {
|
|
|
26
27
|
try {
|
|
27
28
|
console.log('🔍 Detecting CAWS setup...');
|
|
28
29
|
const setup = initializeGlobalSetup();
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
if (setup.hasWorkingSpec) {
|
|
31
32
|
console.log(`✅ Detected ${setup.setupType} CAWS setup`);
|
|
32
33
|
console.log(` Capabilities: ${setup.capabilities.join(', ')}`);
|
|
@@ -70,9 +71,18 @@ async function waiversCommand(subcommand = 'list', options = {}) {
|
|
|
70
71
|
*/
|
|
71
72
|
async function createWaiver(options) {
|
|
72
73
|
// Validate required fields
|
|
73
|
-
const required = [
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const required = [
|
|
75
|
+
'title',
|
|
76
|
+
'reason',
|
|
77
|
+
'description',
|
|
78
|
+
'gates',
|
|
79
|
+
'expiresAt',
|
|
80
|
+
'approvedBy',
|
|
81
|
+
'impactLevel',
|
|
82
|
+
'mitigationPlan',
|
|
83
|
+
];
|
|
84
|
+
const missing = required.filter((field) => !options[field]);
|
|
85
|
+
|
|
76
86
|
if (missing.length > 0) {
|
|
77
87
|
console.error(chalk.red(`\n❌ Missing required fields: ${missing.join(', ')}`));
|
|
78
88
|
console.log(chalk.yellow('\n💡 Example:'));
|
|
@@ -93,9 +103,10 @@ async function createWaiver(options) {
|
|
|
93
103
|
const timestamp = new Date().toISOString();
|
|
94
104
|
|
|
95
105
|
// Parse gates
|
|
96
|
-
const gates =
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
const gates =
|
|
107
|
+
typeof options.gates === 'string'
|
|
108
|
+
? options.gates.split(',').map((g) => g.trim())
|
|
109
|
+
: options.gates;
|
|
99
110
|
|
|
100
111
|
// Create waiver object
|
|
101
112
|
const waiver = {
|
|
@@ -112,10 +123,18 @@ async function createWaiver(options) {
|
|
|
112
123
|
status: 'active',
|
|
113
124
|
};
|
|
114
125
|
|
|
115
|
-
// Save waiver
|
|
126
|
+
// Save individual waiver file
|
|
116
127
|
const waiverPath = path.join(process.cwd(), WAIVER_DIR, `${waiverId}.yaml`);
|
|
117
128
|
fs.writeFileSync(waiverPath, yaml.dump(waiver, { lineWidth: -1 }));
|
|
118
129
|
|
|
130
|
+
// Also add to active waivers file
|
|
131
|
+
try {
|
|
132
|
+
await addToActiveWaivers(waiver);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(`Failed to add waiver to active waivers: ${error.message}`);
|
|
135
|
+
console.error(error.stack);
|
|
136
|
+
}
|
|
137
|
+
|
|
119
138
|
console.log(chalk.green(`\n✅ Waiver created: ${waiverId}`));
|
|
120
139
|
console.log(` Title: ${waiver.title}`);
|
|
121
140
|
console.log(` Reason: ${waiver.reason}`);
|
|
@@ -132,36 +151,42 @@ async function createWaiver(options) {
|
|
|
132
151
|
*/
|
|
133
152
|
async function listWaivers(_options) {
|
|
134
153
|
const waiversDir = path.join(process.cwd(), WAIVER_DIR);
|
|
135
|
-
|
|
154
|
+
|
|
136
155
|
if (!fs.existsSync(waiversDir)) {
|
|
137
156
|
console.log(chalk.yellow('\nℹ️ No waivers found\n'));
|
|
138
157
|
return;
|
|
139
158
|
}
|
|
140
159
|
|
|
141
|
-
const waiverFiles = fs.readdirSync(waiversDir).filter(f => f.endsWith('.yaml'));
|
|
160
|
+
const waiverFiles = fs.readdirSync(waiversDir).filter((f) => f.endsWith('.yaml'));
|
|
142
161
|
|
|
143
162
|
if (waiverFiles.length === 0) {
|
|
144
163
|
console.log(chalk.yellow('\nℹ️ No waivers found\n'));
|
|
145
164
|
return;
|
|
146
165
|
}
|
|
147
166
|
|
|
148
|
-
const waivers = waiverFiles.map(file => {
|
|
167
|
+
const waivers = waiverFiles.map((file) => {
|
|
149
168
|
const content = fs.readFileSync(path.join(waiversDir, file), 'utf8');
|
|
150
169
|
return yaml.load(content);
|
|
151
170
|
});
|
|
152
171
|
|
|
153
172
|
// Filter by status
|
|
154
|
-
const activeWaivers = waivers.filter(
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
const activeWaivers = waivers.filter(
|
|
174
|
+
(w) => w.status === 'active' && new Date(w.expires_at) > new Date()
|
|
175
|
+
);
|
|
176
|
+
const expiredWaivers = waivers.filter(
|
|
177
|
+
(w) => w.status === 'active' && new Date(w.expires_at) <= new Date()
|
|
178
|
+
);
|
|
179
|
+
const revokedWaivers = waivers.filter((w) => w.status === 'revoked');
|
|
157
180
|
|
|
158
181
|
console.log(chalk.blue('\n🔖 CAWS Quality Gate Waivers\n'));
|
|
159
182
|
console.log('─'.repeat(60));
|
|
160
183
|
|
|
161
184
|
if (activeWaivers.length > 0) {
|
|
162
185
|
console.log(chalk.green('\n✅ Active Waivers:\n'));
|
|
163
|
-
activeWaivers.forEach(waiver => {
|
|
164
|
-
const daysLeft = Math.ceil(
|
|
186
|
+
activeWaivers.forEach((waiver) => {
|
|
187
|
+
const daysLeft = Math.ceil(
|
|
188
|
+
(new Date(waiver.expires_at) - new Date()) / (1000 * 60 * 60 * 24)
|
|
189
|
+
);
|
|
165
190
|
console.log(`🔖 ${chalk.bold(waiver.id)}: ${waiver.title}`);
|
|
166
191
|
console.log(` Reason: ${waiver.reason}`);
|
|
167
192
|
console.log(` Gates: ${waiver.gates.join(', ')}`);
|
|
@@ -173,7 +198,7 @@ async function listWaivers(_options) {
|
|
|
173
198
|
|
|
174
199
|
if (expiredWaivers.length > 0) {
|
|
175
200
|
console.log(chalk.yellow('\n⚠️ Expired Waivers:\n'));
|
|
176
|
-
expiredWaivers.forEach(waiver => {
|
|
201
|
+
expiredWaivers.forEach((waiver) => {
|
|
177
202
|
console.log(`🔖 ${chalk.bold(waiver.id)}: ${waiver.title}`);
|
|
178
203
|
console.log(` Expired: ${waiver.expires_at}`);
|
|
179
204
|
console.log();
|
|
@@ -182,7 +207,7 @@ async function listWaivers(_options) {
|
|
|
182
207
|
|
|
183
208
|
if (revokedWaivers.length > 0) {
|
|
184
209
|
console.log(chalk.red('\n❌ Revoked Waivers:\n'));
|
|
185
|
-
revokedWaivers.forEach(waiver => {
|
|
210
|
+
revokedWaivers.forEach((waiver) => {
|
|
186
211
|
console.log(`🔖 ${chalk.bold(waiver.id)}: ${waiver.title}`);
|
|
187
212
|
console.log(` Revoked: ${waiver.revoked_at}`);
|
|
188
213
|
console.log();
|
|
@@ -207,7 +232,7 @@ async function showWaiver(waiverId, _options) {
|
|
|
207
232
|
}
|
|
208
233
|
|
|
209
234
|
const waiverPath = path.join(process.cwd(), WAIVER_DIR, `${waiverId}.yaml`);
|
|
210
|
-
|
|
235
|
+
|
|
211
236
|
if (!fs.existsSync(waiverPath)) {
|
|
212
237
|
console.error(chalk.red(`\n❌ Waiver not found: ${waiverId}\n`));
|
|
213
238
|
process.exit(1);
|
|
@@ -222,7 +247,9 @@ async function showWaiver(waiverId, _options) {
|
|
|
222
247
|
|
|
223
248
|
console.log(chalk.blue('\n🔖 Waiver Details\n'));
|
|
224
249
|
console.log('─'.repeat(60));
|
|
225
|
-
console.log(
|
|
250
|
+
console.log(
|
|
251
|
+
`\n${statusIcon} Status: ${chalk.bold(isActive ? 'Active' : isExpired ? 'Expired' : waiver.status)}`
|
|
252
|
+
);
|
|
226
253
|
console.log(`\n📋 ${chalk.bold(waiver.title)}`);
|
|
227
254
|
console.log(` ID: ${waiver.id}`);
|
|
228
255
|
console.log(` Reason: ${waiver.reason}`);
|
|
@@ -230,7 +257,7 @@ async function showWaiver(waiverId, _options) {
|
|
|
230
257
|
console.log(`\n📝 Description:`);
|
|
231
258
|
console.log(` ${waiver.description}`);
|
|
232
259
|
console.log(`\n🔒 Waived Quality Gates:`);
|
|
233
|
-
waiver.gates.forEach(gate => {
|
|
260
|
+
waiver.gates.forEach((gate) => {
|
|
234
261
|
console.log(` • ${gate}`);
|
|
235
262
|
});
|
|
236
263
|
console.log(`\n🛡️ Mitigation Plan:`);
|
|
@@ -259,7 +286,7 @@ async function revokeWaiver(waiverId, options) {
|
|
|
259
286
|
}
|
|
260
287
|
|
|
261
288
|
const waiverPath = path.join(process.cwd(), WAIVER_DIR, `${waiverId}.yaml`);
|
|
262
|
-
|
|
289
|
+
|
|
263
290
|
if (!fs.existsSync(waiverPath)) {
|
|
264
291
|
console.error(chalk.red(`\n❌ Waiver not found: ${waiverId}\n`));
|
|
265
292
|
process.exit(1);
|
|
@@ -289,5 +316,53 @@ async function revokeWaiver(waiverId, options) {
|
|
|
289
316
|
console.log(` Reason: ${waiver.revocation_reason}\n`);
|
|
290
317
|
}
|
|
291
318
|
|
|
292
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Add waiver to active waivers file for quality gates integration
|
|
321
|
+
*/
|
|
322
|
+
async function addToActiveWaivers(waiver) {
|
|
323
|
+
try {
|
|
324
|
+
const waiversManager = new WaiversManager();
|
|
325
|
+
|
|
326
|
+
// Load existing active waivers
|
|
327
|
+
const activeWaivers = await waiversManager.loadActiveWaivers();
|
|
328
|
+
|
|
329
|
+
// Check if waiver already exists
|
|
330
|
+
const existingIndex = activeWaivers.findIndex((w) => w.id === waiver.id);
|
|
331
|
+
|
|
332
|
+
// Normalize waiver format
|
|
333
|
+
const normalizedWaiver = {
|
|
334
|
+
id: waiver.id,
|
|
335
|
+
title: waiver.title || waiver.description || waiver.id,
|
|
336
|
+
reason: waiver.reason || waiver.reason_code || 'unknown',
|
|
337
|
+
description: waiver.description || waiver.title || waiver.id,
|
|
338
|
+
gates: Array.isArray(waiver.gates) ? waiver.gates : [waiver.gates],
|
|
339
|
+
expires_at: waiver.expires_at,
|
|
340
|
+
approved_by: waiver.approved_by || waiver.risk_owner || 'unknown',
|
|
341
|
+
created_at: waiver.created_at || waiver.approved_at || new Date().toISOString(),
|
|
342
|
+
risk_assessment: waiver.risk_assessment || {
|
|
343
|
+
impact_level: waiver.impact_level || 'medium',
|
|
344
|
+
mitigation_plan: waiver.mitigation || waiver.mitigation_plan || 'Unknown mitigation',
|
|
345
|
+
},
|
|
346
|
+
metadata: waiver.metadata || {},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
if (existingIndex >= 0) {
|
|
350
|
+
// Update existing waiver
|
|
351
|
+
activeWaivers[existingIndex] = normalizedWaiver;
|
|
352
|
+
} else {
|
|
353
|
+
// Add new waiver
|
|
354
|
+
activeWaivers.push(normalizedWaiver);
|
|
355
|
+
}
|
|
293
356
|
|
|
357
|
+
// Save updated active waivers
|
|
358
|
+
await waiversManager.saveActiveWaivers(activeWaivers);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
// Enhanced error logging
|
|
361
|
+
console.error(`Error adding waiver to active waivers: ${error.message}`);
|
|
362
|
+
console.error(error.stack);
|
|
363
|
+
console.warn(`Warning: Could not add waiver to active waivers file: ${error.message}`);
|
|
364
|
+
// Don't fail the waiver creation if this fails
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = { waiversCommand };
|