@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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -96
- package/package.json +3 -3
- package/templates/agents.md +820 -0
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +331 -0
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +360 -0
- package/templates/apps/tools/caws/README.md +463 -0
- package/templates/apps/tools/caws/TEST_STATUS.md +365 -0
- package/templates/apps/tools/caws/attest.js +357 -0
- package/templates/apps/tools/caws/ci-optimizer.js +642 -0
- package/templates/apps/tools/caws/config.ts +245 -0
- package/templates/apps/tools/caws/cross-functional.js +876 -0
- package/templates/apps/tools/caws/dashboard.js +1112 -0
- package/templates/apps/tools/caws/flake-detector.ts +362 -0
- package/templates/apps/tools/caws/gates.js +198 -0
- package/templates/apps/tools/caws/gates.ts +237 -0
- package/templates/apps/tools/caws/language-adapters.ts +381 -0
- package/templates/apps/tools/caws/language-support.d.ts +367 -0
- package/templates/apps/tools/caws/language-support.d.ts.map +1 -0
- package/templates/apps/tools/caws/language-support.js +585 -0
- package/templates/apps/tools/caws/legacy-assessment.ts +408 -0
- package/templates/apps/tools/caws/legacy-assessor.js +764 -0
- package/templates/apps/tools/caws/mutant-analyzer.js +734 -0
- package/templates/apps/tools/caws/perf-budgets.ts +349 -0
- package/templates/apps/tools/caws/property-testing.js +707 -0
- package/templates/apps/tools/caws/provenance.d.ts +14 -0
- package/templates/apps/tools/caws/provenance.d.ts.map +1 -0
- package/templates/apps/tools/caws/provenance.js +132 -0
- package/templates/apps/tools/caws/provenance.ts +211 -0
- package/templates/apps/tools/caws/schemas/waivers.schema.json +30 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +115 -0
- package/templates/apps/tools/caws/scope-guard.js +208 -0
- package/templates/apps/tools/caws/security-provenance.ts +483 -0
- package/templates/apps/tools/caws/shared/base-tool.ts +281 -0
- package/templates/apps/tools/caws/shared/config-manager.ts +366 -0
- package/templates/apps/tools/caws/shared/gate-checker.ts +597 -0
- package/templates/apps/tools/caws/shared/types.ts +444 -0
- package/templates/apps/tools/caws/shared/validator.ts +305 -0
- package/templates/apps/tools/caws/shared/waivers-manager.ts +174 -0
- package/templates/apps/tools/caws/spec-test-mapper.ts +391 -0
- package/templates/apps/tools/caws/templates/working-spec.template.yml +60 -0
- package/templates/apps/tools/caws/test-quality.js +578 -0
- package/templates/apps/tools/caws/tools-allow.json +331 -0
- package/templates/apps/tools/caws/validate.js +76 -0
- package/templates/apps/tools/caws/validate.ts +228 -0
- package/templates/apps/tools/caws/waivers.js +344 -0
- package/templates/apps/tools/caws/waivers.yml +19 -0
- package/templates/codemod/README.md +1 -0
- package/templates/codemod/test.js +1 -0
- package/templates/docs/README.md +150 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["provenance.js"],"names":[],"mappings":";AAYA;;;;GAIG;AACH,uDAkCC;AAED;;;;GAIG;AACH,0DAFW,MAAM,QAShB"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CAWS Provenance Tool
|
|
3
|
+
* @author @darianrosebrook
|
|
4
|
+
*
|
|
5
|
+
* Note: For enhanced TypeScript version with better error handling, use provenance.ts
|
|
6
|
+
* This .js version provides basic provenance for backward compatibility
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generates provenance information for a CAWS project
|
|
11
|
+
* @returns {Object} Provenance data with metadata and artifacts
|
|
12
|
+
*/
|
|
13
|
+
function generateProvenance() {
|
|
14
|
+
try {
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
// Check if we're in a CAWS project
|
|
19
|
+
if (!fs.existsSync('.caws')) {
|
|
20
|
+
throw new Error('Not in a CAWS project directory');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const workingSpecPath = '.caws/working-spec.yaml';
|
|
24
|
+
if (!fs.existsSync(workingSpecPath)) {
|
|
25
|
+
throw new Error('Working specification file not found');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Load working spec
|
|
29
|
+
const yaml = require('js-yaml');
|
|
30
|
+
const specContent = fs.readFileSync(workingSpecPath, 'utf8');
|
|
31
|
+
const spec = yaml.load(specContent);
|
|
32
|
+
|
|
33
|
+
// Generate provenance data
|
|
34
|
+
const provenance = {
|
|
35
|
+
agent: 'caws-cli',
|
|
36
|
+
model: 'cli-interactive',
|
|
37
|
+
modelHash: (() => {
|
|
38
|
+
try {
|
|
39
|
+
return require('../../../package.json').version || '1.0.0';
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return '1.0.0'; // Fallback version if package.json not found
|
|
42
|
+
}
|
|
43
|
+
})(),
|
|
44
|
+
toolAllowlist: [
|
|
45
|
+
'node',
|
|
46
|
+
'npm',
|
|
47
|
+
'git',
|
|
48
|
+
'fs-extra',
|
|
49
|
+
'inquirer',
|
|
50
|
+
'commander',
|
|
51
|
+
'js-yaml',
|
|
52
|
+
'ajv',
|
|
53
|
+
'chalk',
|
|
54
|
+
],
|
|
55
|
+
artifacts: ['.caws/working-spec.yaml'],
|
|
56
|
+
results: {
|
|
57
|
+
project_id: spec.id,
|
|
58
|
+
project_title: spec.title,
|
|
59
|
+
risk_tier: spec.risk_tier,
|
|
60
|
+
mode: spec.mode,
|
|
61
|
+
change_budget: spec.change_budget,
|
|
62
|
+
blast_radius: spec.blast_radius,
|
|
63
|
+
operational_rollback_slo: spec.operational_rollback_slo,
|
|
64
|
+
},
|
|
65
|
+
approvals: [],
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
version: '1.0.0',
|
|
68
|
+
hash: '', // Will be calculated below
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Calculate hash
|
|
72
|
+
provenance.hash = crypto
|
|
73
|
+
.createHash('sha256')
|
|
74
|
+
.update(JSON.stringify(provenance, Object.keys(provenance).sort()))
|
|
75
|
+
.digest('hex');
|
|
76
|
+
|
|
77
|
+
return provenance;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(`Provenance generation failed: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Saves provenance data to a file
|
|
85
|
+
* @param {Object} provenance - Provenance data to save
|
|
86
|
+
* @param {string} outputPath - Path where to save the provenance file
|
|
87
|
+
*/
|
|
88
|
+
function saveProvenance(provenance, outputPath) {
|
|
89
|
+
try {
|
|
90
|
+
const fs = require('fs');
|
|
91
|
+
const path = require('path');
|
|
92
|
+
|
|
93
|
+
// Ensure directory exists
|
|
94
|
+
const dir = path.dirname(outputPath);
|
|
95
|
+
if (!fs.existsSync(dir)) {
|
|
96
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Save provenance
|
|
100
|
+
fs.writeFileSync(outputPath, JSON.stringify(provenance, null, 2));
|
|
101
|
+
console.log(`✅ Provenance saved to ${outputPath}`);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw new Error(`Failed to save provenance: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle direct script execution
|
|
108
|
+
if (require.main === module) {
|
|
109
|
+
const command = process.argv[2];
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
if (command === 'generate') {
|
|
113
|
+
const provenance = generateProvenance();
|
|
114
|
+
const outputPath = process.argv[3] || '.agent/provenance.json';
|
|
115
|
+
saveProvenance(provenance, outputPath);
|
|
116
|
+
console.log('✅ Provenance generated successfully');
|
|
117
|
+
} else {
|
|
118
|
+
console.log('CAWS Provenance Tool');
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log('Usage:');
|
|
121
|
+
console.log(' node provenance.js generate [output-path]');
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log('Note: For enhanced features, use: npx tsx provenance.ts');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`❌ Error: ${error.message}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { generateProvenance, saveProvenance };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CAWS Provenance Tool
|
|
5
|
+
* Enhanced provenance generation with metadata and hashing
|
|
6
|
+
*
|
|
7
|
+
* @author @darianrosebrook
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as crypto from 'crypto';
|
|
13
|
+
import * as yaml from 'js-yaml';
|
|
14
|
+
import { CawsBaseTool } from './shared/base-tool.js';
|
|
15
|
+
|
|
16
|
+
interface ProvenanceData {
|
|
17
|
+
agent: string;
|
|
18
|
+
model: string;
|
|
19
|
+
modelHash: string;
|
|
20
|
+
toolAllowlist: string[];
|
|
21
|
+
artifacts: string[];
|
|
22
|
+
results: Record<string, any>;
|
|
23
|
+
approvals: string[];
|
|
24
|
+
timestamp: string;
|
|
25
|
+
version: string;
|
|
26
|
+
hash: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class ProvenanceCLI extends CawsBaseTool {
|
|
30
|
+
/**
|
|
31
|
+
* Generate provenance information for a CAWS project
|
|
32
|
+
*/
|
|
33
|
+
generateProvenance(): ProvenanceData {
|
|
34
|
+
try {
|
|
35
|
+
// Check if we're in a CAWS project
|
|
36
|
+
if (!this.pathExists('.caws')) {
|
|
37
|
+
throw new Error('Not in a CAWS project directory');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const workingSpecPath = '.caws/working-spec.yaml';
|
|
41
|
+
if (!this.pathExists(workingSpecPath)) {
|
|
42
|
+
throw new Error('Working specification file not found');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Load working spec
|
|
46
|
+
const specContent = fs.readFileSync(workingSpecPath, 'utf8');
|
|
47
|
+
const spec = yaml.load(specContent) as any;
|
|
48
|
+
|
|
49
|
+
// Load package.json for version
|
|
50
|
+
let version = '1.0.0';
|
|
51
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
52
|
+
if (this.pathExists(packageJsonPath)) {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
54
|
+
version = pkg.version || version;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate provenance data
|
|
58
|
+
const provenance: ProvenanceData = {
|
|
59
|
+
agent: 'caws-cli',
|
|
60
|
+
model: process.env.CAWS_MODEL || 'cli-interactive',
|
|
61
|
+
modelHash: version,
|
|
62
|
+
toolAllowlist: [
|
|
63
|
+
'node',
|
|
64
|
+
'npm',
|
|
65
|
+
'git',
|
|
66
|
+
'fs-extra',
|
|
67
|
+
'inquirer',
|
|
68
|
+
'commander',
|
|
69
|
+
'js-yaml',
|
|
70
|
+
'ajv',
|
|
71
|
+
'chalk',
|
|
72
|
+
'tsx',
|
|
73
|
+
'typescript',
|
|
74
|
+
],
|
|
75
|
+
artifacts: ['.caws/working-spec.yaml'],
|
|
76
|
+
results: {
|
|
77
|
+
project_id: spec.id || 'unknown',
|
|
78
|
+
project_title: spec.title || 'Unknown Project',
|
|
79
|
+
risk_tier: spec.risk_tier || 3,
|
|
80
|
+
mode: spec.mode || 'standard',
|
|
81
|
+
change_budget: spec.change_budget,
|
|
82
|
+
blast_radius: spec.blast_radius,
|
|
83
|
+
operational_rollback_slo: spec.operational_rollback_slo,
|
|
84
|
+
acceptance_criteria_count: spec.acceptance?.length || 0,
|
|
85
|
+
contracts_count: spec.contracts?.length || 0,
|
|
86
|
+
},
|
|
87
|
+
approvals: spec.approvals || [],
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
version: '1.0.0',
|
|
90
|
+
hash: '', // Will be calculated below
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Calculate hash
|
|
94
|
+
const hashContent = JSON.stringify(provenance, Object.keys(provenance).sort());
|
|
95
|
+
provenance.hash = crypto.createHash('sha256').update(hashContent).digest('hex');
|
|
96
|
+
|
|
97
|
+
return provenance;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw new Error(`Provenance generation failed: ${error}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Save provenance data to a file
|
|
105
|
+
*/
|
|
106
|
+
saveProvenance(provenance: ProvenanceData, outputPath: string): void {
|
|
107
|
+
try {
|
|
108
|
+
// Ensure directory exists
|
|
109
|
+
const dir = path.dirname(outputPath);
|
|
110
|
+
if (!this.pathExists(dir)) {
|
|
111
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Save provenance
|
|
115
|
+
fs.writeFileSync(outputPath, JSON.stringify(provenance, null, 2));
|
|
116
|
+
this.logSuccess(`Provenance saved to ${outputPath}`);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new Error(`Failed to save provenance: ${error}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Display provenance information
|
|
124
|
+
*/
|
|
125
|
+
displayProvenance(provenance: ProvenanceData): void {
|
|
126
|
+
console.log('\n📋 CAWS Provenance');
|
|
127
|
+
console.log('='.repeat(50));
|
|
128
|
+
console.log(`Agent: ${provenance.agent}`);
|
|
129
|
+
console.log(`Model: ${provenance.model}`);
|
|
130
|
+
console.log(`Version: ${provenance.version}`);
|
|
131
|
+
console.log(`Timestamp: ${provenance.timestamp}`);
|
|
132
|
+
console.log(`Hash: ${provenance.hash.substring(0, 16)}...`);
|
|
133
|
+
|
|
134
|
+
console.log('\n📊 Project Results:');
|
|
135
|
+
Object.entries(provenance.results).forEach(([key, value]) => {
|
|
136
|
+
if (value !== undefined && value !== null) {
|
|
137
|
+
console.log(` ${key}: ${value}`);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
console.log('\n🔧 Tool Allowlist:');
|
|
142
|
+
provenance.toolAllowlist.slice(0, 5).forEach((tool) => {
|
|
143
|
+
console.log(` - ${tool}`);
|
|
144
|
+
});
|
|
145
|
+
if (provenance.toolAllowlist.length > 5) {
|
|
146
|
+
console.log(` ... and ${provenance.toolAllowlist.length - 5} more`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log('\n📦 Artifacts:');
|
|
150
|
+
provenance.artifacts.forEach((artifact) => {
|
|
151
|
+
console.log(` - ${artifact}`);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (provenance.approvals.length > 0) {
|
|
155
|
+
console.log('\n✅ Approvals:');
|
|
156
|
+
provenance.approvals.forEach((approval) => {
|
|
157
|
+
console.log(` - ${approval}`);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('='.repeat(50));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Main CLI handler
|
|
166
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
167
|
+
const command = process.argv[2];
|
|
168
|
+
const cli = new ProvenanceCLI();
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
switch (command) {
|
|
172
|
+
case 'generate': {
|
|
173
|
+
const provenance = cli.generateProvenance();
|
|
174
|
+
const outputPath = process.argv[3] || '.agent/provenance.json';
|
|
175
|
+
cli.saveProvenance(provenance, outputPath);
|
|
176
|
+
cli.displayProvenance(provenance);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case 'show': {
|
|
181
|
+
const filePath = process.argv[3] || '.agent/provenance.json';
|
|
182
|
+
if (!cli.pathExists(filePath)) {
|
|
183
|
+
console.error(`❌ Provenance file not found: ${filePath}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
188
|
+
const provenance = JSON.parse(content) as ProvenanceData;
|
|
189
|
+
cli.displayProvenance(provenance);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
default:
|
|
194
|
+
console.log('CAWS Provenance Tool');
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log('Commands:');
|
|
197
|
+
console.log(' generate [output] - Generate and save provenance data');
|
|
198
|
+
console.log(' show [file] - Display provenance from file');
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log('Examples:');
|
|
201
|
+
console.log(' provenance.ts generate .agent/provenance.json');
|
|
202
|
+
console.log(' provenance.ts show .agent/provenance.json');
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`❌ Error: ${error}`);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export { ProvenanceCLI };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "CAWS Waivers Configuration",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"patternProperties": {
|
|
6
|
+
".*": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": ["gate", "reason", "owner", "expiry"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"gate": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"enum": ["coverage", "mutation", "contracts", "a11y", "perf", "security"]
|
|
13
|
+
},
|
|
14
|
+
"reason": { "type": "string", "minLength": 10 },
|
|
15
|
+
"owner": { "type": "string" },
|
|
16
|
+
"expiry": { "type": "string", "format": "date-time" },
|
|
17
|
+
"compensating_control": { "type": "string" },
|
|
18
|
+
"ticket_url": { "type": "string", "format": "uri" },
|
|
19
|
+
"approved_by": { "type": "string" },
|
|
20
|
+
"created_at": { "type": "string", "format": "date-time" },
|
|
21
|
+
"status": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": ["active", "expired", "revoked"],
|
|
24
|
+
"default": "active"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"additionalProperties": false
|
|
30
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "CAWS Working Spec",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"id",
|
|
7
|
+
"title",
|
|
8
|
+
"risk_tier",
|
|
9
|
+
"scope",
|
|
10
|
+
"invariants",
|
|
11
|
+
"acceptance",
|
|
12
|
+
"non_functional",
|
|
13
|
+
"contracts"
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"id": { "type": "string", "pattern": "^[A-Z]+-\\d+$" },
|
|
17
|
+
"title": { "type": "string", "minLength": 8 },
|
|
18
|
+
"risk_tier": { "type": "integer", "enum": [1, 2, 3] },
|
|
19
|
+
"scope": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["in", "out"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"in": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
|
|
24
|
+
"out": { "type": "array", "items": { "type": "string" } }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"invariants": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
|
|
28
|
+
"acceptance": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"minItems": 1,
|
|
31
|
+
"items": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"required": ["id", "given", "when", "then"],
|
|
34
|
+
"properties": {
|
|
35
|
+
"id": { "type": "string", "pattern": "^A\\d+$" },
|
|
36
|
+
"given": { "type": "string" },
|
|
37
|
+
"when": { "type": "string" },
|
|
38
|
+
"then": { "type": "string" }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"non_functional": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"a11y": { "type": "array", "items": { "type": "string" } },
|
|
46
|
+
"perf": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"api_p95_ms": { "type": "integer", "minimum": 1 },
|
|
50
|
+
"lcp_ms": { "type": "integer", "minimum": 1 }
|
|
51
|
+
},
|
|
52
|
+
"additionalProperties": false
|
|
53
|
+
},
|
|
54
|
+
"security": { "type": "array", "items": { "type": "string" } }
|
|
55
|
+
},
|
|
56
|
+
"additionalProperties": false
|
|
57
|
+
},
|
|
58
|
+
"contracts": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"minItems": 1,
|
|
61
|
+
"items": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": ["type", "path"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"type": { "type": "string", "enum": ["openapi", "graphql", "proto", "pact"] },
|
|
66
|
+
"path": { "type": "string" }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"observability": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {
|
|
73
|
+
"logs": { "type": "array", "items": { "type": "string" } },
|
|
74
|
+
"metrics": { "type": "array", "items": { "type": "string" } },
|
|
75
|
+
"traces": { "type": "array", "items": { "type": "string" } }
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"migrations": { "type": "array", "items": { "type": "string" } },
|
|
79
|
+
"rollback": { "type": "array", "items": { "type": "string" } },
|
|
80
|
+
"experiment_mode": {
|
|
81
|
+
"type": "boolean",
|
|
82
|
+
"description": "Enables experimental mode with reduced requirements"
|
|
83
|
+
},
|
|
84
|
+
"timeboxed_hours": {
|
|
85
|
+
"type": "integer",
|
|
86
|
+
"minimum": 1,
|
|
87
|
+
"description": "Time limit for experimental features in hours"
|
|
88
|
+
},
|
|
89
|
+
"human_override": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"properties": {
|
|
92
|
+
"approved_by": { "type": "string" },
|
|
93
|
+
"reason": { "type": "string" },
|
|
94
|
+
"waived_requirements": {
|
|
95
|
+
"type": "array",
|
|
96
|
+
"items": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"enum": ["mutation_testing", "contract_tests", "coverage", "manual_review"]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"expiry_date": { "type": "string", "format": "date-time" }
|
|
102
|
+
},
|
|
103
|
+
"required": ["approved_by", "reason"]
|
|
104
|
+
},
|
|
105
|
+
"ai_assessment": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"properties": {
|
|
108
|
+
"confidence_level": { "type": "integer", "minimum": 1, "maximum": 10 },
|
|
109
|
+
"uncertainty_areas": { "type": "array", "items": { "type": "string" } },
|
|
110
|
+
"recommended_pairing": { "type": "boolean" }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"additionalProperties": false
|
|
115
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview CAWS Scope Guard
|
|
5
|
+
* Enforces that experimental code stays within designated sandbox areas
|
|
6
|
+
* @author @darianrosebrook
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if experimental code is properly contained
|
|
14
|
+
* @param {string} workingSpecPath - Path to working spec file
|
|
15
|
+
* @returns {Object} Scope validation results
|
|
16
|
+
*/
|
|
17
|
+
function checkExperimentalContainment(workingSpecPath = '.caws/working-spec.yaml') {
|
|
18
|
+
try {
|
|
19
|
+
if (!fs.existsSync(workingSpecPath)) {
|
|
20
|
+
console.error('❌ Working spec not found:', workingSpecPath);
|
|
21
|
+
return { valid: false, errors: ['Working spec not found'] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const yaml = require('js-yaml');
|
|
25
|
+
const spec = yaml.load(fs.readFileSync(workingSpecPath, 'utf8'));
|
|
26
|
+
|
|
27
|
+
const results = {
|
|
28
|
+
valid: true,
|
|
29
|
+
errors: [],
|
|
30
|
+
warnings: [],
|
|
31
|
+
experimentalFiles: [],
|
|
32
|
+
nonExperimentalFiles: [],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Only check if experimental mode is enabled
|
|
36
|
+
if (!spec.experimental_mode?.enabled) {
|
|
37
|
+
console.log('ℹ️ Experimental mode not enabled - skipping containment check');
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const sandboxLocation = spec.experimental_mode.sandbox_location || 'experimental/';
|
|
42
|
+
console.log(`🔍 Checking containment for experimental code in: ${sandboxLocation}`);
|
|
43
|
+
|
|
44
|
+
// Get list of changed files (this would typically come from git diff)
|
|
45
|
+
const changedFiles = getChangedFiles();
|
|
46
|
+
|
|
47
|
+
if (changedFiles.length === 0) {
|
|
48
|
+
console.log('ℹ️ No files changed - skipping scope check');
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check each changed file
|
|
53
|
+
changedFiles.forEach((file) => {
|
|
54
|
+
const isInSandbox =
|
|
55
|
+
file.startsWith(sandboxLocation) ||
|
|
56
|
+
file.includes(`/${sandboxLocation}`) ||
|
|
57
|
+
file.includes(sandboxLocation);
|
|
58
|
+
|
|
59
|
+
if (isInSandbox) {
|
|
60
|
+
results.experimentalFiles.push(file);
|
|
61
|
+
console.log(`✅ Experimental file properly contained: ${file}`);
|
|
62
|
+
} else {
|
|
63
|
+
results.nonExperimentalFiles.push(file);
|
|
64
|
+
results.valid = false;
|
|
65
|
+
results.errors.push(`Experimental code found outside sandbox: ${file}`);
|
|
66
|
+
console.error(`❌ Experimental code outside sandbox: ${file}`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Check if experimental files actually exist
|
|
71
|
+
results.experimentalFiles.forEach((file) => {
|
|
72
|
+
if (!fs.existsSync(file)) {
|
|
73
|
+
results.warnings.push(`Experimental file not found (may have been deleted): ${file}`);
|
|
74
|
+
console.warn(`⚠️ Experimental file not found: ${file}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return results;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('❌ Error checking experimental containment:', error.message);
|
|
81
|
+
return { valid: false, errors: [error.message] };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get list of changed files from git
|
|
87
|
+
* @returns {Array} List of changed file paths
|
|
88
|
+
*/
|
|
89
|
+
function getChangedFiles() {
|
|
90
|
+
try {
|
|
91
|
+
// Get files that are staged or modified
|
|
92
|
+
const staged = execSync('git diff --cached --name-only', { encoding: 'utf8' })
|
|
93
|
+
.split('\n')
|
|
94
|
+
.filter((file) => file.trim());
|
|
95
|
+
|
|
96
|
+
const modified = execSync('git diff --name-only', { encoding: 'utf8' })
|
|
97
|
+
.split('\n')
|
|
98
|
+
.filter((file) => file.trim());
|
|
99
|
+
|
|
100
|
+
// Combine and deduplicate
|
|
101
|
+
const allFiles = [...new Set([...staged, ...modified])];
|
|
102
|
+
|
|
103
|
+
// Filter out deleted files (they might still be in the diff)
|
|
104
|
+
return allFiles.filter((file) => {
|
|
105
|
+
try {
|
|
106
|
+
return fs.existsSync(file);
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.warn('⚠️ Could not get changed files from git:', error.message);
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Validate that experimental code follows containment rules
|
|
119
|
+
* @param {string} workingSpecPath - Path to working spec file
|
|
120
|
+
*/
|
|
121
|
+
function validateExperimentalScope(workingSpecPath = '.caws/working-spec.yaml') {
|
|
122
|
+
console.log('🔍 Validating experimental code containment...');
|
|
123
|
+
|
|
124
|
+
const results = checkExperimentalContainment(workingSpecPath);
|
|
125
|
+
|
|
126
|
+
if (!results.valid) {
|
|
127
|
+
console.error('\n❌ Experimental containment validation failed:');
|
|
128
|
+
results.errors.forEach((error) => {
|
|
129
|
+
console.error(` - ${error}`);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (results.warnings.length > 0) {
|
|
133
|
+
console.warn('\n⚠️ Warnings:');
|
|
134
|
+
results.warnings.forEach((warning) => {
|
|
135
|
+
console.warn(` - ${warning}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.error('\n💡 To fix containment issues:');
|
|
140
|
+
console.error(' 1. Move experimental code to the designated sandbox location');
|
|
141
|
+
console.error(' 2. Update the sandbox_location in your working spec');
|
|
142
|
+
console.error(' 3. Or disable experimental mode if this is production code');
|
|
143
|
+
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (results.warnings.length > 0) {
|
|
148
|
+
console.warn('\n⚠️ Experimental containment warnings:');
|
|
149
|
+
results.warnings.forEach((warning) => {
|
|
150
|
+
console.warn(` - ${warning}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log('✅ Experimental code containment validated');
|
|
155
|
+
console.log(` - Files in sandbox: ${results.experimentalFiles.length}`);
|
|
156
|
+
console.log(` - Files outside sandbox: ${results.nonExperimentalFiles.length}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// CLI interface
|
|
160
|
+
if (require.main === module) {
|
|
161
|
+
const command = process.argv[2];
|
|
162
|
+
const specPath = process.argv[3] || '.caws/working-spec.yaml';
|
|
163
|
+
|
|
164
|
+
switch (command) {
|
|
165
|
+
case 'validate':
|
|
166
|
+
validateExperimentalScope(specPath);
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
case 'check':
|
|
170
|
+
const results = checkExperimentalContainment(specPath);
|
|
171
|
+
console.log('\n📊 Containment Check Results:');
|
|
172
|
+
console.log(` Valid: ${results.valid}`);
|
|
173
|
+
console.log(` Experimental files: ${results.experimentalFiles.length}`);
|
|
174
|
+
console.log(` Non-experimental files: ${results.nonExperimentalFiles.length}`);
|
|
175
|
+
console.log(` Errors: ${results.errors.length}`);
|
|
176
|
+
console.log(` Warnings: ${results.warnings.length}`);
|
|
177
|
+
|
|
178
|
+
if (results.errors.length > 0) {
|
|
179
|
+
console.log('\n❌ Errors:');
|
|
180
|
+
results.errors.forEach((error) => console.log(` - ${error}`));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (results.warnings.length > 0) {
|
|
184
|
+
console.log('\n⚠️ Warnings:');
|
|
185
|
+
results.warnings.forEach((warning) => console.log(` - ${warning}`));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
process.exit(results.valid ? 0 : 1);
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
default:
|
|
192
|
+
console.log('CAWS Scope Guard');
|
|
193
|
+
console.log('Usage:');
|
|
194
|
+
console.log(' node scope-guard.js validate [spec-path]');
|
|
195
|
+
console.log(' node scope-guard.js check [spec-path]');
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log('Examples:');
|
|
198
|
+
console.log(' node scope-guard.js validate');
|
|
199
|
+
console.log(' node scope-guard.js check .caws/working-spec.yaml');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
checkExperimentalContainment,
|
|
206
|
+
validateExperimentalScope,
|
|
207
|
+
getChangedFiles,
|
|
208
|
+
};
|