@paths.design/caws-cli 2.0.0 → 3.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.
Files changed (50) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +101 -96
  3. package/package.json +3 -3
  4. package/templates/agents.md +820 -0
  5. package/templates/apps/tools/caws/COMPLETION_REPORT.md +331 -0
  6. package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +360 -0
  7. package/templates/apps/tools/caws/README.md +463 -0
  8. package/templates/apps/tools/caws/TEST_STATUS.md +365 -0
  9. package/templates/apps/tools/caws/attest.js +357 -0
  10. package/templates/apps/tools/caws/ci-optimizer.js +642 -0
  11. package/templates/apps/tools/caws/config.ts +245 -0
  12. package/templates/apps/tools/caws/cross-functional.js +876 -0
  13. package/templates/apps/tools/caws/dashboard.js +1112 -0
  14. package/templates/apps/tools/caws/flake-detector.ts +362 -0
  15. package/templates/apps/tools/caws/gates.js +198 -0
  16. package/templates/apps/tools/caws/gates.ts +237 -0
  17. package/templates/apps/tools/caws/language-adapters.ts +381 -0
  18. package/templates/apps/tools/caws/language-support.d.ts +367 -0
  19. package/templates/apps/tools/caws/language-support.d.ts.map +1 -0
  20. package/templates/apps/tools/caws/language-support.js +585 -0
  21. package/templates/apps/tools/caws/legacy-assessment.ts +408 -0
  22. package/templates/apps/tools/caws/legacy-assessor.js +764 -0
  23. package/templates/apps/tools/caws/mutant-analyzer.js +734 -0
  24. package/templates/apps/tools/caws/perf-budgets.ts +349 -0
  25. package/templates/apps/tools/caws/property-testing.js +707 -0
  26. package/templates/apps/tools/caws/provenance.d.ts +14 -0
  27. package/templates/apps/tools/caws/provenance.d.ts.map +1 -0
  28. package/templates/apps/tools/caws/provenance.js +132 -0
  29. package/templates/apps/tools/caws/provenance.ts +211 -0
  30. package/templates/apps/tools/caws/schemas/waivers.schema.json +30 -0
  31. package/templates/apps/tools/caws/schemas/working-spec.schema.json +115 -0
  32. package/templates/apps/tools/caws/scope-guard.js +208 -0
  33. package/templates/apps/tools/caws/security-provenance.ts +483 -0
  34. package/templates/apps/tools/caws/shared/base-tool.ts +281 -0
  35. package/templates/apps/tools/caws/shared/config-manager.ts +366 -0
  36. package/templates/apps/tools/caws/shared/gate-checker.ts +597 -0
  37. package/templates/apps/tools/caws/shared/types.ts +444 -0
  38. package/templates/apps/tools/caws/shared/validator.ts +305 -0
  39. package/templates/apps/tools/caws/shared/waivers-manager.ts +174 -0
  40. package/templates/apps/tools/caws/spec-test-mapper.ts +391 -0
  41. package/templates/apps/tools/caws/templates/working-spec.template.yml +60 -0
  42. package/templates/apps/tools/caws/test-quality.js +578 -0
  43. package/templates/apps/tools/caws/tools-allow.json +331 -0
  44. package/templates/apps/tools/caws/validate.js +76 -0
  45. package/templates/apps/tools/caws/validate.ts +228 -0
  46. package/templates/apps/tools/caws/waivers.js +344 -0
  47. package/templates/apps/tools/caws/waivers.yml +19 -0
  48. package/templates/codemod/README.md +1 -0
  49. package/templates/codemod/test.js +1 -0
  50. package/templates/docs/README.md +150 -0
