@paths.design/caws-cli 3.0.0 → 3.1.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/README.md +295 -150
- package/dist/budget-derivation.d.ts +35 -0
- package/dist/budget-derivation.d.ts.map +1 -0
- package/dist/budget-derivation.js +204 -0
- package/dist/cicd-optimizer.d.ts +142 -0
- package/dist/cicd-optimizer.d.ts.map +1 -0
- package/dist/cicd-optimizer.js +504 -0
- package/dist/commands/burnup.d.ts +6 -0
- package/dist/commands/burnup.d.ts.map +1 -0
- package/dist/commands/burnup.js +90 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +514 -0
- package/dist/commands/provenance.d.ts +22 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +594 -0
- package/dist/commands/tool.d.ts +13 -0
- package/dist/commands/tool.d.ts.map +1 -0
- package/dist/commands/tool.js +138 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +80 -0
- package/dist/config/index.d.ts +29 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +132 -0
- package/dist/error-handler.d.ts +50 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +253 -0
- package/dist/generators/working-spec.d.ts +13 -0
- package/dist/generators/working-spec.d.ts.map +1 -0
- package/dist/generators/working-spec.js +204 -0
- package/dist/index-new.d.ts +5 -0
- package/dist/index-new.d.ts.map +1 -0
- package/dist/index-new.js +317 -0
- package/dist/index.d.ts +3 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -1659
- package/dist/index.js.backup +4711 -0
- package/dist/scaffold/cursor-hooks.d.ts +7 -0
- package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
- package/dist/scaffold/cursor-hooks.js +152 -0
- package/dist/scaffold/index.d.ts +20 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +486 -0
- package/dist/test-analysis.d.ts +182 -0
- package/dist/test-analysis.d.ts.map +1 -0
- package/dist/test-analysis.js +580 -0
- package/dist/tool-interface.d.ts +236 -0
- package/dist/tool-interface.d.ts.map +1 -0
- package/dist/tool-interface.js +314 -0
- package/dist/tool-loader.d.ts +77 -0
- package/dist/tool-loader.d.ts.map +1 -0
- package/dist/tool-loader.js +298 -0
- package/dist/tool-validator.d.ts +72 -0
- package/dist/tool-validator.d.ts.map +1 -0
- package/dist/tool-validator.js +387 -0
- package/dist/utils/detection.d.ts +7 -0
- package/dist/utils/detection.d.ts.map +1 -0
- package/dist/utils/detection.js +174 -0
- package/dist/utils/finalization.d.ts +17 -0
- package/dist/utils/finalization.d.ts.map +1 -0
- package/dist/utils/finalization.js +229 -0
- package/dist/utils/project-analysis.d.ts +14 -0
- package/dist/utils/project-analysis.d.ts.map +1 -0
- package/dist/utils/project-analysis.js +105 -0
- package/dist/validation/spec-validation.d.ts +29 -0
- package/dist/validation/spec-validation.d.ts.map +1 -0
- package/dist/validation/spec-validation.js +376 -0
- package/dist/waivers-manager.d.ts +167 -0
- package/dist/waivers-manager.d.ts.map +1 -0
- package/dist/waivers-manager.js +549 -0
- package/package.json +10 -12
- package/templates/.cursor/README.md +311 -0
- package/templates/.cursor/hooks/audit.sh +55 -0
- package/templates/.cursor/hooks/block-dangerous.sh +77 -0
- package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
- package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/templates/.cursor/hooks/format.sh +38 -0
- package/templates/.cursor/hooks/naming-check.sh +64 -0
- package/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/templates/.cursor/hooks/validate-spec.sh +38 -0
- package/templates/.cursor/hooks.json +59 -0
- package/templates/.github/copilot/instructions.md +311 -0
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/templates/.vscode/launch.json +56 -0
- package/templates/.vscode/settings.json +93 -0
- package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/templates/apps/tools/caws/README.md +1 -1
- package/templates/apps/tools/caws/prompt-lint.js.backup +274 -0
- package/templates/apps/tools/caws/provenance.js.backup +73 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
- package/templates/codemod/test.js +93 -1
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CAWS Waivers Manager
|
|
3
|
+
*
|
|
4
|
+
* Provides fast-lane escape hatches for exceptional circumstances.
|
|
5
|
+
* Waivers are temporary bypasses of quality gates with full audit trails.
|
|
6
|
+
*
|
|
7
|
+
* @author @darianrosebrook
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Waiver Manager Class
|
|
16
|
+
* Handles waiver creation, validation, expiration, and audit logging
|
|
17
|
+
*/
|
|
18
|
+
class WaiversManager {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
21
|
+
this.waiversDir = path.join(this.projectRoot, '.caws', 'waivers');
|
|
22
|
+
this.waiversFile = path.join(this.waiversDir, 'active-waivers.yaml');
|
|
23
|
+
this.auditLogFile = path.join(this.waiversDir, 'waiver-audit.log');
|
|
24
|
+
|
|
25
|
+
// Ensure waivers directory exists
|
|
26
|
+
if (!fs.existsSync(this.waiversDir)) {
|
|
27
|
+
fs.mkdirSync(this.waiversDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Waiver Schema Definition
|
|
33
|
+
*/
|
|
34
|
+
getWaiverSchema() {
|
|
35
|
+
return {
|
|
36
|
+
type: 'object',
|
|
37
|
+
required: ['id', 'title', 'reason', 'gates', 'expires_at', 'approved_by', 'created_at'],
|
|
38
|
+
properties: {
|
|
39
|
+
id: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
pattern: '^WV-\\d{4}$',
|
|
42
|
+
description: 'Waiver ID in format WV-XXXX',
|
|
43
|
+
},
|
|
44
|
+
title: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
minLength: 10,
|
|
47
|
+
maxLength: 200,
|
|
48
|
+
description: 'Clear, descriptive title explaining the waiver',
|
|
49
|
+
},
|
|
50
|
+
reason: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
enum: [
|
|
53
|
+
'emergency_hotfix',
|
|
54
|
+
'legacy_integration',
|
|
55
|
+
'experimental_feature',
|
|
56
|
+
'third_party_constraint',
|
|
57
|
+
'performance_critical',
|
|
58
|
+
'security_patch',
|
|
59
|
+
'infrastructure_limitation',
|
|
60
|
+
'other',
|
|
61
|
+
],
|
|
62
|
+
description: 'Categorization of waiver reason',
|
|
63
|
+
},
|
|
64
|
+
description: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
minLength: 50,
|
|
67
|
+
maxLength: 1000,
|
|
68
|
+
description: 'Detailed explanation of why waiver is needed',
|
|
69
|
+
},
|
|
70
|
+
gates: {
|
|
71
|
+
type: 'array',
|
|
72
|
+
items: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
enum: [
|
|
75
|
+
'spec_completeness',
|
|
76
|
+
'contract_compliance',
|
|
77
|
+
'coverage_threshold',
|
|
78
|
+
'mutation_threshold',
|
|
79
|
+
'security_scan',
|
|
80
|
+
'accessibility_check',
|
|
81
|
+
'performance_budget',
|
|
82
|
+
'scope_boundary',
|
|
83
|
+
'budget_limit',
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
minItems: 1,
|
|
87
|
+
description: 'Quality gates to waive',
|
|
88
|
+
},
|
|
89
|
+
risk_assessment: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
impact_level: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
enum: ['low', 'medium', 'high', 'critical'],
|
|
95
|
+
},
|
|
96
|
+
mitigation_plan: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
minLength: 50,
|
|
99
|
+
},
|
|
100
|
+
review_required: {
|
|
101
|
+
type: 'boolean',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
required: ['impact_level', 'mitigation_plan'],
|
|
105
|
+
},
|
|
106
|
+
expires_at: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
format: 'date-time',
|
|
109
|
+
description: 'ISO 8601 datetime when waiver expires',
|
|
110
|
+
},
|
|
111
|
+
approved_by: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
description: 'Person/entity approving the waiver',
|
|
114
|
+
},
|
|
115
|
+
created_at: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
format: 'date-time',
|
|
118
|
+
description: 'ISO 8601 datetime when waiver was created',
|
|
119
|
+
},
|
|
120
|
+
metadata: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
related_pr: { type: 'string' },
|
|
124
|
+
related_issue: { type: 'string' },
|
|
125
|
+
environment: { type: 'string', enum: ['development', 'staging', 'production'] },
|
|
126
|
+
urgency: { type: 'string', enum: ['low', 'normal', 'high', 'critical'] },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a new waiver
|
|
135
|
+
*/
|
|
136
|
+
async createWaiver(waiverData) {
|
|
137
|
+
// Generate waiver ID
|
|
138
|
+
const waiverId = await this.generateWaiverId();
|
|
139
|
+
|
|
140
|
+
// Set creation timestamp
|
|
141
|
+
const now = new Date().toISOString();
|
|
142
|
+
|
|
143
|
+
// Construct full waiver object
|
|
144
|
+
const waiver = {
|
|
145
|
+
id: waiverId,
|
|
146
|
+
title: waiverData.title,
|
|
147
|
+
reason: waiverData.reason,
|
|
148
|
+
description: waiverData.description,
|
|
149
|
+
gates: waiverData.gates,
|
|
150
|
+
risk_assessment: waiverData.risk_assessment,
|
|
151
|
+
expires_at: waiverData.expires_at,
|
|
152
|
+
approved_by: waiverData.approved_by,
|
|
153
|
+
created_at: now,
|
|
154
|
+
metadata: waiverData.metadata || {},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Validate waiver against schema
|
|
158
|
+
const validation = this.validateWaiver(waiver);
|
|
159
|
+
if (!validation.valid) {
|
|
160
|
+
throw new Error(`Waiver validation failed: ${validation.errors.join(', ')}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check for conflicts with existing waivers
|
|
164
|
+
const conflicts = await this.checkWaiverConflicts(waiver);
|
|
165
|
+
if (conflicts.length > 0) {
|
|
166
|
+
throw new Error(`Waiver conflicts with existing waivers: ${conflicts.join(', ')}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Load existing waivers
|
|
170
|
+
const waivers = await this.loadActiveWaivers();
|
|
171
|
+
|
|
172
|
+
// Add new waiver
|
|
173
|
+
waivers.push(waiver);
|
|
174
|
+
|
|
175
|
+
// Save waivers
|
|
176
|
+
await this.saveActiveWaivers(waivers);
|
|
177
|
+
|
|
178
|
+
// Log waiver creation
|
|
179
|
+
await this.auditLog('CREATE', waiverId, {
|
|
180
|
+
title: waiver.title,
|
|
181
|
+
reason: waiver.reason,
|
|
182
|
+
gates: waiver.gates,
|
|
183
|
+
expires_at: waiver.expires_at,
|
|
184
|
+
approved_by: waiver.approved_by,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Flag high-risk waivers for review
|
|
188
|
+
if (
|
|
189
|
+
waiver.risk_assessment.impact_level === 'critical' ||
|
|
190
|
+
waiver.risk_assessment.review_required
|
|
191
|
+
) {
|
|
192
|
+
await this.flagForReview(waiver);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return waiver;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check if waiver applies to specific gates
|
|
200
|
+
*/
|
|
201
|
+
async checkWaiverCoverage(gatesToCheck, context = {}) {
|
|
202
|
+
const activeWaivers = await this.getActiveWaivers();
|
|
203
|
+
const coveredGates = new Set();
|
|
204
|
+
const waiverDetails = [];
|
|
205
|
+
|
|
206
|
+
for (const waiver of activeWaivers) {
|
|
207
|
+
// Check if waiver applies to current context
|
|
208
|
+
if (!this.waiverAppliesToContext(waiver, context)) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check which gates this waiver covers
|
|
213
|
+
for (const gate of gatesToCheck) {
|
|
214
|
+
if (waiver.gates.includes(gate)) {
|
|
215
|
+
coveredGates.add(gate);
|
|
216
|
+
waiverDetails.push({
|
|
217
|
+
gate,
|
|
218
|
+
waiver_id: waiver.id,
|
|
219
|
+
reason: waiver.reason,
|
|
220
|
+
expires_at: waiver.expires_at,
|
|
221
|
+
approved_by: waiver.approved_by,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
coveredGates: Array.from(coveredGates),
|
|
229
|
+
waiverDetails,
|
|
230
|
+
allCovered: coveredGates.size === gatesToCheck.length,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get all active waivers
|
|
236
|
+
*/
|
|
237
|
+
async getActiveWaivers() {
|
|
238
|
+
const waivers = await this.loadActiveWaivers();
|
|
239
|
+
const now = new Date();
|
|
240
|
+
|
|
241
|
+
// Filter out expired waivers and clean up
|
|
242
|
+
const activeWaivers = waivers.filter((waiver) => {
|
|
243
|
+
const expiresAt = new Date(waiver.expires_at);
|
|
244
|
+
return expiresAt > now;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Auto-cleanup expired waivers
|
|
248
|
+
if (activeWaivers.length !== waivers.length) {
|
|
249
|
+
await this.saveActiveWaivers(activeWaivers);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return activeWaivers;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Revoke a waiver
|
|
257
|
+
*/
|
|
258
|
+
async revokeWaiver(waiverId, reason = 'Manual revocation') {
|
|
259
|
+
const waivers = await this.loadActiveWaivers();
|
|
260
|
+
const index = waivers.findIndex((w) => w.id === waiverId);
|
|
261
|
+
|
|
262
|
+
if (index === -1) {
|
|
263
|
+
throw new Error(`Waiver ${waiverId} not found`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const waiver = waivers[index];
|
|
267
|
+
waivers.splice(index, 1);
|
|
268
|
+
|
|
269
|
+
await this.saveActiveWaivers(waivers);
|
|
270
|
+
await this.auditLog('REVOKE', waiverId, { reason, original_waiver: waiver });
|
|
271
|
+
|
|
272
|
+
return waiver;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Extend waiver expiration
|
|
277
|
+
*/
|
|
278
|
+
async extendWaiver(waiverId, newExpiryDate, approvedBy) {
|
|
279
|
+
const waivers = await this.loadActiveWaivers();
|
|
280
|
+
const waiver = waivers.find((w) => w.id === waiverId);
|
|
281
|
+
|
|
282
|
+
if (!waiver) {
|
|
283
|
+
throw new Error(`Waiver ${waiverId} not found`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const oldExpiry = waiver.expires_at;
|
|
287
|
+
waiver.expires_at = new Date(newExpiryDate).toISOString();
|
|
288
|
+
waiver.metadata = waiver.metadata || {};
|
|
289
|
+
waiver.metadata.extended_by = approvedBy;
|
|
290
|
+
waiver.metadata.extended_at = new Date().toISOString();
|
|
291
|
+
waiver.metadata.previous_expiry = oldExpiry;
|
|
292
|
+
|
|
293
|
+
await this.saveActiveWaivers(waivers);
|
|
294
|
+
await this.auditLog('EXTEND', waiverId, {
|
|
295
|
+
new_expiry: waiver.expires_at,
|
|
296
|
+
approved_by: approvedBy,
|
|
297
|
+
old_expiry: oldExpiry,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return waiver;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get waiver statistics and health metrics
|
|
305
|
+
*/
|
|
306
|
+
async getWaiverStats() {
|
|
307
|
+
const waivers = await this.getActiveWaivers();
|
|
308
|
+
const now = new Date();
|
|
309
|
+
|
|
310
|
+
const stats = {
|
|
311
|
+
total_active: waivers.length,
|
|
312
|
+
by_reason: {},
|
|
313
|
+
by_risk_level: {},
|
|
314
|
+
expiring_soon: [], // Next 7 days
|
|
315
|
+
high_risk: [],
|
|
316
|
+
total_gates_waived: 0,
|
|
317
|
+
average_lifespan_days: 0,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
|
321
|
+
|
|
322
|
+
for (const waiver of waivers) {
|
|
323
|
+
// Count by reason
|
|
324
|
+
stats.by_reason[waiver.reason] = (stats.by_reason[waiver.reason] || 0) + 1;
|
|
325
|
+
|
|
326
|
+
// Count by risk level
|
|
327
|
+
const riskLevel = waiver.risk_assessment.impact_level;
|
|
328
|
+
stats.by_risk_level[riskLevel] = (stats.by_risk_level[riskLevel] || 0) + 1;
|
|
329
|
+
|
|
330
|
+
// Check expiring soon
|
|
331
|
+
const expiresAt = new Date(waiver.expires_at);
|
|
332
|
+
if (expiresAt <= sevenDaysFromNow) {
|
|
333
|
+
stats.expiring_soon.push({
|
|
334
|
+
id: waiver.id,
|
|
335
|
+
title: waiver.title,
|
|
336
|
+
expires_at: waiver.expires_at,
|
|
337
|
+
days_remaining: Math.ceil((expiresAt - now) / (24 * 60 * 60 * 1000)),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Track high-risk waivers
|
|
342
|
+
if (riskLevel === 'high' || riskLevel === 'critical') {
|
|
343
|
+
stats.high_risk.push({
|
|
344
|
+
id: waiver.id,
|
|
345
|
+
title: waiver.title,
|
|
346
|
+
risk_level: riskLevel,
|
|
347
|
+
reason: waiver.reason,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Count gates waived
|
|
352
|
+
stats.total_gates_waived += waiver.gates.length;
|
|
353
|
+
|
|
354
|
+
// Calculate lifespan
|
|
355
|
+
const createdAt = new Date(waiver.created_at);
|
|
356
|
+
const lifespanDays = (expiresAt - createdAt) / (24 * 60 * 60 * 1000);
|
|
357
|
+
stats.average_lifespan_days += lifespanDays;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (waivers.length > 0) {
|
|
361
|
+
stats.average_lifespan_days /= waivers.length;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return stats;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Private helper methods
|
|
368
|
+
|
|
369
|
+
async generateWaiverId() {
|
|
370
|
+
const existingWaivers = await this.loadActiveWaivers();
|
|
371
|
+
const usedIds = new Set(existingWaivers.map((w) => parseInt(w.id.split('-')[1])));
|
|
372
|
+
|
|
373
|
+
let counter = 1;
|
|
374
|
+
while (usedIds.has(counter)) {
|
|
375
|
+
counter++;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return `WV-${counter.toString().padStart(4, '0')}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
validateWaiver(waiver) {
|
|
382
|
+
// Basic validation - in production, use a full JSON schema validator
|
|
383
|
+
const errors = [];
|
|
384
|
+
|
|
385
|
+
if (!waiver.id || !waiver.id.match(/^WV-\d{4}$/)) {
|
|
386
|
+
errors.push('Invalid waiver ID format');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!waiver.title || waiver.title.length < 10) {
|
|
390
|
+
errors.push('Title too short (minimum 10 characters)');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
!waiver.reason ||
|
|
395
|
+
![
|
|
396
|
+
'emergency_hotfix',
|
|
397
|
+
'legacy_integration',
|
|
398
|
+
'experimental_feature',
|
|
399
|
+
'third_party_constraint',
|
|
400
|
+
'performance_critical',
|
|
401
|
+
'security_patch',
|
|
402
|
+
'infrastructure_limitation',
|
|
403
|
+
'other',
|
|
404
|
+
].includes(waiver.reason)
|
|
405
|
+
) {
|
|
406
|
+
errors.push('Invalid waiver reason');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!waiver.gates || !Array.isArray(waiver.gates) || waiver.gates.length === 0) {
|
|
410
|
+
errors.push('At least one gate must be specified');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (!waiver.expires_at) {
|
|
414
|
+
errors.push('Expiration date required');
|
|
415
|
+
} else {
|
|
416
|
+
const expiresAt = new Date(waiver.expires_at);
|
|
417
|
+
const now = new Date();
|
|
418
|
+
if (expiresAt <= now) {
|
|
419
|
+
errors.push('Expiration date must be in the future');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!waiver.approved_by) {
|
|
424
|
+
errors.push('Approval information required');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
valid: errors.length === 0,
|
|
429
|
+
errors,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async checkWaiverConflicts(newWaiver) {
|
|
434
|
+
const activeWaivers = await this.getActiveWaivers();
|
|
435
|
+
const conflicts = [];
|
|
436
|
+
|
|
437
|
+
for (const existingWaiver of activeWaivers) {
|
|
438
|
+
// Check for overlapping gates
|
|
439
|
+
const overlappingGates = newWaiver.gates.filter((gate) =>
|
|
440
|
+
existingWaiver.gates.includes(gate)
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
if (overlappingGates.length > 0) {
|
|
444
|
+
conflicts.push(
|
|
445
|
+
`Waiver ${existingWaiver.id} already covers gates: ${overlappingGates.join(', ')}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return conflicts;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
waiverAppliesToContext(waiver, context) {
|
|
454
|
+
// Check environment restrictions
|
|
455
|
+
if (waiver.metadata?.environment && context.environment) {
|
|
456
|
+
if (waiver.metadata.environment !== context.environment) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Add more context checks as needed (branch, user, etc.)
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async loadActiveWaivers() {
|
|
466
|
+
try {
|
|
467
|
+
if (!fs.existsSync(this.waiversFile)) {
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const content = fs.readFileSync(this.waiversFile, 'utf8');
|
|
472
|
+
return yaml.load(content) || [];
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.warn(`Warning: Could not load waivers file: ${error.message}`);
|
|
475
|
+
return [];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async saveActiveWaivers(waivers) {
|
|
480
|
+
const content = yaml.dump(waivers, {
|
|
481
|
+
indent: 2,
|
|
482
|
+
sortKeys: true,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
fs.writeFileSync(this.waiversFile, content, 'utf8');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async auditLog(action, waiverId, details) {
|
|
489
|
+
const logEntry = {
|
|
490
|
+
timestamp: new Date().toISOString(),
|
|
491
|
+
action,
|
|
492
|
+
waiver_id: waiverId,
|
|
493
|
+
details,
|
|
494
|
+
user: process.env.USER || process.env.USERNAME || 'unknown',
|
|
495
|
+
cwd: process.cwd(),
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const logLine = JSON.stringify(logEntry) + '\n';
|
|
499
|
+
|
|
500
|
+
fs.appendFileSync(this.auditLogFile, logLine);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async flagForReview(waiver) {
|
|
504
|
+
// Create a flag file for code owners to review
|
|
505
|
+
const flagFile = path.join(this.waiversDir, `review-${waiver.id}.md`);
|
|
506
|
+
|
|
507
|
+
const flagContent = `# Waiver Review Required: ${waiver.id}
|
|
508
|
+
|
|
509
|
+
## Waiver Details
|
|
510
|
+
- **ID**: ${waiver.id}
|
|
511
|
+
- **Title**: ${waiver.title}
|
|
512
|
+
- **Reason**: ${waiver.reason}
|
|
513
|
+
- **Risk Level**: ${waiver.risk_assessment.impact_level}
|
|
514
|
+
- **Approved By**: ${waiver.approved_by}
|
|
515
|
+
- **Expires**: ${waiver.expires_at}
|
|
516
|
+
|
|
517
|
+
## Description
|
|
518
|
+
${waiver.description}
|
|
519
|
+
|
|
520
|
+
## Gates Waived
|
|
521
|
+
${waiver.gates.map((gate) => `- ${gate}`).join('\n')}
|
|
522
|
+
|
|
523
|
+
## Risk Assessment
|
|
524
|
+
**Impact Level**: ${waiver.risk_assessment.impact_level}
|
|
525
|
+
**Mitigation Plan**: ${waiver.risk_assessment.mitigation_plan}
|
|
526
|
+
|
|
527
|
+
## Review Checklist
|
|
528
|
+
- [ ] Risk assessment is adequate
|
|
529
|
+
- [ ] Mitigation plan is sufficient
|
|
530
|
+
- [ ] Waiver duration is appropriate
|
|
531
|
+
- [ ] No alternative solutions available
|
|
532
|
+
- [ ] Code owners approve waiver usage
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
*This waiver requires manual review. Please check the waiver details and mitigation plan before approving continued use.*
|
|
536
|
+
`;
|
|
537
|
+
|
|
538
|
+
fs.writeFileSync(flagFile, flagContent);
|
|
539
|
+
|
|
540
|
+
// Also log this flagging action
|
|
541
|
+
await this.auditLog('FLAG_REVIEW', waiver.id, {
|
|
542
|
+
flag_file: flagFile,
|
|
543
|
+
risk_level: waiver.risk_assessment.impact_level,
|
|
544
|
+
review_required: waiver.risk_assessment.review_required,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
module.exports = WaiversManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paths.design/caws-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "CAWS CLI - Coding Agent Workflow System command line tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"templates"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "mkdir -p dist && cp src
|
|
17
|
-
"dev": "mkdir -p dist && cp src
|
|
16
|
+
"build": "mkdir -p dist && cp -r src/* dist/ && npm run typecheck",
|
|
17
|
+
"dev": "mkdir -p dist && cp -r src/* dist/ && node dist/index.js",
|
|
18
18
|
"typecheck": "tsc --emitDeclarationOnly --outDir dist",
|
|
19
19
|
"start": "node dist/index.js",
|
|
20
20
|
"test": "npm run build && jest && npm run test:cleanup",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"perf:budgets": "npm run build && jest --testPathPatterns=perf && npm run test:cleanup",
|
|
28
28
|
"test:watch": "npm run build && jest --watch",
|
|
29
29
|
"test:cleanup": "node scripts/cleanup-tests.js",
|
|
30
|
-
"lint": "eslint src/**/*.js tests/**/*.js",
|
|
31
|
-
"lint:fix": "eslint src/**/*.js tests/**/*.js --fix",
|
|
32
|
-
"lint:staged": "eslint src/**/*.js tests/**/*.js",
|
|
30
|
+
"lint": "npx eslint src/**/*.js tests/**/*.js",
|
|
31
|
+
"lint:fix": "npx eslint src/**/*.js tests/**/*.js --fix",
|
|
32
|
+
"lint:staged": "npx eslint src/**/*.js tests/**/*.js",
|
|
33
33
|
"format": "prettier --write src/**/*.js tests/**/*.js",
|
|
34
34
|
"validate": "echo 'CLI package validation not required'",
|
|
35
35
|
"caws:validate": "node ../../.caws/validate.js ../../.caws/working-spec.yaml",
|
|
@@ -54,22 +54,20 @@
|
|
|
54
54
|
"ajv": "^8.12.0",
|
|
55
55
|
"commander": "^11.0.0",
|
|
56
56
|
"fs-extra": "^11.0.0",
|
|
57
|
-
"inquirer": "
|
|
57
|
+
"inquirer": "8.2.7"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^9.0.0",
|
|
60
61
|
"@semantic-release/changelog": "6.0.3",
|
|
61
62
|
"@semantic-release/exec": "7.1.0",
|
|
62
63
|
"@semantic-release/git": "10.0.1",
|
|
63
64
|
"@semantic-release/npm": "12.0.2",
|
|
64
65
|
"@types/fs-extra": "^11.0.0",
|
|
65
|
-
"@types/inquirer": "^
|
|
66
|
+
"@types/inquirer": "^8.2.6",
|
|
66
67
|
"@types/js-yaml": "^4.0.0",
|
|
67
68
|
"@types/node": "^20.0.0",
|
|
68
69
|
"chalk": "4.1.2",
|
|
69
|
-
"eslint": "9.
|
|
70
|
-
"eslint-config-prettier": "10.1.8",
|
|
71
|
-
"eslint-plugin-n": "17.23.1",
|
|
72
|
-
"eslint-plugin-node": "^11.1.0",
|
|
70
|
+
"eslint": "^9.0.0",
|
|
73
71
|
"jest": "30.1.3",
|
|
74
72
|
"js-yaml": "4.1.0",
|
|
75
73
|
"lint-staged": "15.5.2",
|