@paths.design/caws-cli 2.0.1 → 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 -2
  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 @@
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
+ };