@@ -0,0 +1,305 @@
1
+ /**
2
+ * CAWS Validator
3
+ * Shared validation utilities for working specs, provenance, and other data
4
+ *
5
+ * @author @darianrosebrook
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import Ajv from 'ajv';
11
+ import yaml from 'js-yaml';
12
+ import { ValidationResult, ContractValidationResult } from './types.js';
13
+ import { CawsBaseTool } from './base-tool.js';
14
+
15
+ export class CawsValidator extends CawsBaseTool {
16
+ private ajv: Ajv;
17
+
18
+ constructor() {
19
+ super();
20
+ this.ajv = new Ajv({
21
+ allErrors: true,
22
+ strict: false,
23
+ allowUnionTypes: true,
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Validate a working spec file
29
+ */
30
+ validateWorkingSpec(specPath: string): ValidationResult {
31
+ try {
32
+ // Read the working spec file
33
+ const specContent = fs.readFileSync(specPath, 'utf-8');
34
+ let spec: any;
35
+
36
+ // Try to parse as YAML first, then JSON
37
+ try {
38
+ spec = yaml.load(specContent);
39
+ } catch {
40
+ try {
41
+ spec = JSON.parse(specContent);
42
+ } catch {
43
+ return {
44
+ passed: false,
45
+ score: 0,
46
+ details: {},
47
+ errors: ['Invalid JSON/YAML format in working spec'],
48
+ };
49
+ }
50
+ }
51
+
52
+ // Load schema if available
53
+ const schemaPath = path.join(this.getCawsDirectory(), 'schemas/working-spec.schema.json');
54
+
55
+ if (fs.existsSync(schemaPath)) {
56
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
57
+ const schema = JSON.parse(schemaContent);
58
+
59
+ // Validate against schema
60
+ const validate = this.ajv.compile(schema);
61
+ const valid = validate(spec);
62
+
63
+ if (!valid) {
64
+ return {
65
+ passed: false,
66
+ errors: validate.errors?.map((err) => `${err.instancePath}: ${err.message}`) || [],
67
+ score: 0,
68
+ details: {},
69
+ };
70
+ }
71
+ }
72
+
73
+ // Additional business logic validations
74
+ const warnings: string[] = [];
75
+
76
+ // Check risk tier thresholds
77
+ if (spec.risk_tier === 1 && spec.acceptance?.length < 5) {
78
+ warnings.push('Tier 1 specs should have at least 5 acceptance criteria');
79
+ }
80
+
81
+ if (spec.risk_tier === 2 && spec.contracts?.length === 0) {
82
+ warnings.push('Tier 2 specs should have contract definitions');
83
+ }
84
+
85
+ // Check for required non-functional requirements
86
+ const requiredNonFunctional = ['perf'];
87
+ const missingNonFunctional = requiredNonFunctional.filter(
88
+ (req) => !spec.non_functional?.[req]
89
+ );
90
+
91
+ if (missingNonFunctional.length > 0) {
92
+ warnings.push(`Missing non-functional requirements: ${missingNonFunctional.join(', ')}`);
93
+ }
94
+
95
+ return {
96
+ passed: true,
97
+ score: 1,
98
+ details: {},
99
+ warnings: warnings.length > 0 ? warnings : undefined,
100
+ };
101
+ } catch (error) {
102
+ return {
103
+ passed: false,
104
+ score: 0,
105
+ details: {},
106
+ errors: [`Validation failed: ${error}`],
107
+ };
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Validate a provenance file
113
+ */
114
+ validateProvenance(provenancePath: string): ValidationResult {
115
+ try {
116
+ const provenanceContent = fs.readFileSync(provenancePath, 'utf-8');
117
+ const provenance = JSON.parse(provenanceContent);
118
+
119
+ // Basic structure validation
120
+ const requiredFields = ['agent', 'model', 'commit', 'artifacts', 'results', 'approvals'];
121
+ const missingFields = requiredFields.filter((field) => !provenance[field]);
122
+
123
+ if (missingFields.length > 0) {
124
+ return {
125
+ passed: false,
126
+ score: 0,
127
+ details: {},
128
+ errors: [`Missing required fields: ${missingFields.join(', ')}`],
129
+ };
130
+ }
131
+
132
+ // Validate results structure
133
+ const requiredResults = ['coverage_branch', 'mutation_score', 'tests_passed'];
134
+ const missingResults = requiredResults.filter(
135
+ (field) => typeof provenance.results[field] !== 'number'
136
+ );
137
+
138
+ if (missingResults.length > 0) {
139
+ return {
140
+ passed: false,
141
+ score: 0,
142
+ details: {},
143
+ errors: [`Missing numeric results: ${missingResults.join(', ')}`],
144
+ };
145
+ }
146
+
147
+ return {
148
+ passed: true,
149
+ score: 1,
150
+ details: {},
151
+ };
152
+ } catch (error) {
153
+ return {
154
+ passed: false,
155
+ score: 0,
156
+ details: {},
157
+ errors: [`Provenance validation failed: ${error}`],
158
+ };
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Validate a JSON file against a schema
164
+ */
165
+ validateJsonAgainstSchema(jsonPath: string, schemaPath: string): ValidationResult {
166
+ try {
167
+ // Read JSON file
168
+ const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
169
+ const jsonData = JSON.parse(jsonContent);
170
+
171
+ // Read schema file
172
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
173
+ const schema = JSON.parse(schemaContent);
174
+
175
+ // Validate
176
+ const validate = this.ajv.compile(schema);
177
+ const valid = validate(jsonData);
178
+
179
+ if (!valid) {
180
+ return {
181
+ passed: false,
182
+ score: 0,
183
+ details: {},
184
+ errors: validate.errors?.map((err) => `${err.instancePath}: ${err.message}`) || [],
185
+ };
186
+ }
187
+
188
+ return {
189
+ passed: true,
190
+ score: 1,
191
+ details: {},
192
+ };
193
+ } catch (error) {
194
+ return {
195
+ passed: false,
196
+ score: 0,
197
+ details: {},
198
+ errors: [`Schema validation failed: ${error}`],
199
+ };
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Validate a YAML file against a schema
205
+ */
206
+ validateYamlAgainstSchema(yamlPath: string, schemaPath: string): ValidationResult {
207
+ try {
208
+ // Read YAML file
209
+ const yamlContent = fs.readFileSync(yamlPath, 'utf-8');
210
+ const yamlData = yaml.load(yamlContent);
211
+
212
+ // Read schema file
213
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
214
+ const schema = JSON.parse(schemaContent);
215
+
216
+ // Validate
217
+ const validate = this.ajv.compile(schema);
218
+ const valid = validate(yamlData);
219
+
220
+ if (!valid) {
221
+ return {
222
+ passed: false,
223
+ score: 0,
224
+ details: {},
225
+ errors: validate.errors?.map((err) => `${err.instancePath}: ${err.message}`) || [],
226
+ };
227
+ }
228
+
229
+ return {
230
+ passed: true,
231
+ score: 1,
232
+ details: {},
233
+ };
234
+ } catch (error) {
235
+ return {
236
+ passed: false,
237
+ score: 0,
238
+ details: {},
239
+ errors: [`YAML schema validation failed: ${error}`],
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Validate file exists and is readable
246
+ */
247
+ validateFileExists(filePath: string): ValidationResult {
248
+ try {
249
+ if (!fs.existsSync(filePath)) {
250
+ return {
251
+ passed: false,
252
+ score: 0,
253
+ details: {},
254
+ errors: [`File not found: ${filePath}`],
255
+ };
256
+ }
257
+
258
+ // Try to read the file
259
+ fs.accessSync(filePath, fs.constants.R_OK);
260
+ return {
261
+ passed: true,
262
+ score: 1,
263
+ details: {},
264
+ };
265
+ } catch {
266
+ return {
267
+ passed: false,
268
+ score: 0,
269
+ details: {},
270
+ errors: [`File not readable: ${filePath}`],
271
+ };
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Validate directory exists and is writable
277
+ */
278
+ validateDirectoryExists(dirPath: string): ValidationResult {
279
+ try {
280
+ if (!fs.existsSync(dirPath)) {
281
+ return {
282
+ passed: false,
283
+ score: 0,
284
+ details: {},
285
+ errors: [`Directory not found: ${dirPath}`],
286
+ };
287
+ }
288
+
289
+ // Try to write to the directory
290
+ fs.accessSync(dirPath, fs.constants.W_OK);
291
+ return {
292
+ passed: true,
293
+ score: 1,
294
+ details: {},
295
+ };
296
+ } catch {
297
+ return {
298
+ passed: false,
299
+ score: 0,
300
+ details: {},
301
+ errors: [`Directory not writable: ${dirPath}`],
302
+ };
303
+ }
304
+ }
305
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * CAWS Waivers Manager
3
+ * TypeScript wrapper for waivers management functionality
4
+ *
5
+ * @author @darianrosebrook
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import yaml from 'js-yaml';
11
+ import { WaiverConfig } from './types.js';
12
+ import { CawsBaseTool } from './base-tool.js';
13
+
14
+ export class WaiversManager extends CawsBaseTool {
15
+ private readonly waiversPath: string;
16
+
17
+ constructor(waiversPath?: string) {
18
+ super();
19
+ this.waiversPath = waiversPath || path.join(this.getCawsDirectory(), 'waivers.yml');
20
+ }
21
+
22
+ /**
23
+ * Load waivers configuration
24
+ */
25
+ private loadWaiversConfig(): { waivers: WaiverConfig[] } {
26
+ try {
27
+ if (!fs.existsSync(this.waiversPath)) {
28
+ return { waivers: [] };
29
+ }
30
+
31
+ const content = fs.readFileSync(this.waiversPath, 'utf8');
32
+ return yaml.load(content) as { waivers: WaiverConfig[] };
33
+ } catch (error) {
34
+ this.logError(`Error loading waivers config: ${error}`);
35
+ return { waivers: [] };
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Save waivers configuration
41
+ */
42
+ private saveWaiversConfig(config: { waivers: WaiverConfig[] }): void {
43
+ try {
44
+ const yamlContent = yaml.dump(config, { indent: 2 });
45
+ fs.writeFileSync(this.waiversPath, yamlContent);
46
+ this.logSuccess(`Waivers configuration saved to ${this.waiversPath}`);
47
+ } catch (error) {
48
+ this.logError(`Error saving waivers config: ${error}`);
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Get all waivers for a specific gate
55
+ */
56
+ async getWaiversByGate(gate: string): Promise<WaiverConfig[]> {
57
+ const config = this.loadWaiversConfig();
58
+ const now = new Date();
59
+
60
+ return config.waivers.filter((waiver) => {
61
+ // Check if waiver covers this gate
62
+ if (waiver.gate !== gate) {
63
+ return false;
64
+ }
65
+
66
+ // Check if waiver is still active
67
+ const expiresAt = new Date(waiver.expiry);
68
+ if (now > expiresAt) {
69
+ return false;
70
+ }
71
+
72
+ return waiver.status === 'active';
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Check waiver status
78
+ */
79
+ async checkWaiverStatus(waiverId: string): Promise<{
80
+ active: boolean;
81
+ waiver?: WaiverConfig;
82
+ reason?: string;
83
+ }> {
84
+ const config = this.loadWaiversConfig();
85
+ const now = new Date();
86
+
87
+ const waiver = config.waivers.find((w) => w.created_at === waiverId);
88
+
89
+ if (!waiver) {
90
+ return { active: false, reason: 'Waiver not found' };
91
+ }
92
+
93
+ const expiresAt = new Date(waiver.expiry);
94
+ if (now > expiresAt) {
95
+ return { active: false, waiver, reason: 'Waiver expired' };
96
+ }
97
+
98
+ if (waiver.status !== 'active') {
99
+ return { active: false, waiver, reason: `Waiver status: ${waiver.status}` };
100
+ }
101
+
102
+ return { active: true, waiver };
103
+ }
104
+
105
+ /**
106
+ * Create a new waiver
107
+ */
108
+ async createWaiver(waiver: Omit<WaiverConfig, 'created_at'>): Promise<void> {
109
+ const config = this.loadWaiversConfig();
110
+
111
+ const newWaiver: WaiverConfig = {
112
+ ...waiver,
113
+ created_at: new Date().toISOString(),
114
+ };
115
+
116
+ config.waivers.push(newWaiver);
117
+ this.saveWaiversConfig(config);
118
+ }
119
+
120
+ /**
121
+ * Revoke a waiver
122
+ */
123
+ async revokeWaiver(gate: string, owner: string): Promise<void> {
124
+ const config = this.loadWaiversConfig();
125
+
126
+ const waiver = config.waivers.find(
127
+ (w) => w.gate === gate && w.owner === owner && w.status === 'active'
128
+ );
129
+
130
+ if (waiver) {
131
+ waiver.status = 'revoked';
132
+ this.saveWaiversConfig(config);
133
+ this.logSuccess(`Revoked waiver for gate: ${gate}`);
134
+ } else {
135
+ this.logWarning(`No active waiver found for gate: ${gate}`);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Cleanup expired waivers
141
+ */
142
+ async cleanupExpiredWaivers(): Promise<number> {
143
+ const config = this.loadWaiversConfig();
144
+ const now = new Date();
145
+
146
+ const activeWaivers = config.waivers.filter((waiver) => {
147
+ const expiresAt = new Date(waiver.expiry);
148
+ return now <= expiresAt;
149
+ });
150
+
151
+ const removedCount = config.waivers.length - activeWaivers.length;
152
+
153
+ if (removedCount > 0) {
154
+ config.waivers = activeWaivers;
155
+ this.saveWaiversConfig(config);
156
+ this.logSuccess(`Cleaned up ${removedCount} expired waiver(s)`);
157
+ }
158
+
159
+ return removedCount;
160
+ }
161
+
162
+ /**
163
+ * List all active waivers
164
+ */
165
+ async listActiveWaivers(): Promise<WaiverConfig[]> {
166
+ const config = this.loadWaiversConfig();
167
+ const now = new Date();
168
+
169
+ return config.waivers.filter((waiver) => {
170
+ const expiresAt = new Date(waiver.expiry);
171
+ return now <= expiresAt && waiver.status === 'active';
172
+ });
173
+ }
174
+ }