@paths.design/caws-cli 3.1.1 → 3.2.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.
@@ -1,4711 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * @fileoverview CAWS CLI - Scaffolding tool for Coding Agent Workflow System
5
- * Provides commands to initialize new projects and scaffold existing ones with CAWS
6
- * @author @darianrosebrook
7
- */
8
-
9
- const { Command } = require('commander');
10
- const fs = require('fs-extra');
11
- const path = require('path');
12
- const yaml = require('js-yaml');
13
- const chalk = require('chalk');
14
-
15
- // Import configuration and utilities
16
- const {
17
- CLI_VERSION,
18
- initializeGlobalSetup,
19
- loadProvenanceTools,
20
- initializeLanguageSupport,
21
- getGlobalCAWSSetup,
22
- } = require('./config');
23
-
24
- // Import command handlers
25
- const { initProject } = require('./commands/init');
26
-
27
- // Import scaffold functionality
28
- const { scaffoldProject, setScaffoldDependencies } = require('./scaffold');
29
-
30
- // Import validation functionality
31
- const { validateWorkingSpecWithSuggestions } = require('./validation/spec-validation');
32
-
33
- // Import finalization utilities
34
- const { finalizeProject, continueToSuccess, setFinalizationDependencies } = require('./utils/finalization');
35
-
36
- // Import generators
37
- const { generateWorkingSpec, validateGeneratedSpec } = require('./generators/working-spec');
38
-
39
- // Import tool system
40
- const ToolLoader = require('./tool-loader');
41
- const ToolValidator = require('./tool-validator');
42
-
43
- // Initialize global configuration
44
- const program = new Command();
45
-
46
- // Initialize global state
47
- const cawsSetup = initializeGlobalSetup();
48
- const languageSupport = initializeLanguageSupport();
49
-
50
- // Set up dependencies for modules that need them
51
- setScaffoldDependencies({
52
- cawsSetup,
53
- loadProvenanceTools,
54
- });
55
-
56
- setFinalizationDependencies({
57
- languageSupport,
58
- loadProvenanceTools,
59
- });
60
-
61
- // Tool system state
62
- let toolLoader = null;
63
- let toolValidator = null;
64
-
65
- // CAWS Detection and Configuration
66
- function detectCAWSSetup(cwd = process.cwd()) {
67
- // Skip logging for version/help commands
68
- const isQuietCommand =
69
- process.argv.includes('--version') ||
70
- process.argv.includes('-V') ||
71
- process.argv.includes('--help');
72
-
73
- if (!isQuietCommand) {
74
- console.log(chalk.blue('🔍 Detecting CAWS setup...'));
75
- }
76
-
77
- // Check for existing CAWS setup
78
- const cawsDir = path.join(cwd, '.caws');
79
- const hasCAWSDir = fs.existsSync(cawsDir);
80
-
81
- if (!hasCAWSDir) {
82
- if (!isQuietCommand) {
83
- console.log(chalk.gray('â„šī¸ No .caws directory found - new project setup'));
84
- }
85
- return {
86
- type: 'new',
87
- hasCAWSDir: false,
88
- cawsDir: null,
89
- capabilities: [],
90
- hasTemplateDir: false,
91
- templateDir: null,
92
- };
93
- }
94
-
95
- // Analyze existing setup
96
- const files = fs.readdirSync(cawsDir);
97
- const hasWorkingSpec = fs.existsSync(path.join(cawsDir, 'working-spec.yaml'));
98
- const hasValidateScript = fs.existsSync(path.join(cawsDir, 'validate.js'));
99
- const hasPolicy = fs.existsSync(path.join(cawsDir, 'policy'));
100
- const hasSchemas = fs.existsSync(path.join(cawsDir, 'schemas'));
101
- const hasTemplates = fs.existsSync(path.join(cawsDir, 'templates'));
102
-
103
- // Check for multiple spec files (enhanced project pattern)
104
- const specFiles = files.filter((f) => f.endsWith('-spec.yaml'));
105
- const hasMultipleSpecs = specFiles.length > 1;
106
-
107
- // Check for tools directory (enhanced setup)
108
- const toolsDir = path.join(cwd, 'apps/tools/caws');
109
- const hasTools = fs.existsSync(toolsDir);
110
-
111
- // Determine setup type
112
- let setupType = 'basic';
113
- let capabilities = [];
114
-
115
- if (hasMultipleSpecs && hasWorkingSpec) {
116
- setupType = 'enhanced';
117
- capabilities.push('multiple-specs', 'working-spec', 'domain-specific');
118
- } else if (hasWorkingSpec) {
119
- setupType = 'standard';
120
- capabilities.push('working-spec');
121
- }
122
-
123
- if (hasValidateScript) {
124
- capabilities.push('validation');
125
- }
126
- if (hasPolicy) {
127
- capabilities.push('policies');
128
- }
129
- if (hasSchemas) {
130
- capabilities.push('schemas');
131
- }
132
- if (hasTemplates) {
133
- capabilities.push('templates');
134
- }
135
- if (hasTools) {
136
- capabilities.push('tools');
137
- }
138
-
139
- if (!isQuietCommand) {
140
- console.log(chalk.green(`✅ Detected ${setupType} CAWS setup`));
141
- console.log(chalk.gray(` Capabilities: ${capabilities.join(', ')}`));
142
- }
143
-
144
- // Check for template directory - try multiple possible locations
145
- let templateDir = null;
146
- const possibleTemplatePaths = [
147
- // FIRST: Try bundled templates (for npm-installed CLI)
148
- { path: path.resolve(__dirname, '../templates'), source: 'bundled with CLI' },
149
- { path: path.resolve(__dirname, 'templates'), source: 'bundled with CLI (fallback)' },
150
- // Try relative to current working directory (for monorepo setups)
151
- { path: path.resolve(cwd, '../caws-template'), source: 'monorepo parent directory' },
152
- { path: path.resolve(cwd, '../../caws-template'), source: 'monorepo grandparent' },
153
- { path: path.resolve(cwd, '../../../caws-template'), source: 'workspace root' },
154
- { path: path.resolve(cwd, 'packages/caws-template'), source: 'packages/ subdirectory' },
155
- { path: path.resolve(cwd, 'caws-template'), source: 'caws-template/ subdirectory' },
156
- // Try relative to CLI location (for installed CLI)
157
- { path: path.resolve(__dirname, '../caws-template'), source: 'CLI installation' },
158
- { path: path.resolve(__dirname, '../../caws-template'), source: 'CLI parent directory' },
159
- { path: path.resolve(__dirname, '../../../caws-template'), source: 'CLI workspace root' },
160
- // Try absolute paths for CI environments
161
- { path: path.resolve(process.cwd(), 'packages/caws-template'), source: 'current packages/' },
162
- { path: path.resolve(process.cwd(), '../packages/caws-template'), source: 'parent packages/' },
163
- {
164
- path: path.resolve(process.cwd(), '../../packages/caws-template'),
165
- source: 'grandparent packages/',
166
- },
167
- {
168
- path: path.resolve(process.cwd(), '../../../packages/caws-template'),
169
- source: 'workspace packages/',
170
- },
171
- // Try from workspace root
172
- { path: path.resolve(process.cwd(), 'caws-template'), source: 'workspace caws-template/' },
173
- // Try various other common locations
174
- {
175
- path: '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
176
- source: 'GitHub Actions CI',
177
- },
178
- { path: '/workspace/packages/caws-template', source: 'Docker workspace' },
179
- { path: '/caws/packages/caws-template', source: 'Container workspace' },
180
- ];
181
-
182
- for (const { path: testPath, source } of possibleTemplatePaths) {
183
- if (fs.existsSync(testPath)) {
184
- templateDir = testPath;
185
- if (!isQuietCommand) {
186
- console.log(`✅ Found CAWS templates in ${source}:`);
187
- console.log(` ${chalk.gray(testPath)}`);
188
- }
189
- break;
190
- }
191
- }
192
-
193
- if (!templateDir && !isQuietCommand) {
194
- console.warn(chalk.yellow('âš ī¸ CAWS templates not found in standard locations'));
195
- console.warn(chalk.blue('💡 This may limit available scaffolding features'));
196
- console.warn(
197
- chalk.blue('💡 For full functionality, ensure caws-template package is available')
198
- );
199
- }
200
-
201
- const hasTemplateDir = templateDir !== null;
202
-
203
- return {
204
- type: setupType,
205
- hasCAWSDir: true,
206
- cawsDir,
207
- hasWorkingSpec,
208
- hasMultipleSpecs,
209
- hasValidateScript,
210
- hasPolicy,
211
- hasSchemas,
212
- hasTemplates,
213
- hasTools,
214
- hasTemplateDir,
215
- templateDir,
216
- capabilities,
217
- isEnhanced: setupType === 'enhanced',
218
- isAdvanced: hasTools || hasValidateScript,
219
- };
220
- }
221
-
222
- let cawsSetup = null;
223
-
224
- // Initialize global setup detection
225
- try {
226
- cawsSetup = detectCAWSSetup();
227
-
228
- // If no template dir found in current directory, check CLI installation location
229
- if (!cawsSetup.hasTemplateDir) {
230
- const cliTemplatePaths = [
231
- path.resolve(__dirname, '../templates'),
232
- path.resolve(__dirname, 'templates'),
233
- ];
234
-
235
- for (const testPath of cliTemplatePaths) {
236
- if (fs.existsSync(testPath)) {
237
- cawsSetup.templateDir = testPath;
238
- cawsSetup.hasTemplateDir = true;
239
- break;
240
- }
241
- }
242
- }
243
- } catch (error) {
244
- console.warn('âš ī¸ Failed to detect CAWS setup globally:', error.message);
245
- cawsSetup = {
246
- type: 'unknown',
247
- hasCAWSDir: false,
248
- cawsDir: null,
249
- hasWorkingSpec: false,
250
- hasMultipleSpecs: false,
251
- hasValidateScript: false,
252
- hasPolicy: false,
253
- hasSchemas: false,
254
- hasTemplates: false,
255
- hasTools: false,
256
- hasTemplateDir: false,
257
- templateDir: null,
258
- capabilities: [],
259
- isEnhanced: false,
260
- isAdvanced: false,
261
- };
262
- }
263
-
264
- // Dynamic imports based on setup
265
- let provenanceTools = null;
266
-
267
- // Function to load provenance tools dynamically
268
- function loadProvenanceTools() {
269
- if (provenanceTools) return provenanceTools; // Already loaded
270
-
271
- try {
272
- const setup = detectCAWSSetup();
273
- if (setup?.hasTemplateDir && setup?.templateDir) {
274
- const { generateProvenance, saveProvenance } = require(
275
- path.join(setup.templateDir, 'apps/tools/caws/provenance.js')
276
- );
277
- provenanceTools = { generateProvenance, saveProvenance };
278
- console.log('✅ Loaded provenance tools from:', setup.templateDir);
279
- }
280
- } catch (error) {
281
- // Fallback for environments without template
282
- provenanceTools = null;
283
- console.warn('âš ī¸ Provenance tools not available:', error.message);
284
- }
285
-
286
- return provenanceTools;
287
- }
288
-
289
- const CLI_VERSION = require('../package.json').version;
290
-
291
- // Initialize JSON Schema validator - using simplified validation for CLI stability
292
- const validateWorkingSpec = (spec) => {
293
- try {
294
- // Basic structural validation for essential fields
295
- const requiredFields = [
296
- 'id',
297
- 'title',
298
- 'risk_tier',
299
- 'mode',
300
- 'change_budget',
301
- 'blast_radius',
302
- 'operational_rollback_slo',
303
- 'scope',
304
- 'invariants',
305
- 'acceptance',
306
- 'non_functional',
307
- 'contracts',
308
- ];
309
-
310
- for (const field of requiredFields) {
311
- if (!spec[field]) {
312
- return {
313
- valid: false,
314
- errors: [
315
- {
316
- instancePath: `/${field}`,
317
- message: `Missing required field: ${field}`,
318
- },
319
- ],
320
- };
321
- }
322
- }
323
-
324
- // Validate specific field formats
325
- if (!/^[A-Z]+-\d+$/.test(spec.id)) {
326
- return {
327
- valid: false,
328
- errors: [
329
- {
330
- instancePath: '/id',
331
- message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
332
- },
333
- ],
334
- };
335
- }
336
-
337
- // Validate experimental mode
338
- if (spec.experimental_mode) {
339
- if (typeof spec.experimental_mode !== 'object') {
340
- return {
341
- valid: false,
342
- errors: [
343
- {
344
- instancePath: '/experimental_mode',
345
- message:
346
- 'Experimental mode must be an object with enabled, rationale, and expires_at fields',
347
- },
348
- ],
349
- };
350
- }
351
-
352
- const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
353
- for (const field of requiredExpFields) {
354
- if (!(field in spec.experimental_mode)) {
355
- return {
356
- valid: false,
357
- errors: [
358
- {
359
- instancePath: `/experimental_mode/${field}`,
360
- message: `Missing required experimental mode field: ${field}`,
361
- },
362
- ],
363
- };
364
- }
365
- }
366
-
367
- if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
368
- return {
369
- valid: false,
370
- errors: [
371
- {
372
- instancePath: '/experimental_mode',
373
- message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
374
- },
375
- ],
376
- };
377
- }
378
- }
379
-
380
- if (spec.risk_tier < 1 || spec.risk_tier > 3) {
381
- return {
382
- valid: false,
383
- errors: [
384
- {
385
- instancePath: '/risk_tier',
386
- message: 'Risk tier must be 1, 2, or 3',
387
- },
388
- ],
389
- };
390
- }
391
-
392
- if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
393
- return {
394
- valid: false,
395
- errors: [
396
- {
397
- instancePath: '/scope/in',
398
- message: 'Scope IN must not be empty',
399
- },
400
- ],
401
- };
402
- }
403
-
404
- return { valid: true };
405
- } catch (error) {
406
- return {
407
- valid: false,
408
- errors: [
409
- {
410
- instancePath: '',
411
- message: `Validation error: ${error.message}`,
412
- },
413
- ],
414
- };
415
- }
416
- };
417
-
418
- /**
419
- * Enhanced validation with suggestions and auto-fix
420
- */
421
- function validateWorkingSpecWithSuggestions(spec, options = {}) {
422
- const { autoFix = false, suggestions = true } = options;
423
-
424
- try {
425
- // Basic structural validation for essential fields
426
- const requiredFields = [
427
- 'id',
428
- 'title',
429
- 'risk_tier',
430
- 'mode',
431
- 'change_budget',
432
- 'blast_radius',
433
- 'operational_rollback_slo',
434
- 'scope',
435
- 'invariants',
436
- 'acceptance',
437
- 'non_functional',
438
- 'contracts',
439
- ];
440
-
441
- let errors = [];
442
- let warnings = [];
443
- let fixes = [];
444
-
445
- for (const field of requiredFields) {
446
- if (!spec[field]) {
447
- errors.push({
448
- instancePath: `/${field}`,
449
- message: `Missing required field: ${field}`,
450
- suggestion: getFieldSuggestion(field, spec),
451
- canAutoFix: canAutoFixField(field, spec),
452
- });
453
- }
454
- }
455
-
456
- // Validate specific field formats
457
- if (spec.id && !/^[A-Z]+-\d+$/.test(spec.id)) {
458
- errors.push({
459
- instancePath: '/id',
460
- message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
461
- suggestion: 'Use format like: PROJ-001, FEAT-002, FIX-003',
462
- canAutoFix: false,
463
- });
464
- }
465
-
466
- // Validate risk tier
467
- if (spec.risk_tier !== undefined && (spec.risk_tier < 1 || spec.risk_tier > 3)) {
468
- errors.push({
469
- instancePath: '/risk_tier',
470
- message: 'Risk tier must be 1, 2, or 3',
471
- suggestion:
472
- 'Tier 1: Critical (auth, billing), Tier 2: Standard (features), Tier 3: Low risk (UI)',
473
- canAutoFix: true,
474
- });
475
- fixes.push({ field: 'risk_tier', value: Math.max(1, Math.min(3, spec.risk_tier || 2)) });
476
- }
477
-
478
- // Validate scope.in is not empty
479
- if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
480
- errors.push({
481
- instancePath: '/scope/in',
482
- message: 'Scope IN must not be empty',
483
- suggestion: 'Specify directories/files that are included in changes',
484
- canAutoFix: false,
485
- });
486
- }
487
-
488
- // Check for common issues
489
- if (!spec.invariants || spec.invariants.length === 0) {
490
- warnings.push({
491
- instancePath: '/invariants',
492
- message: 'No system invariants defined',
493
- suggestion: 'Add 1-3 statements about what must always remain true',
494
- });
495
- }
496
-
497
- if (!spec.acceptance || spec.acceptance.length === 0) {
498
- warnings.push({
499
- instancePath: '/acceptance',
500
- message: 'No acceptance criteria defined',
501
- suggestion: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
502
- });
503
- }
504
-
505
- // Validate experimental mode
506
- if (spec.experimental_mode) {
507
- if (typeof spec.experimental_mode !== 'object') {
508
- errors.push({
509
- instancePath: '/experimental_mode',
510
- message:
511
- 'Experimental mode must be an object with enabled, rationale, and expires_at fields',
512
- suggestion: 'Fix experimental_mode structure',
513
- canAutoFix: false,
514
- });
515
- } else {
516
- const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
517
- for (const field of requiredExpFields) {
518
- if (!(field in spec.experimental_mode)) {
519
- errors.push({
520
- instancePath: `/experimental_mode/${field}`,
521
- message: `Missing required experimental mode field: ${field}`,
522
- suggestion: `Add ${field} to experimental_mode`,
523
- canAutoFix: field === 'enabled' ? true : false,
524
- });
525
- if (field === 'enabled') {
526
- fixes.push({ field: `experimental_mode.${field}`, value: true });
527
- }
528
- }
529
- }
530
-
531
- if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
532
- warnings.push({
533
- instancePath: '/experimental_mode',
534
- message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
535
- suggestion: 'Either set risk_tier to 3 or disable experimental mode',
536
- });
537
- }
538
- }
539
- }
540
-
541
- // Apply auto-fixes if requested
542
- if (autoFix && fixes.length > 0) {
543
- console.log(chalk.cyan('🔧 Applying auto-fixes...'));
544
- for (const fix of fixes) {
545
- if (fix.field.includes('.')) {
546
- const [parent, child] = fix.field.split('.');
547
- if (!spec[parent]) spec[parent] = {};
548
- spec[parent][child] = fix.value;
549
- } else {
550
- spec[fix.field] = fix.value;
551
- }
552
- console.log(` Fixed: ${fix.field} = ${JSON.stringify(fix.value)}`);
553
- }
554
- }
555
-
556
- // Display results
557
- if (errors.length > 0) {
558
- console.error(chalk.red('❌ Validation failed with errors:'));
559
- errors.forEach((error, index) => {
560
- console.error(`${index + 1}. ${error.instancePath || 'root'}: ${error.message}`);
561
- if (suggestions && error.suggestion) {
562
- console.error(` 💡 ${error.suggestion}`);
563
- }
564
- if (error.canAutoFix) {
565
- console.error(` 🔧 Can auto-fix: ${autoFix ? 'applied' : 'run with --auto-fix'}`);
566
- }
567
- });
568
- return { valid: false, errors, warnings };
569
- }
570
-
571
- if (warnings.length > 0 && suggestions) {
572
- console.warn(chalk.yellow('âš ī¸ Validation passed with warnings:'));
573
- warnings.forEach((warning, index) => {
574
- console.warn(`${index + 1}. ${warning.instancePath || 'root'}: ${warning.message}`);
575
- if (warning.suggestion) {
576
- console.warn(` 💡 ${warning.suggestion}`);
577
- }
578
- });
579
- }
580
-
581
- console.log(chalk.green('✅ Working specification is valid'));
582
- return { valid: true, errors: [], warnings };
583
- } catch (error) {
584
- console.error(chalk.red('❌ Error during validation:'), error.message);
585
- return {
586
- valid: false,
587
- errors: [{ instancePath: '', message: `Validation error: ${error.message}` }],
588
- warnings: [],
589
- };
590
- }
591
- }
592
-
593
- function getFieldSuggestion(field, _spec) {
594
- const suggestions = {
595
- id: 'Use format like: PROJ-001, FEAT-002, FIX-003',
596
- title: 'Add a descriptive project title',
597
- risk_tier: 'Choose: 1 (critical), 2 (standard), or 3 (low risk)',
598
- mode: 'Choose: feature, refactor, fix, doc, or chore',
599
- change_budget: 'Set max_files and max_loc based on risk tier',
600
- blast_radius: 'List affected modules and data migration needs',
601
- operational_rollback_slo: 'Choose: 1m, 5m, 15m, or 1h',
602
- scope: "Define what's included (in) and excluded (out) from changes",
603
- invariants: 'Add 1-3 statements about what must always remain true',
604
- acceptance: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
605
- non_functional: 'Define accessibility, performance, and security requirements',
606
- contracts: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
607
- };
608
- return suggestions[field] || `Add the ${field} field`;
609
- }
610
-
611
- function canAutoFixField(field, _spec) {
612
- const autoFixable = ['risk_tier'];
613
- return autoFixable.includes(field);
614
- }
615
-
616
- /**
617
- * Generate a getting started guide based on project analysis
618
- */
619
- function generateGettingStartedGuide(analysis) {
620
- const { projectType, packageJson, hasTests, hasLinting } = analysis;
621
-
622
- const projectName = packageJson.name || 'your-project';
623
- const capitalizedType = projectType.charAt(0).toUpperCase() + projectType.slice(1);
624
-
625
- let guide = `# Getting Started with CAWS - ${capitalizedType} Project
626
-
627
- **Project**: ${projectName}
628
- **Type**: ${capitalizedType}
629
- **Generated**: ${new Date().toLocaleDateString()}
630
-
631
- ---
632
-
633
- ## Phase 1: Setup Verification (15 mins)
634
-
635
- Complete these steps to ensure your CAWS setup is working:
636
-
637
- ### ✅ Already Done
638
- - [x] Initialize CAWS project
639
- - [x] Generate working spec
640
- - [x] Set up basic structure
641
-
642
- ### Next Steps
643
- - [ ] Review \`.caws/working-spec.yaml\` - customize for your needs
644
- - [ ] Run validation: \`caws validate --suggestions\`
645
- - [ ] Review tier policy in \`.caws/policy/\` (if applicable)
646
- - [ ] Update \`.caws/templates/\` with project-specific examples
647
-
648
- ---
649
-
650
- ## Phase 2: First Feature (30 mins)
651
-
652
- Time to create your first CAWS-managed feature:
653
-
654
- ### Steps
655
- 1. **Copy a template**:
656
- \`\`\`bash
657
- cp .caws/templates/feature.plan.md docs/plans/FEATURE-001.md
658
- \`\`\`
659
-
660
- 2. **Customize the plan**:
661
- - Update title and description
662
- - Fill in acceptance criteria (GIVEN/WHEN/THEN format)
663
- - Set appropriate risk tier
664
- - Define scope and invariants
665
-
666
- 3. **Write tests first** (TDD approach):
667
- \`\`\`bash
668
- # For ${projectType} projects, focus on:
669
- ${getTestingGuidance(projectType)}
670
- \`\`\`
671
-
672
- 4. **Implement the feature**:
673
- - Stay within change budget limits
674
- - Follow acceptance criteria
675
- - Maintain system invariants
676
-
677
- 5. **Run full verification**:
678
- \`\`\`bash
679
- caws validate --suggestions
680
- ${hasTests ? 'npm test' : '# Add tests when ready'}
681
- ${hasLinting ? 'npm run lint' : '# Add linting when ready'}
682
- \`\`\`
683
-
684
- ---
685
-
686
- ## Phase 3: CI/CD Setup (20 mins)
687
-
688
- Set up automated quality gates:
689
-
690
- ### GitHub Actions (Recommended)
691
- 1. **Create workflow**: \`.github/workflows/caws.yml\`
692
- \`\`\`yaml
693
- name: CAWS Quality Gates
694
- on: [pull_request]
695
-
696
- jobs:
697
- validate:
698
- runs-on: ubuntu-latest
699
- steps:
700
- - uses: actions/checkout@v4
701
- - uses: actions/setup-node@v4
702
- with:
703
- node-version: '18'
704
- - run: npm ci
705
- - run: npx caws validate --quiet
706
- - run: npm test # Add when ready
707
- \`\`\`
708
-
709
- 2. **Configure branch protection**:
710
- - Require PR validation
711
- - Require tests to pass
712
- - Require CAWS spec validation
713
-
714
- ### Other CI Systems
715
- - **GitLab CI**: Use \`caws validate\` in \`.gitlab-ci.yml\`
716
- - **Jenkins**: Add validation step to pipeline
717
- - **CircleCI**: Include in \`.circleci/config.yml\`
718
-
719
- ---
720
-
721
- ## Phase 4: Team Onboarding (ongoing)
722
-
723
- ### For Team Members
724
- 1. **Read the basics**: Start with this guide
725
- 2. **Learn by example**: Review completed features
726
- 3. **Practice**: Create small features following the process
727
- 4. **Contribute**: Help improve templates and processes
728
-
729
- ### For Project Leads
730
- 1. **Customize templates**: Adapt to team preferences
731
- 2. **Set standards**: Define project-specific conventions
732
- 3. **Monitor quality**: Review metrics and adjust gates
733
- 4. **Scale practices**: Apply CAWS to more complex work
734
-
735
- ---
736
-
737
- ## Key Concepts Quick Reference
738
-
739
- ### Risk Tiers
740
- - **Tier 1**: Critical (auth, billing, migrations) - Max rigor
741
- - **Tier 2**: Standard (features, APIs) - Standard rigor
742
- - **Tier 3**: Low risk (UI, tooling) - Basic rigor
743
-
744
- ### Change Budget
745
- - Limits help maintain quality and reviewability
746
- - Adjust based on risk tier and team experience
747
- - Track actual vs. budgeted changes
748
-
749
- ### System Invariants
750
- - Core guarantees that must always hold true
751
- - Examples: "Data integrity maintained", "API contracts honored"
752
- - Define 2-4 key invariants for your system
753
-
754
- ### Acceptance Criteria
755
- - Use GIVEN/WHEN/THEN format
756
- - Focus on observable behavior
757
- - Include edge cases and error conditions
758
-
759
- ---
760
-
761
- ## Common Pitfalls to Avoid
762
-
763
- ### For ${capitalizedType} Projects
764
- ${getProjectSpecificPitfalls(projectType)}
765
-
766
- ### General Issues
767
- 1. **Over-customization**: Start with defaults, customize gradually
768
- 2. **Missing invariants**: Define what must never break
769
- 3. **Vague acceptance**: Make criteria measurable and testable
770
- 4. **Large changes**: Break big features into smaller, reviewable pieces
771
-
772
- ---
773
-
774
- ## Resources
775
-
776
- ### Documentation
777
- - **Quick Reference**: This guide
778
- - **Templates**: \`.caws/templates/\`
779
- - **Examples**: \`.caws/examples/\` (when available)
780
-
781
- ### Commands
782
- - \`caws validate --suggestions\` - Get help with issues
783
- - \`caws validate --auto-fix\` - Fix safe problems automatically
784
- - \`caws init --interactive\` - Customize existing setup
785
-
786
- ### Community
787
- - **GitHub Issues**: Report problems and request features
788
- - **Discussions**: Share experiences and best practices
789
- - **Wiki**: Growing collection of examples and guides
790
-
791
- ---
792
-
793
- ## Next Steps
794
-
795
- 1. **Right now**: Review your working spec and customize it
796
- 2. **Today**: Create your first feature plan
797
- 3. **This week**: Set up CI/CD and branch protection
798
- 4. **Ongoing**: Refine processes based on team feedback
799
-
800
- Remember: CAWS is a framework, not a straightjacket. Adapt it to your team's needs while maintaining the core principles of determinism and quality.
801
-
802
- **Happy coding! đŸŽ¯**
803
- `;
804
-
805
- return guide;
806
- }
807
-
808
- function getTestingGuidance(projectType) {
809
- const guidance = {
810
- extension: `- Webview rendering tests\n- Command registration tests\n- Extension activation tests`,
811
- library: `- Component rendering tests\n- API function tests\n- Type export tests`,
812
- api: `- Endpoint response tests\n- Error handling tests\n- Authentication tests`,
813
- cli: `- Command parsing tests\n- Output formatting tests\n- Error code tests`,
814
- monorepo: `- Cross-package integration tests\n- Shared module tests\n- Build pipeline tests`,
815
- application: `- User interaction tests\n- State management tests\n- Integration tests`,
816
- };
817
- return (
818
- guidance[projectType] || `- Unit tests for core functions\n- Integration tests for workflows`
819
- );
820
- }
821
-
822
- function getProjectSpecificPitfalls(projectType) {
823
- const pitfalls = {
824
- extension: `1. **Webview security**: Never use \`vscode.executeCommand\` from untrusted content
825
- 2. **Activation timing**: Test cold start performance
826
- 3. **API compatibility**: Check VS Code API version compatibility`,
827
- library: `1. **Bundle size**: Monitor and limit package size
828
- 2. **Type exports**: Ensure all public APIs are typed
829
- 3. **Peer dependencies**: Handle React/Angular versions carefully`,
830
- api: `1. **Backward compatibility**: Version APIs carefully
831
- 2. **Rate limiting**: Test and document limits
832
- 3. **Data validation**: Validate all inputs thoroughly`,
833
- cli: `1. **Exit codes**: Use standard codes (0=success, 1=error)
834
- 2. **Help text**: Keep it concise and helpful
835
- 3. **Error messages**: Make them actionable`,
836
- monorepo: `1. **Dependency cycles**: Avoid circular imports
837
- 2. **Version consistency**: Keep package versions aligned
838
- 3. **Build order**: Ensure correct build dependencies`,
839
- application: `1. **State consistency**: Prevent invalid state transitions
840
- 2. **Performance**: Monitor and optimize critical paths
841
- 3. **Accessibility**: Test with screen readers and keyboard navigation`,
842
- };
843
- return (
844
- pitfalls[projectType] ||
845
- `1. **Test coverage**: Maintain adequate test coverage
846
- 2. **Documentation**: Keep code and APIs documented
847
- 3. **Dependencies**: Review and update regularly`
848
- );
849
- }
850
-
851
- /**
852
- * Generate smart .gitignore patterns for CAWS projects
853
- */
854
- function generateGitignorePatterns(existingGitignore = '') {
855
- const cawsPatterns = `
856
- # CAWS Configuration (tracked - these should be versioned)
857
- # Note: .caws/ and .agent/ are tracked for provenance
858
- # But we exclude temporary/generated files:
859
-
860
- # CAWS temporary files (ignored)
861
- .agent/temp/
862
- .agent/cache/
863
- .caws/.cache/
864
- .caws/tmp/
865
-
866
- # Build outputs (common patterns)
867
- dist/
868
- build/
869
- *.tsbuildinfo
870
- .next/
871
- .nuxt/
872
- .vite/
873
-
874
- # Dependencies
875
- node_modules/
876
- .pnpm-store/
877
-
878
- # Environment files
879
- .env
880
- .env.local
881
- .env.development.local
882
- .env.test.local
883
- .env.production.local
884
-
885
- # IDE files
886
- .vscode/settings.json
887
- .idea/
888
- *.swp
889
- *.swo
890
-
891
- # OS files
892
- .DS_Store
893
- Thumbs.db
894
-
895
- # Logs
896
- logs/
897
- *.log
898
- npm-debug.log*
899
- yarn-debug.log*
900
- yarn-error.log*
901
-
902
- # Coverage reports
903
- coverage/
904
- .nyc_output/
905
-
906
- # Test results
907
- test-results/
908
- playwright-report/
909
- `;
910
-
911
- // If there's an existing .gitignore, merge intelligently
912
- if (existingGitignore.trim()) {
913
- // Check if CAWS patterns are already present
914
- if (existingGitignore.includes('# CAWS Configuration')) {
915
- console.log(chalk.blue('â„šī¸ .gitignore already contains CAWS patterns - skipping'));
916
- return existingGitignore;
917
- }
918
-
919
- // Append CAWS patterns to existing .gitignore
920
- return existingGitignore.trim() + '\n\n' + cawsPatterns.trim() + '\n';
921
- }
922
-
923
- return cawsPatterns.trim();
924
- }
925
-
926
- // Only log schema validation if not running quiet commands
927
- if (!process.argv.includes('--version') && !process.argv.includes('-V')) {
928
- console.log(chalk.green('✅ Schema validation initialized successfully'));
929
- }
930
-
931
- /**
932
- * Generate working spec YAML with user input
933
- * @param {Object} answers - User responses
934
- * @returns {string} - Generated YAML content
935
- */
936
- function generateWorkingSpec(answers) {
937
- const template = {
938
- id: answers.projectId,
939
- title: answers.projectTitle,
940
- risk_tier: answers.riskTier,
941
- mode: answers.projectMode,
942
- change_budget: {
943
- max_files: answers.maxFiles,
944
- max_loc: answers.maxLoc,
945
- },
946
- blast_radius: {
947
- modules: answers.blastModules
948
- .split(',')
949
- .map((m) => m.trim())
950
- .filter((m) => m),
951
- data_migration: answers.dataMigration,
952
- },
953
- operational_rollback_slo: answers.rollbackSlo,
954
- threats: (answers.projectThreats || '')
955
- .split('\n')
956
- .map((t) => t.trim())
957
- .filter((t) => t && !t.startsWith('-') === false), // Allow lines starting with -
958
- scope: {
959
- in: (answers.scopeIn || '')
960
- .split(',')
961
- .map((s) => s.trim())
962
- .filter((s) => s),
963
- out: (answers.scopeOut || '')
964
- .split(',')
965
- .map((s) => s.trim())
966
- .filter((s) => s),
967
- },
968
- invariants: (answers.projectInvariants || '')
969
- .split('\n')
970
- .map((i) => i.trim())
971
- .filter((i) => i),
972
- acceptance: answers.acceptanceCriteria
973
- .split('\n')
974
- .filter((a) => a.trim())
975
- .map((criteria, index) => {
976
- const id = `A${index + 1}`;
977
- const upperCriteria = criteria.toUpperCase();
978
-
979
- // Try different variations of the format
980
- let given = '';
981
- let when = '';
982
- let then = '';
983
-
984
- if (
985
- upperCriteria.includes('GIVEN') &&
986
- upperCriteria.includes('WHEN') &&
987
- upperCriteria.includes('THEN')
988
- ) {
989
- given = criteria.split(/WHEN/i)[0]?.replace(/GIVEN/i, '').trim() || '';
990
- const whenThen = criteria.split(/WHEN/i)[1];
991
- when = whenThen?.split(/THEN/i)[0]?.trim() || '';
992
- then = whenThen?.split(/THEN/i)[1]?.trim() || '';
993
- } else {
994
- // Fallback: just split by lines and create simple criteria
995
- given = 'Current system state';
996
- when = criteria.replace(/^(GIVEN|WHEN|THEN)/i, '').trim();
997
- then = 'Expected behavior occurs';
998
- }
999
-
1000
- return {
1001
- id,
1002
- given: given || 'Current system state',
1003
- when: when || criteria,
1004
- then: then || 'Expected behavior occurs',
1005
- };
1006
- }),
1007
- non_functional: {
1008
- a11y: answers.a11yRequirements
1009
- .split(',')
1010
- .map((a) => a.trim())
1011
- .filter((a) => a),
1012
- perf: { api_p95_ms: answers.perfBudget },
1013
- security: answers.securityRequirements
1014
- .split(',')
1015
- .map((s) => s.trim())
1016
- .filter((s) => s),
1017
- },
1018
- contracts: [
1019
- {
1020
- type: answers.contractType,
1021
- path: answers.contractPath,
1022
- },
1023
- ],
1024
- observability: {
1025
- logs: answers.observabilityLogs
1026
- .split(',')
1027
- .map((l) => l.trim())
1028
- .filter((l) => l),
1029
- metrics: answers.observabilityMetrics
1030
- .split(',')
1031
- .map((m) => m.trim())
1032
- .filter((m) => m),
1033
- traces: answers.observabilityTraces
1034
- .split(',')
1035
- .map((t) => t.trim())
1036
- .filter((t) => t),
1037
- },
1038
- migrations: (answers.migrationPlan || '')
1039
- .split('\n')
1040
- .map((m) => m.trim())
1041
- .filter((m) => m),
1042
- rollback: (answers.rollbackPlan || '')
1043
- .split('\n')
1044
- .map((r) => r.trim())
1045
- .filter((r) => r),
1046
- human_override: answers.needsOverride
1047
- ? {
1048
- enabled: true,
1049
- approver: answers.overrideApprover,
1050
- rationale: answers.overrideRationale,
1051
- waived_gates: answers.waivedGates,
1052
- approved_at: new Date().toISOString(),
1053
- expires_at: new Date(
1054
- Date.now() + answers.overrideExpiresDays * 24 * 60 * 60 * 1000
1055
- ).toISOString(),
1056
- }
1057
- : undefined,
1058
- experimental_mode: answers.isExperimental
1059
- ? {
1060
- enabled: true,
1061
- rationale: answers.experimentalRationale,
1062
- expires_at: new Date(
1063
- Date.now() + answers.experimentalExpiresDays * 24 * 60 * 60 * 1000
1064
- ).toISOString(),
1065
- sandbox_location: answers.experimentalSandbox,
1066
- }
1067
- : undefined,
1068
- ai_assessment: {
1069
- confidence_level: answers.aiConfidence,
1070
- uncertainty_areas: answers.uncertaintyAreas
1071
- .split(',')
1072
- .map((a) => a.trim())
1073
- .filter((a) => a),
1074
- complexity_factors: answers.complexityFactors
1075
- .split(',')
1076
- .map((f) => f.trim())
1077
- .filter((f) => f),
1078
- risk_factors: [], // Could be populated by AI analysis
1079
- },
1080
- };
1081
-
1082
- return yaml.dump(template, { indent: 2 });
1083
- }
1084
-
1085
- /**
1086
- * Validate generated working spec against JSON schema
1087
- * @param {string} specContent - YAML spec content
1088
- * @param {Object} answers - User responses for error context
1089
- */
1090
- function validateGeneratedSpec(specContent, _answers) {
1091
- try {
1092
- const spec = yaml.load(specContent);
1093
-
1094
- const isValid = validateWorkingSpec(spec);
1095
-
1096
- if (!isValid) {
1097
- console.error(chalk.red('❌ Generated working spec failed validation:'));
1098
- validateWorkingSpec.errors.forEach((error) => {
1099
- console.error(` - ${error.instancePath || 'root'}: ${error.message}`);
1100
- });
1101
-
1102
- // Provide helpful guidance
1103
- console.log(chalk.blue('\n💡 Validation Tips:'));
1104
- console.log(' - Ensure risk_tier is 1, 2, or 3');
1105
- console.log(' - Check that scope.in is not empty');
1106
- console.log(' - Verify invariants and acceptance criteria are provided');
1107
- console.log(' - For tier 1 and 2, ensure contracts are specified');
1108
-
1109
- process.exit(1);
1110
- }
1111
-
1112
- console.log(chalk.green('✅ Generated working spec passed validation'));
1113
- } catch (error) {
1114
- console.error(chalk.red('❌ Error validating working spec:'), error.message);
1115
- process.exit(1);
1116
- }
1117
- }
1118
-
1119
- /**
1120
- * Detect project type from existing files and structure
1121
- */
1122
- function detectProjectType(cwd = process.cwd()) {
1123
- const files = fs.readdirSync(cwd);
1124
-
1125
- // Check for various project indicators
1126
- const hasPackageJson = files.includes('package.json');
1127
- const hasPnpm = files.includes('pnpm-workspace.yaml');
1128
- const hasYarn = files.includes('yarn.lock');
1129
-
1130
- let packageJson = {};
1131
- if (hasPackageJson) {
1132
- try {
1133
- packageJson = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
1134
- } catch (e) {
1135
- // Ignore parse errors
1136
- }
1137
- }
1138
-
1139
- // VS Code Extension detection
1140
- const isVscodeExtension =
1141
- packageJson.engines?.vscode ||
1142
- packageJson.contributes ||
1143
- packageJson.activationEvents ||
1144
- packageJson.main?.includes('extension.js');
1145
-
1146
- // Monorepo detection
1147
- const isMonorepo = hasPnpm || hasYarn || files.includes('packages') || files.includes('apps');
1148
-
1149
- // Library detection
1150
- const isLibrary = packageJson.main || packageJson.module || packageJson.exports;
1151
-
1152
- // CLI detection
1153
- const isCli = packageJson.bin || packageJson.name?.startsWith('@') === false;
1154
-
1155
- // API detection
1156
- const isApi =
1157
- packageJson.scripts?.start ||
1158
- packageJson.dependencies?.express ||
1159
- packageJson.dependencies?.fastify ||
1160
- packageJson.dependencies?.['@types/express'];
1161
-
1162
- // Determine primary type
1163
- if (isVscodeExtension) return 'extension';
1164
- if (isMonorepo) return 'monorepo';
1165
- if (isApi) return 'api';
1166
- if (isLibrary) return 'library';
1167
- if (isCli) return 'cli';
1168
-
1169
- // Default fallback
1170
- return 'application';
1171
- }
1172
-
1173
- /**
1174
- * Generate working spec from project analysis
1175
- */
1176
- function generateWorkingSpecFromAnalysis(analysis) {
1177
- const { projectType, packageJson } = analysis;
1178
-
1179
- const templates = {
1180
- extension: {
1181
- risk_tier: 2,
1182
- mode: 'feature',
1183
- change_budget: { max_files: 25, max_loc: 1000 },
1184
- invariants: [
1185
- 'Webview only accesses workspace files via VS Code API',
1186
- 'Extension activates in <1s on typical machine',
1187
- 'All commands have keyboard shortcuts',
1188
- ],
1189
- scope: {
1190
- in: ['src/', 'package.json', 'tsconfig.json'],
1191
- out: ['node_modules/', '*.vsix'],
1192
- },
1193
- acceptance: [
1194
- {
1195
- id: 'A1',
1196
- given: 'User has workspace open',
1197
- when: 'Extension activates',
1198
- then: 'Webview loads within 1 second',
1199
- },
1200
- ],
1201
- non_functional: {
1202
- a11y: ['keyboard navigation', 'screen reader support', 'high contrast theme'],
1203
- perf: { api_p95_ms: 100 },
1204
- security: ['CSP enforcement for webviews', 'No arbitrary filesystem access'],
1205
- },
1206
- },
1207
- library: {
1208
- risk_tier: 2,
1209
- mode: 'feature',
1210
- change_budget: { max_files: 20, max_loc: 800 },
1211
- invariants: [
1212
- 'No runtime dependencies except React',
1213
- 'Tree-shakeable exports',
1214
- 'TypeScript types exported',
1215
- ],
1216
- scope: {
1217
- in: ['src/', 'lib/', 'package.json'],
1218
- out: ['examples/', 'docs/', 'node_modules/'],
1219
- },
1220
- acceptance: [
1221
- {
1222
- id: 'A1',
1223
- given: 'Library is imported',
1224
- when: 'Component is rendered',
1225
- then: 'No runtime errors occur',
1226
- },
1227
- ],
1228
- non_functional: {
1229
- a11y: ['WCAG 2.1 AA compliance', 'Semantic HTML'],
1230
- perf: { bundle_size_kb: 50 },
1231
- security: ['Input validation', 'XSS prevention'],
1232
- },
1233
- },
1234
- api: {
1235
- risk_tier: 1,
1236
- mode: 'feature',
1237
- change_budget: { max_files: 40, max_loc: 1500 },
1238
- invariants: [
1239
- 'API maintains backward compatibility',
1240
- 'All endpoints respond within 100ms',
1241
- 'Data consistency maintained across requests',
1242
- ],
1243
- scope: {
1244
- in: ['src/', 'routes/', 'models/', 'tests/'],
1245
- out: ['node_modules/', 'logs/', 'temp/'],
1246
- },
1247
- acceptance: [
1248
- {
1249
- id: 'A1',
1250
- given: 'Valid request is made',
1251
- when: 'Endpoint is called',
1252
- then: 'Correct response returned within SLO',
1253
- },
1254
- ],
1255
- non_functional: {
1256
- a11y: ['API documentation accessible'],
1257
- perf: { api_p95_ms: 100 },
1258
- security: ['Input validation', 'Rate limiting', 'Authentication'],
1259
- },
1260
- },
1261
- cli: {
1262
- risk_tier: 3,
1263
- mode: 'feature',
1264
- change_budget: { max_files: 15, max_loc: 600 },
1265
- invariants: [
1266
- 'CLI exits with appropriate codes',
1267
- 'Help text is informative',
1268
- 'Error messages are clear',
1269
- ],
1270
- scope: {
1271
- in: ['src/', 'bin/', 'lib/', 'tests/'],
1272
- out: ['node_modules/', 'dist/'],
1273
- },
1274
- acceptance: [
1275
- {
1276
- id: 'A1',
1277
- given: 'User runs command with --help',
1278
- when: 'Help flag is provided',
1279
- then: 'Help text displays clearly',
1280
- },
1281
- ],
1282
- non_functional: {
1283
- a11y: ['Color contrast in terminal output'],
1284
- perf: { api_p95_ms: 50 },
1285
- security: ['Input validation', 'No arbitrary execution'],
1286
- },
1287
- },
1288
- monorepo: {
1289
- risk_tier: 1,
1290
- mode: 'feature',
1291
- change_budget: { max_files: 50, max_loc: 2000 },
1292
- invariants: [
1293
- 'All packages remain compatible',
1294
- 'Cross-package dependencies work',
1295
- 'Build system remains stable',
1296
- ],
1297
- scope: {
1298
- in: ['packages/', 'apps/', 'tools/', 'scripts/'],
1299
- out: ['node_modules/', 'dist/', 'build/'],
1300
- },
1301
- acceptance: [
1302
- {
1303
- id: 'A1',
1304
- given: 'Change is made to shared package',
1305
- when: 'All dependent packages build',
1306
- then: 'No breaking changes introduced',
1307
- },
1308
- ],
1309
- non_functional: {
1310
- a11y: ['Documentation accessible across packages'],
1311
- perf: { api_p95_ms: 200 },
1312
- security: ['Dependency audit passes', 'No vulnerable packages'],
1313
- },
1314
- },
1315
- application: {
1316
- risk_tier: 2,
1317
- mode: 'feature',
1318
- change_budget: { max_files: 30, max_loc: 1200 },
1319
- invariants: [
1320
- 'Application remains functional',
1321
- 'User data is preserved',
1322
- 'Performance does not degrade',
1323
- ],
1324
- scope: {
1325
- in: ['src/', 'components/', 'pages/', 'lib/'],
1326
- out: ['node_modules/', 'build/', 'dist/'],
1327
- },
1328
- acceptance: [
1329
- {
1330
- id: 'A1',
1331
- given: 'User interacts with application',
1332
- when: 'Feature is used',
1333
- then: 'Expected behavior occurs',
1334
- },
1335
- ],
1336
- non_functional: {
1337
- a11y: ['WCAG 2.1 AA compliance', 'Keyboard navigation'],
1338
- perf: { api_p95_ms: 250 },
1339
- security: ['Input validation', 'Authentication', 'Authorization'],
1340
- },
1341
- },
1342
- };
1343
-
1344
- const baseSpec = templates[projectType] || templates.application;
1345
-
1346
- return {
1347
- id: `${packageJson.name?.toUpperCase().replace(/[^A-Z0-9]/g, '-') || 'PROJECT'}-001`,
1348
- title: packageJson.name || 'Project',
1349
- risk_tier: baseSpec.risk_tier,
1350
- mode: baseSpec.mode,
1351
- change_budget: baseSpec.change_budget,
1352
- blast_radius: {
1353
- modules: ['core', 'api', 'ui'],
1354
- data_migration: false,
1355
- },
1356
- operational_rollback_slo: '5m',
1357
- scope: baseSpec.scope,
1358
- invariants: baseSpec.invariants,
1359
- acceptance: baseSpec.acceptance,
1360
- non_functional: baseSpec.non_functional,
1361
- contracts: [
1362
- {
1363
- type: projectType === 'api' ? 'openapi' : 'none',
1364
- path: projectType === 'api' ? 'docs/api.yaml' : '',
1365
- },
1366
- ],
1367
- observability: {
1368
- logs: ['error', 'warn', 'info'],
1369
- metrics: ['requests_total', 'errors_total'],
1370
- traces: ['request_flow'],
1371
- },
1372
- ai_assessment: {
1373
- confidence_level: 8,
1374
- uncertainty_areas: [],
1375
- complexity_factors: [],
1376
- risk_factors: [],
1377
- },
1378
- };
1379
- }
1380
-
1381
- /**
1382
- * Detect if current directory appears to be a project that should be initialized directly
1383
- */
1384
- function shouldInitInCurrentDirectory(projectName, currentDir) {
1385
- // If explicitly '.', always init in current directory
1386
- if (projectName === '.') return true;
1387
-
1388
- // Check for common project indicators
1389
- const projectIndicators = [
1390
- 'package.json',
1391
- 'tsconfig.json',
1392
- 'jest.config.js',
1393
- 'eslint.config.js',
1394
- 'README.md',
1395
- 'src/',
1396
- 'lib/',
1397
- 'app/',
1398
- 'packages/',
1399
- '.git/',
1400
- 'node_modules/', // Even if empty, suggests intent to be a project
1401
- ];
1402
-
1403
- const files = fs.readdirSync(currentDir);
1404
- const hasProjectIndicators = projectIndicators.some((indicator) => {
1405
- if (indicator.endsWith('/')) {
1406
- return files.includes(indicator.slice(0, -1));
1407
- }
1408
- return files.includes(indicator);
1409
- });
1410
-
1411
- return hasProjectIndicators;
1412
- }
1413
-
1414
- /**
1415
- * Initialize a new project with CAWS
1416
- */
1417
- async function initProject(projectName, options) {
1418
- const currentDir = process.cwd();
1419
- const isCurrentDirInit = shouldInitInCurrentDirectory(projectName, currentDir);
1420
-
1421
- if (!isCurrentDirInit && projectName !== '.') {
1422
- console.log(chalk.cyan(`🚀 Initializing new CAWS project: ${projectName}`));
1423
- console.log(chalk.gray(` (Creating subdirectory: ${projectName}/)`));
1424
- } else {
1425
- console.log(
1426
- chalk.cyan(`🚀 Initializing CAWS in current project: ${path.basename(currentDir)}`)
1427
- );
1428
- console.log(chalk.gray(` (Adding CAWS files to existing project)`));
1429
- }
1430
-
1431
- let answers; // Will be set either interactively or with defaults
1432
-
1433
- try {
1434
- // Validate project name
1435
- if (!projectName || projectName.trim() === '') {
1436
- console.error(chalk.red('❌ Project name is required'));
1437
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1438
- process.exit(1);
1439
- }
1440
-
1441
- // Special case: '.' means current directory, don't sanitize
1442
- if (projectName !== '.') {
1443
- // Sanitize project name
1444
- const sanitizedName = projectName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
1445
- if (sanitizedName !== projectName) {
1446
- console.warn(chalk.yellow(`âš ī¸ Project name sanitized to: ${sanitizedName}`));
1447
- projectName = sanitizedName;
1448
- }
1449
- }
1450
-
1451
- // Validate project name length
1452
- if (projectName.length > 50) {
1453
- console.error(chalk.red('❌ Project name is too long (max 50 characters)'));
1454
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1455
- process.exit(1);
1456
- }
1457
-
1458
- // Validate project name format
1459
- if (projectName.length === 0) {
1460
- console.error(chalk.red('❌ Project name cannot be empty'));
1461
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1462
- process.exit(1);
1463
- }
1464
-
1465
- // Check for invalid characters that should cause immediate failure
1466
- if (projectName.includes('/') || projectName.includes('\\') || projectName.includes('..')) {
1467
- console.error(chalk.red('❌ Project name contains invalid characters'));
1468
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1469
- console.error(chalk.blue('💡 Project name should not contain: / \\ ..'));
1470
- process.exit(1);
1471
- }
1472
-
1473
- // Determine if initializing in current directory
1474
- const initInCurrentDir = projectName === '.';
1475
- const targetDir = initInCurrentDir ? process.cwd() : path.resolve(process.cwd(), projectName);
1476
-
1477
- // Check if target directory already exists and has content (skip check for current directory)
1478
- if (!initInCurrentDir && fs.existsSync(projectName)) {
1479
- const existingFiles = fs.readdirSync(projectName);
1480
- if (existingFiles.length > 0) {
1481
- console.error(chalk.red(`❌ Directory '${projectName}' already exists and contains files`));
1482
- console.error(chalk.blue('💡 To initialize CAWS in current directory instead:'));
1483
- console.error(` ${chalk.cyan('caws init .')}`);
1484
- console.error(chalk.blue('💡 Or choose a different name/remove existing directory'));
1485
- process.exit(1);
1486
- }
1487
- }
1488
-
1489
- // Check if current directory has project files when trying to init in subdirectory
1490
- if (!initInCurrentDir) {
1491
- const currentDirFiles = fs.readdirSync(process.cwd());
1492
- const hasProjectFiles = currentDirFiles.some(
1493
- (file) => !file.startsWith('.') && file !== 'node_modules' && file !== '.git'
1494
- );
1495
-
1496
- if (hasProjectFiles) {
1497
- console.warn(chalk.yellow('âš ī¸ Current directory contains project files'));
1498
- console.warn(
1499
- chalk.blue('💡 You might want to initialize CAWS in current directory instead:')
1500
- );
1501
- console.warn(` ${chalk.cyan('caws init .')}`);
1502
- console.warn(chalk.blue(' Or continue to create subdirectory (Ctrl+C to cancel)'));
1503
- }
1504
- }
1505
-
1506
- // Save the original template directory before changing directories
1507
- const originalTemplateDir = cawsSetup?.hasTemplateDir ? cawsSetup.templateDir : null;
1508
-
1509
- // Check for existing agents.md/caws.md in target directory
1510
- const existingAgentsMd = fs.existsSync(path.join(targetDir, 'agents.md'));
1511
- const existingCawsMd = fs.existsSync(path.join(targetDir, 'caws.md'));
1512
-
1513
- // Create project directory and change to it (unless already in current directory)
1514
- if (!initInCurrentDir) {
1515
- await fs.ensureDir(projectName);
1516
- process.chdir(projectName);
1517
- console.log(chalk.green(`📁 Created project directory: ${projectName}`));
1518
- } else {
1519
- console.log(chalk.green(`📁 Initializing in current directory`));
1520
- }
1521
-
1522
- // Detect and adapt to existing setup
1523
- const currentSetup = detectCAWSSetup(process.cwd());
1524
-
1525
- if (currentSetup.type === 'new') {
1526
- // Create minimal CAWS structure
1527
- await fs.ensureDir('.caws');
1528
- await fs.ensureDir('.agent');
1529
- console.log(chalk.blue('â„šī¸ Created basic CAWS structure'));
1530
-
1531
- // Copy agents.md guide if templates are available
1532
- if (originalTemplateDir) {
1533
- try {
1534
- const agentsMdSource = path.join(originalTemplateDir, 'agents.md');
1535
- let targetFile = 'agents.md';
1536
-
1537
- if (fs.existsSync(agentsMdSource)) {
1538
- // Use the pre-checked values for conflicts
1539
- if (existingAgentsMd) {
1540
- // Conflict: user already has agents.md
1541
- if (options.interactive && !options.nonInteractive) {
1542
- // Interactive mode: ask user
1543
- const overwriteAnswer = await inquirer.prompt([
1544
- {
1545
- type: 'confirm',
1546
- name: 'overwrite',
1547
- message: 'âš ī¸ agents.md already exists. Overwrite with CAWS guide?',
1548
- default: false,
1549
- },
1550
- ]);
1551
-
1552
- if (overwriteAnswer.overwrite) {
1553
- targetFile = 'agents.md';
1554
- } else {
1555
- targetFile = 'caws.md';
1556
- }
1557
- } else {
1558
- // Non-interactive mode: use caws.md instead
1559
- targetFile = 'caws.md';
1560
- console.log(chalk.blue('â„šī¸ agents.md exists, using caws.md for CAWS guide'));
1561
- }
1562
- }
1563
-
1564
- // If caws.md also exists and that's our target, skip
1565
- if (targetFile === 'caws.md' && existingCawsMd) {
1566
- console.log(
1567
- chalk.yellow('âš ī¸ Both agents.md and caws.md exist, skipping guide copy')
1568
- );
1569
- } else {
1570
- const agentsMdDest = path.join(process.cwd(), targetFile);
1571
- await fs.copyFile(agentsMdSource, agentsMdDest);
1572
- console.log(chalk.green(`✅ Added ${targetFile} guide`));
1573
- }
1574
- }
1575
- } catch (templateError) {
1576
- console.warn(chalk.yellow('âš ī¸ Could not copy agents guide:'), templateError.message);
1577
- console.warn(
1578
- chalk.blue('💡 You can manually copy the guide from the caws-template package')
1579
- );
1580
- }
1581
- }
1582
- } else {
1583
- // Already has CAWS setup
1584
- console.log(chalk.green('✅ CAWS project detected - skipping template copy'));
1585
- }
1586
-
1587
- // Handle interactive wizard or template-based setup
1588
- if (options.interactive && !options.nonInteractive) {
1589
- console.log(chalk.cyan('đŸŽ¯ CAWS Interactive Setup Wizard'));
1590
- console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1591
- console.log(chalk.gray('This wizard will guide you through creating a CAWS working spec\n'));
1592
-
1593
- // Detect project type
1594
- const detectedType = detectProjectType(process.cwd());
1595
- console.log(chalk.blue(`đŸ“Ļ Detected project type: ${chalk.cyan(detectedType)}`));
1596
-
1597
- // Get package.json info if available
1598
- let packageJson = {};
1599
- try {
1600
- packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
1601
- } catch (e) {
1602
- // No package.json, that's fine
1603
- }
1604
-
1605
- const wizardQuestions = [
1606
- {
1607
- type: 'list',
1608
- name: 'projectType',
1609
- message: '❓ What type of project is this?',
1610
- choices: [
1611
- {
1612
- name: '🔌 VS Code Extension (webview, commands, integrations)',
1613
- value: 'extension',
1614
- short: 'VS Code Extension',
1615
- },
1616
- {
1617
- name: '📚 Library/Package (reusable components, utilities)',
1618
- value: 'library',
1619
- short: 'Library',
1620
- },
1621
- {
1622
- name: '🌐 API Service (REST, GraphQL, microservices)',
1623
- value: 'api',
1624
- short: 'API Service',
1625
- },
1626
- {
1627
- name: 'đŸ’ģ CLI Tool (command-line interface)',
1628
- value: 'cli',
1629
- short: 'CLI Tool',
1630
- },
1631
- {
1632
- name: 'đŸ—ī¸ Monorepo (multiple packages/apps)',
1633
- value: 'monorepo',
1634
- short: 'Monorepo',
1635
- },
1636
- {
1637
- name: '📱 Application (standalone app)',
1638
- value: 'application',
1639
- short: 'Application',
1640
- },
1641
- ],
1642
- default: detectedType,
1643
- },
1644
- {
1645
- type: 'input',
1646
- name: 'projectTitle',
1647
- message: '📝 Project Title (descriptive name):',
1648
- default:
1649
- packageJson.name ||
1650
- projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
1651
- },
1652
- {
1653
- type: 'list',
1654
- name: 'riskTier',
1655
- message: 'âš ī¸ Risk Tier (higher tier = more rigor):',
1656
- choices: [
1657
- {
1658
- name: '🔴 Tier 1 - Critical (auth, billing, migrations) - Max rigor',
1659
- value: 1,
1660
- short: 'Critical',
1661
- },
1662
- {
1663
- name: '🟡 Tier 2 - Standard (features, APIs) - Standard rigor',
1664
- value: 2,
1665
- short: 'Standard',
1666
- },
1667
- {
1668
- name: 'đŸŸĸ Tier 3 - Low Risk (UI, tooling) - Basic rigor',
1669
- value: 3,
1670
- short: 'Low Risk',
1671
- },
1672
- ],
1673
- default: (answers) => {
1674
- const typeDefaults = {
1675
- extension: 2,
1676
- library: 2,
1677
- api: 1,
1678
- cli: 3,
1679
- monorepo: 1,
1680
- application: 2,
1681
- };
1682
- return typeDefaults[answers.projectType] || 2;
1683
- },
1684
- },
1685
- {
1686
- type: 'list',
1687
- name: 'projectMode',
1688
- message: 'đŸŽ¯ Primary development mode:',
1689
- choices: [
1690
- { name: '✨ feature (new functionality)', value: 'feature' },
1691
- { name: '🔄 refactor (code restructuring)', value: 'refactor' },
1692
- { name: '🐛 fix (bug fixes)', value: 'fix' },
1693
- { name: '📚 doc (documentation)', value: 'doc' },
1694
- { name: '🧹 chore (maintenance)', value: 'chore' },
1695
- ],
1696
- default: 'feature',
1697
- },
1698
- {
1699
- type: 'number',
1700
- name: 'maxFiles',
1701
- message: '📊 Max files to change per feature:',
1702
- default: (answers) => {
1703
- const tierDefaults = { 1: 40, 2: 25, 3: 15 };
1704
- const typeAdjustments = {
1705
- extension: -5,
1706
- library: -10,
1707
- api: 10,
1708
- cli: -10,
1709
- monorepo: 25,
1710
- application: 0,
1711
- };
1712
- return Math.max(
1713
- 5,
1714
- tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
1715
- );
1716
- },
1717
- },
1718
- {
1719
- type: 'number',
1720
- name: 'maxLoc',
1721
- message: '📏 Max lines of code to change per feature:',
1722
- default: (answers) => {
1723
- const tierDefaults = { 1: 1500, 2: 1000, 3: 600 };
1724
- const typeAdjustments = {
1725
- extension: -200,
1726
- library: -300,
1727
- api: 500,
1728
- cli: -400,
1729
- monorepo: 1000,
1730
- application: 0,
1731
- };
1732
- return Math.max(
1733
- 50,
1734
- tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
1735
- );
1736
- },
1737
- },
1738
- {
1739
- type: 'input',
1740
- name: 'blastModules',
1741
- message: 'đŸ’Ĩ Affected modules (comma-separated):',
1742
- default: (answers) => {
1743
- const typeDefaults = {
1744
- extension: 'core,webview',
1745
- library: 'components,utils',
1746
- api: 'routes,models,controllers',
1747
- cli: 'commands,utils',
1748
- monorepo: 'shared,packages',
1749
- application: 'ui,logic,data',
1750
- };
1751
- return typeDefaults[answers.projectType] || 'core,ui';
1752
- },
1753
- },
1754
- {
1755
- type: 'confirm',
1756
- name: 'dataMigration',
1757
- message: 'đŸ—„ī¸ Requires data migration?',
1758
- default: false,
1759
- },
1760
- {
1761
- type: 'list',
1762
- name: 'rollbackSlo',
1763
- message: 'âąī¸ Operational rollback SLO:',
1764
- choices: [
1765
- { name: '⚡ 1 minute (critical systems)', value: '1m' },
1766
- { name: '🟡 5 minutes (standard)', value: '5m' },
1767
- { name: '🟠 15 minutes (complex)', value: '15m' },
1768
- { name: '🔴 1 hour (data migration)', value: '1h' },
1769
- ],
1770
- default: '5m',
1771
- },
1772
- {
1773
- type: 'confirm',
1774
- name: 'enableCursorHooks',
1775
- message: '📌 Enable Cursor hooks for real-time quality gates?',
1776
- default: true,
1777
- },
1778
- {
1779
- type: 'checkbox',
1780
- name: 'cursorHookLevels',
1781
- message: '📌 Which Cursor hooks should be enabled?',
1782
- when: (answers) => answers.enableCursorHooks,
1783
- choices: [
1784
- { name: 'Safety (secrets, PII, dangerous commands)', value: 'safety', checked: true },
1785
- { name: 'Quality (formatting, linting, validation)', value: 'quality', checked: true },
1786
- {
1787
- name: 'Scope guards (file scope, naming conventions)',
1788
- value: 'scope',
1789
- checked: true,
1790
- },
1791
- { name: 'Audit trail (provenance tracking)', value: 'audit', checked: true },
1792
- ],
1793
- default: ['safety', 'quality', 'scope', 'audit'],
1794
- },
1795
- {
1796
- type: 'confirm',
1797
- name: 'configureGit',
1798
- message: '🔧 Configure git author information for commits?',
1799
- default: true,
1800
- },
1801
- {
1802
- type: 'input',
1803
- name: 'gitAuthorName',
1804
- message: '👤 Git author name:',
1805
- when: (answers) => answers.configureGit,
1806
- default: () => {
1807
- try {
1808
- return require('child_process')
1809
- .execSync('git config user.name', { encoding: 'utf8' })
1810
- .trim();
1811
- } catch {
1812
- return '';
1813
- }
1814
- },
1815
- validate: (input) => input.length > 0 || 'Git author name is required',
1816
- },
1817
- {
1818
- type: 'input',
1819
- name: 'gitAuthorEmail',
1820
- message: '📧 Git author email:',
1821
- when: (answers) => answers.configureGit,
1822
- default: () => {
1823
- try {
1824
- return require('child_process')
1825
- .execSync('git config user.email', { encoding: 'utf8' })
1826
- .trim();
1827
- } catch {
1828
- return '';
1829
- }
1830
- },
1831
- validate: (input) => {
1832
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1833
- return emailRegex.test(input) || 'Please enter a valid email address';
1834
- },
1835
- },
1836
- ];
1837
-
1838
- console.log(chalk.cyan('âŗ Gathering project requirements...'));
1839
-
1840
- let wizardAnswers;
1841
- try {
1842
- wizardAnswers = await inquirer.prompt(wizardQuestions);
1843
- } catch (error) {
1844
- if (error.isTtyError) {
1845
- console.error(chalk.red('❌ Interactive prompts not supported in this environment'));
1846
- console.error(chalk.blue('💡 Run with --non-interactive flag to use defaults'));
1847
- process.exit(1);
1848
- } else {
1849
- console.error(chalk.red('❌ Error during interactive setup:'), error.message);
1850
- process.exit(1);
1851
- }
1852
- }
1853
-
1854
- console.log(chalk.green('✅ Project requirements gathered successfully!'));
1855
-
1856
- // Show summary before generating spec
1857
- console.log(chalk.bold('\n📋 Configuration Summary:'));
1858
- console.log(` ${chalk.cyan('Type')}: ${wizardAnswers.projectType}`);
1859
- console.log(` ${chalk.cyan('Project')}: ${wizardAnswers.projectTitle}`);
1860
- console.log(
1861
- ` ${chalk.cyan('Mode')}: ${wizardAnswers.projectMode} | ${chalk.cyan('Tier')}: ${wizardAnswers.riskTier}`
1862
- );
1863
- console.log(
1864
- ` ${chalk.cyan('Budget')}: ${wizardAnswers.maxFiles} files, ${wizardAnswers.maxLoc} lines`
1865
- );
1866
- console.log(
1867
- ` ${chalk.cyan('Data Migration')}: ${wizardAnswers.dataMigration ? 'Yes' : 'No'}`
1868
- );
1869
- console.log(` ${chalk.cyan('Rollback SLO')}: ${wizardAnswers.rollbackSlo}`);
1870
-
1871
- // Generate working spec using the template system
1872
- const analysis = {
1873
- projectType: wizardAnswers.projectType,
1874
- packageJson: { name: wizardAnswers.projectTitle },
1875
- hasTests: false,
1876
- hasLinting: false,
1877
- hasCi: false,
1878
- };
1879
-
1880
- const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
1881
-
1882
- // Override template-generated values with wizard answers
1883
- const spec = yaml.load(workingSpecContent);
1884
- spec.title = wizardAnswers.projectTitle;
1885
- spec.risk_tier = wizardAnswers.riskTier;
1886
- spec.mode = wizardAnswers.projectMode;
1887
- spec.change_budget = {
1888
- max_files: wizardAnswers.maxFiles,
1889
- max_loc: wizardAnswers.maxLoc,
1890
- };
1891
- spec.blast_radius = {
1892
- modules: wizardAnswers.blastModules
1893
- .split(',')
1894
- .map((m) => m.trim())
1895
- .filter((m) => m),
1896
- data_migration: wizardAnswers.dataMigration,
1897
- };
1898
- spec.operational_rollback_slo = wizardAnswers.rollbackSlo;
1899
-
1900
- // Add git configuration if provided
1901
- if (
1902
- wizardAnswers.configureGit &&
1903
- wizardAnswers.gitAuthorName &&
1904
- wizardAnswers.gitAuthorEmail
1905
- ) {
1906
- spec.git_config = {
1907
- author_name: wizardAnswers.gitAuthorName,
1908
- author_email: wizardAnswers.gitAuthorEmail,
1909
- };
1910
- }
1911
-
1912
- // Validate the generated spec
1913
- validateGeneratedSpec(yaml.dump(spec), wizardAnswers);
1914
-
1915
- // Save the working spec
1916
- await fs.writeFile('.caws/working-spec.yaml', yaml.dump(spec, { indent: 2 }));
1917
-
1918
- console.log(chalk.green('✅ Working spec generated and validated'));
1919
-
1920
- // Generate getting started guide
1921
- const wizardAnalysis = {
1922
- projectType: wizardAnswers.projectType,
1923
- packageJson: { name: wizardAnswers.projectTitle },
1924
- hasTests: false,
1925
- hasLinting: false,
1926
- hasCi: false,
1927
- };
1928
-
1929
- const guideContent = generateGettingStartedGuide(wizardAnalysis);
1930
- await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
1931
- console.log(chalk.green('✅ Getting started guide created'));
1932
-
1933
- // Generate or update .gitignore with CAWS patterns
1934
- const existingGitignore = fs.existsSync('.gitignore')
1935
- ? fs.readFileSync('.gitignore', 'utf8')
1936
- : '';
1937
-
1938
- const updatedGitignore = generateGitignorePatterns(existingGitignore);
1939
- if (updatedGitignore !== existingGitignore) {
1940
- await fs.writeFile('.gitignore', updatedGitignore);
1941
- const action = existingGitignore.trim() ? 'updated' : 'created';
1942
- console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
1943
- }
1944
-
1945
- // Finalize project with provenance and git initialization
1946
- await finalizeProject(projectName, options, wizardAnswers);
1947
-
1948
- continueToSuccess();
1949
- return;
1950
- }
1951
-
1952
- // Handle template-based setup
1953
- if (options.template) {
1954
- console.log(chalk.cyan(`đŸŽ¯ Using ${options.template} template`));
1955
-
1956
- const validTemplates = ['extension', 'library', 'api', 'cli', 'monorepo'];
1957
- if (!validTemplates.includes(options.template)) {
1958
- console.error(chalk.red(`❌ Invalid template: ${options.template}`));
1959
- console.error(chalk.blue(`💡 Valid templates: ${validTemplates.join(', ')}`));
1960
- process.exit(1);
1961
- }
1962
-
1963
- const analysis = {
1964
- projectType: options.template,
1965
- packageJson: { name: projectName },
1966
- hasTests: false,
1967
- hasLinting: false,
1968
- hasCi: false,
1969
- };
1970
-
1971
- const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
1972
-
1973
- // Validate the generated spec
1974
- validateGeneratedSpec(workingSpecContent, { projectType: options.template });
1975
-
1976
- // Save the working spec
1977
- await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
1978
-
1979
- console.log(chalk.green('✅ Working spec generated from template'));
1980
-
1981
- // Generate getting started guide
1982
- const templateAnalysis = {
1983
- projectType: options.template,
1984
- packageJson: { name: projectName },
1985
- hasTests: false,
1986
- hasLinting: false,
1987
- hasCi: false,
1988
- };
1989
-
1990
- const guideContent = generateGettingStartedGuide(templateAnalysis);
1991
- await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
1992
- console.log(chalk.green('✅ Getting started guide created'));
1993
-
1994
- // Generate or update .gitignore with CAWS patterns
1995
- const existingGitignore = fs.existsSync('.gitignore')
1996
- ? fs.readFileSync('.gitignore', 'utf8')
1997
- : '';
1998
-
1999
- const updatedGitignore = generateGitignorePatterns(existingGitignore);
2000
- if (updatedGitignore !== existingGitignore) {
2001
- await fs.writeFile('.gitignore', updatedGitignore);
2002
- const action = existingGitignore.trim() ? 'updated' : 'created';
2003
- console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
2004
- }
2005
-
2006
- // Finalize project
2007
- await finalizeProject(projectName, options, { projectType: options.template });
2008
-
2009
- continueToSuccess();
2010
- return;
2011
- }
2012
-
2013
- // Set default answers for non-interactive mode
2014
- if (!options.interactive || options.nonInteractive) {
2015
- // Use directory name for current directory init
2016
- const displayName = initInCurrentDir ? path.basename(process.cwd()) : projectName;
2017
-
2018
- answers = {
2019
- projectId: displayName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
2020
- projectTitle: displayName.charAt(0).toUpperCase() + displayName.slice(1).replace(/-/g, ' '),
2021
- riskTier: 2,
2022
- projectMode: 'feature',
2023
- maxFiles: 25,
2024
- maxLoc: 1000,
2025
- blastModules: 'core,ui',
2026
- dataMigration: false,
2027
- rollbackSlo: '5m',
2028
- projectThreats: 'Standard project threats',
2029
- scopeIn: 'project files',
2030
- scopeOut: 'external dependencies',
2031
- projectInvariants: 'System maintains consistency',
2032
- acceptanceCriteria: 'GIVEN current state WHEN action THEN expected result',
2033
- a11yRequirements: 'keyboard navigation, screen reader support',
2034
- perfBudget: 250,
2035
- securityRequirements: 'input validation, authentication',
2036
- contractType: 'openapi',
2037
- contractPath: 'apps/contracts/api.yaml',
2038
- observabilityLogs: 'auth.success,api.request',
2039
- observabilityMetrics: 'requests_total',
2040
- observabilityTraces: 'api_flow',
2041
- migrationPlan: 'Standard deployment process',
2042
- rollbackPlan: 'Feature flag disable and rollback',
2043
- needsOverride: false,
2044
- overrideRationale: '',
2045
- overrideApprover: '',
2046
- waivedGates: [],
2047
- overrideExpiresDays: 7,
2048
- isExperimental: false,
2049
- experimentalRationale: '',
2050
- experimentalSandbox: 'experimental/',
2051
- experimentalExpiresDays: 14,
2052
- aiConfidence: 7,
2053
- uncertaintyAreas: '',
2054
- complexityFactors: '',
2055
- };
2056
-
2057
- // Generate working spec for non-interactive mode
2058
- const workingSpecContent = generateWorkingSpec(answers);
2059
-
2060
- // Validate the generated spec
2061
- validateGeneratedSpec(workingSpecContent, answers);
2062
-
2063
- // Save the working spec
2064
- await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
2065
-
2066
- console.log(chalk.green('✅ Working spec generated and validated'));
2067
-
2068
- // Generate getting started guide (detect project type)
2069
- const detectedType = detectProjectType(process.cwd());
2070
- const defaultAnalysis = {
2071
- projectType: detectedType,
2072
- packageJson: { name: displayName },
2073
- hasTests: false,
2074
- hasLinting: false,
2075
- hasCi: false,
2076
- };
2077
-
2078
- const guideContent = generateGettingStartedGuide(defaultAnalysis);
2079
- await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
2080
- console.log(chalk.green('✅ Getting started guide created'));
2081
-
2082
- // Generate or update .gitignore with CAWS patterns
2083
- const existingGitignore = fs.existsSync('.gitignore')
2084
- ? fs.readFileSync('.gitignore', 'utf8')
2085
- : '';
2086
-
2087
- const updatedGitignore = generateGitignorePatterns(existingGitignore);
2088
- if (updatedGitignore !== existingGitignore) {
2089
- await fs.writeFile('.gitignore', updatedGitignore);
2090
- const action = existingGitignore.trim() ? 'updated' : 'created';
2091
- console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
2092
- }
2093
-
2094
- // Finalize project with provenance and git initialization
2095
- await finalizeProject(projectName, options, answers);
2096
-
2097
- continueToSuccess();
2098
- return;
2099
- }
2100
-
2101
- if (options.interactive && !options.nonInteractive) {
2102
- // Interactive setup with enhanced prompts
2103
- console.log(chalk.cyan('🔧 Starting interactive project configuration...'));
2104
- console.log(chalk.blue('💡 Press Ctrl+C at any time to exit and use defaults'));
2105
-
2106
- const questions = [
2107
- {
2108
- type: 'input',
2109
- name: 'projectId',
2110
- message: '📋 Project ID (e.g., FEAT-1234, AUTH-456):',
2111
- default: projectName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
2112
- validate: (input) => {
2113
- if (!input.trim()) return 'Project ID is required';
2114
- const pattern = /^[A-Z]+-\d+$/;
2115
- if (!pattern.test(input)) {
2116
- return 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)';
2117
- }
2118
- return true;
2119
- },
2120
- },
2121
- {
2122
- type: 'input',
2123
- name: 'projectTitle',
2124
- message: '📝 Project Title (descriptive name):',
2125
- default: projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
2126
- validate: (input) => {
2127
- if (!input.trim()) return 'Project title is required';
2128
- if (input.trim().length < 8) {
2129
- return 'Project title should be at least 8 characters long';
2130
- }
2131
- return true;
2132
- },
2133
- },
2134
- {
2135
- type: 'list',
2136
- name: 'riskTier',
2137
- message: 'âš ī¸ Risk Tier (higher tier = more rigor):',
2138
- choices: [
2139
- {
2140
- name: '🔴 Tier 1 - Critical (auth, billing, migrations) - Max rigor',
2141
- value: 1,
2142
- },
2143
- {
2144
- name: '🟡 Tier 2 - Standard (features, APIs) - Standard rigor',
2145
- value: 2,
2146
- },
2147
- {
2148
- name: 'đŸŸĸ Tier 3 - Low Risk (UI, tooling) - Basic rigor',
2149
- value: 3,
2150
- },
2151
- ],
2152
- default: 2,
2153
- },
2154
- {
2155
- type: 'list',
2156
- name: 'projectMode',
2157
- message: 'đŸŽ¯ Project Mode:',
2158
- choices: [
2159
- { name: '✨ feature (new functionality)', value: 'feature' },
2160
- { name: '🔄 refactor (code restructuring)', value: 'refactor' },
2161
- { name: '🐛 fix (bug fixes)', value: 'fix' },
2162
- { name: '📚 doc (documentation)', value: 'doc' },
2163
- { name: '🧹 chore (maintenance)', value: 'chore' },
2164
- ],
2165
- default: 'feature',
2166
- },
2167
- {
2168
- type: 'number',
2169
- name: 'maxFiles',
2170
- message: '📊 Max files to change:',
2171
- default: (answers) => {
2172
- // Dynamic defaults based on risk tier
2173
- switch (answers.riskTier) {
2174
- case 1:
2175
- return 40;
2176
- case 2:
2177
- return 25;
2178
- case 3:
2179
- return 15;
2180
- default:
2181
- return 25;
2182
- }
2183
- },
2184
- validate: (input) => {
2185
- if (input < 1) return 'Must change at least 1 file';
2186
- return true;
2187
- },
2188
- },
2189
- {
2190
- type: 'number',
2191
- name: 'maxLoc',
2192
- message: '📏 Max lines of code to change:',
2193
- default: (answers) => {
2194
- // Dynamic defaults based on risk tier
2195
- switch (answers.riskTier) {
2196
- case 1:
2197
- return 1500;
2198
- case 2:
2199
- return 1000;
2200
- case 3:
2201
- return 600;
2202
- default:
2203
- return 1000;
2204
- }
2205
- },
2206
- validate: (input) => {
2207
- if (input < 1) return 'Must change at least 1 line';
2208
- return true;
2209
- },
2210
- },
2211
- {
2212
- type: 'input',
2213
- name: 'blastModules',
2214
- message: 'đŸ’Ĩ Blast Radius - Affected modules (comma-separated):',
2215
- default: 'core,api',
2216
- validate: (input) => {
2217
- if (!input.trim()) return 'At least one module must be specified';
2218
- return true;
2219
- },
2220
- },
2221
- {
2222
- type: 'confirm',
2223
- name: 'dataMigration',
2224
- message: 'đŸ—„ī¸ Requires data migration?',
2225
- default: false,
2226
- },
2227
- {
2228
- type: 'input',
2229
- name: 'rollbackSlo',
2230
- message: 'âąī¸ Operational rollback SLO (e.g., 5m, 1h, 24h):',
2231
- default: '5m',
2232
- validate: (input) => {
2233
- const pattern = /^([0-9]+m|[0-9]+h|[0-9]+d)$/;
2234
- if (!pattern.test(input)) {
2235
- return 'SLO should be in format: NUMBER + m/h/d (e.g., 5m, 1h, 24h)';
2236
- }
2237
- return true;
2238
- },
2239
- },
2240
- {
2241
- type: 'editor',
2242
- name: 'projectThreats',
2243
- message: 'âš ī¸ Project Threats & Risks (one per line, ESC to finish):',
2244
- default: (answers) => {
2245
- const baseThreats =
2246
- '- Race condition in concurrent operations\n- Performance degradation under load';
2247
- if (answers.dataMigration) {
2248
- return baseThreats + '\n- Data migration failure\n- Inconsistent data state';
2249
- }
2250
- return baseThreats;
2251
- },
2252
- },
2253
- {
2254
- type: 'input',
2255
- name: 'scopeIn',
2256
- message: "đŸŽ¯ Scope IN - What's included (comma-separated):",
2257
- default: (answers) => {
2258
- if (answers.projectMode === 'feature') return 'user authentication, api endpoints';
2259
- if (answers.projectMode === 'refactor') return 'authentication module, user service';
2260
- if (answers.projectMode === 'fix') return 'error handling, validation';
2261
- return 'project files';
2262
- },
2263
- validate: (input) => {
2264
- if (!input.trim()) return 'At least one scope item must be specified';
2265
- return true;
2266
- },
2267
- },
2268
- {
2269
- type: 'input',
2270
- name: 'scopeOut',
2271
- message: "đŸšĢ Scope OUT - What's excluded (comma-separated):",
2272
- default: (answers) => {
2273
- if (answers.projectMode === 'feature')
2274
- return 'legacy authentication, deprecated endpoints';
2275
- if (answers.projectMode === 'refactor')
2276
- return 'external dependencies, configuration files';
2277
- return 'unrelated features';
2278
- },
2279
- },
2280
- {
2281
- type: 'editor',
2282
- name: 'projectInvariants',
2283
- message: 'đŸ›Ąī¸ System Invariants (one per line, ESC to finish):',
2284
- default:
2285
- '- System remains available\n- Data consistency maintained\n- User sessions preserved',
2286
- },
2287
- {
2288
- type: 'editor',
2289
- name: 'acceptanceCriteria',
2290
- message: '✅ Acceptance Criteria (GIVEN...WHEN...THEN, one per line, ESC to finish):',
2291
- default: (answers) => {
2292
- if (answers.projectMode === 'feature') {
2293
- return 'GIVEN user is authenticated WHEN accessing protected endpoint THEN access is granted\nGIVEN invalid credentials WHEN attempting login THEN access is denied';
2294
- }
2295
- if (answers.projectMode === 'fix') {
2296
- return 'GIVEN existing functionality WHEN applying fix THEN behavior is preserved\nGIVEN error condition WHEN fix is applied THEN error is resolved';
2297
- }
2298
- return 'GIVEN current system state WHEN change is applied THEN expected behavior occurs';
2299
- },
2300
- validate: (input) => {
2301
- if (!input.trim()) return 'At least one acceptance criterion is required';
2302
- const lines = input
2303
- .trim()
2304
- .split('\n')
2305
- .filter((line) => line.trim());
2306
- if (lines.length === 0) return 'At least one acceptance criterion is required';
2307
- return true;
2308
- },
2309
- },
2310
- {
2311
- type: 'input',
2312
- name: 'a11yRequirements',
2313
- message: 'â™ŋ Accessibility Requirements (comma-separated):',
2314
- default: 'keyboard navigation, screen reader support, color contrast',
2315
- },
2316
- {
2317
- type: 'number',
2318
- name: 'perfBudget',
2319
- message: '⚡ Performance Budget (API p95 latency in ms):',
2320
- default: 250,
2321
- validate: (input) => {
2322
- if (input < 1) return 'Performance budget must be at least 1ms';
2323
- if (input > 10000) return 'Performance budget seems too high (max 10s)';
2324
- return true;
2325
- },
2326
- },
2327
- {
2328
- type: 'input',
2329
- name: 'securityRequirements',
2330
- message: '🔒 Security Requirements (comma-separated):',
2331
- default: 'input validation, rate limiting, authentication, authorization',
2332
- },
2333
- {
2334
- type: 'list',
2335
- name: 'contractType',
2336
- message: '📄 Contract Type:',
2337
- choices: [
2338
- { name: 'OpenAPI (REST APIs)', value: 'openapi' },
2339
- { name: 'GraphQL Schema', value: 'graphql' },
2340
- { name: 'Protocol Buffers', value: 'proto' },
2341
- { name: 'Pact (consumer-driven)', value: 'pact' },
2342
- ],
2343
- default: 'openapi',
2344
- },
2345
- {
2346
- type: 'input',
2347
- name: 'contractPath',
2348
- message: '📁 Contract File Path:',
2349
- default: (answers) => {
2350
- if (answers.contractType === 'openapi') return 'apps/contracts/api.yaml';
2351
- if (answers.contractType === 'graphql') return 'apps/contracts/schema.graphql';
2352
- if (answers.contractType === 'proto') return 'apps/contracts/service.proto';
2353
- if (answers.contractType === 'pact') return 'apps/contracts/pacts/';
2354
- return 'apps/contracts/api.yaml';
2355
- },
2356
- },
2357
- {
2358
- type: 'input',
2359
- name: 'observabilityLogs',
2360
- message: '📝 Observability - Log Events (comma-separated):',
2361
- default: 'auth.success, auth.failure, api.request, api.response',
2362
- },
2363
- {
2364
- type: 'input',
2365
- name: 'observabilityMetrics',
2366
- message: '📊 Observability - Metrics (comma-separated):',
2367
- default: 'auth_attempts_total, auth_success_total, api_requests_total, api_errors_total',
2368
- },
2369
- {
2370
- type: 'input',
2371
- name: 'observabilityTraces',
2372
- message: '🔍 Observability - Traces (comma-separated):',
2373
- default: 'auth_flow, api_request',
2374
- },
2375
- {
2376
- type: 'editor',
2377
- name: 'migrationPlan',
2378
- message: '🔄 Migration Plan (one per line, ESC to finish):',
2379
- default: (answers) => {
2380
- if (answers.dataMigration) {
2381
- return '- Create new database schema\n- Add new auth table\n- Migrate existing users\n- Validate data integrity';
2382
- }
2383
- return '- Deploy feature flags\n- Roll out gradually\n- Monitor metrics';
2384
- },
2385
- validate: (input) => {
2386
- if (!input.trim()) return 'Migration plan is required';
2387
- return true;
2388
- },
2389
- },
2390
- {
2391
- type: 'editor',
2392
- name: 'rollbackPlan',
2393
- message: '🔙 Rollback Plan (one per line, ESC to finish):',
2394
- default: (answers) => {
2395
- if (answers.dataMigration) {
2396
- return '- Feature flag kill-switch\n- Database rollback script\n- Restore from backup\n- Verify system state';
2397
- }
2398
- return '- Feature flag disable\n- Deploy previous version\n- Monitor for issues';
2399
- },
2400
- validate: (input) => {
2401
- if (!input.trim()) return 'Rollback plan is required';
2402
- return true;
2403
- },
2404
- },
2405
- {
2406
- type: 'confirm',
2407
- name: 'needsOverride',
2408
- message: '🚨 Need human override for urgent/low-risk change?',
2409
- default: false,
2410
- },
2411
- {
2412
- type: 'input',
2413
- name: 'overrideRationale',
2414
- message: '📝 Override rationale (urgency, low risk, etc.):',
2415
- when: (answers) => answers.needsOverride,
2416
- validate: (input) => {
2417
- if (!input.trim()) return 'Rationale is required for override';
2418
- return true;
2419
- },
2420
- },
2421
- {
2422
- type: 'input',
2423
- name: 'overrideApprover',
2424
- message: '👤 Override approver (GitHub username or email):',
2425
- when: (answers) => answers.needsOverride,
2426
- validate: (input) => {
2427
- if (!input.trim()) return 'Approver is required for override';
2428
- return true;
2429
- },
2430
- },
2431
- {
2432
- type: 'checkbox',
2433
- name: 'waivedGates',
2434
- message: 'âš ī¸ Gates to waive (select with space):',
2435
- choices: [
2436
- { name: 'Coverage testing', value: 'coverage' },
2437
- { name: 'Mutation testing', value: 'mutation' },
2438
- { name: 'Contract testing', value: 'contracts' },
2439
- { name: 'Manual review', value: 'manual_review' },
2440
- { name: 'Trust score check', value: 'trust_score' },
2441
- ],
2442
- when: (answers) => answers.needsOverride,
2443
- validate: (input) => {
2444
- if (input.length === 0) return 'At least one gate must be waived';
2445
- return true;
2446
- },
2447
- },
2448
- {
2449
- type: 'number',
2450
- name: 'overrideExpiresDays',
2451
- message: '⏰ Override expires in how many days?',
2452
- default: 7,
2453
- when: (answers) => answers.needsOverride,
2454
- validate: (input) => {
2455
- if (input < 1) return 'Must expire in at least 1 day';
2456
- if (input > 30) return 'Cannot exceed 30 days';
2457
- return true;
2458
- },
2459
- },
2460
- {
2461
- type: 'confirm',
2462
- name: 'isExperimental',
2463
- message: 'đŸ§Ē Experimental/Prototype mode? (Reduced requirements for sandbox code)',
2464
- default: false,
2465
- },
2466
- {
2467
- type: 'input',
2468
- name: 'experimentalRationale',
2469
- message: 'đŸ”Ŧ Experimental rationale (what are you exploring?):',
2470
- when: (answers) => answers.isExperimental,
2471
- validate: (input) => {
2472
- if (!input.trim()) return 'Rationale is required for experimental mode';
2473
- return true;
2474
- },
2475
- },
2476
- {
2477
- type: 'input',
2478
- name: 'experimentalSandbox',
2479
- message: '📁 Sandbox location (directory or feature flag):',
2480
- default: 'experimental/',
2481
- when: (answers) => answers.isExperimental,
2482
- validate: (input) => {
2483
- if (!input.trim()) return 'Sandbox location is required';
2484
- return true;
2485
- },
2486
- },
2487
- {
2488
- type: 'number',
2489
- name: 'experimentalExpiresDays',
2490
- message: '⏰ Experimental code expires in how many days?',
2491
- default: 14,
2492
- when: (answers) => answers.isExperimental,
2493
- validate: (input) => {
2494
- if (input < 1) return 'Must expire in at least 1 day';
2495
- if (input > 90) return 'Cannot exceed 90 days for experimental code';
2496
- return true;
2497
- },
2498
- },
2499
- {
2500
- type: 'number',
2501
- name: 'aiConfidence',
2502
- message: '🤖 AI confidence level (1-10, 10 = very confident):',
2503
- default: 7,
2504
- validate: (input) => {
2505
- if (input < 1 || input > 10) return 'Must be between 1 and 10';
2506
- return true;
2507
- },
2508
- },
2509
- {
2510
- type: 'input',
2511
- name: 'uncertaintyAreas',
2512
- message: '❓ Areas of uncertainty (comma-separated):',
2513
- default: '',
2514
- },
2515
- {
2516
- type: 'input',
2517
- name: 'complexityFactors',
2518
- message: '🔧 Complexity factors (comma-separated):',
2519
- default: '',
2520
- },
2521
- ];
2522
-
2523
- console.log(chalk.cyan('âŗ Gathering project requirements...'));
2524
-
2525
- let answers;
2526
- try {
2527
- answers = await inquirer.prompt(questions);
2528
- } catch (error) {
2529
- if (error.isTtyError) {
2530
- console.error(chalk.red('❌ Interactive prompts not supported in this environment'));
2531
- console.error(chalk.blue('💡 Run with --non-interactive flag to use defaults'));
2532
- process.exit(1);
2533
- } else {
2534
- console.error(chalk.red('❌ Error during interactive setup:'), error.message);
2535
- process.exit(1);
2536
- }
2537
- }
2538
-
2539
- console.log(chalk.green('✅ Project requirements gathered successfully!'));
2540
-
2541
- // Show summary before generating spec
2542
- console.log(chalk.bold('\n📋 Configuration Summary:'));
2543
- console.log(` ${chalk.cyan('Project')}: ${answers.projectTitle} (${answers.projectId})`);
2544
- console.log(
2545
- ` ${chalk.cyan('Mode')}: ${answers.projectMode} | ${chalk.cyan('Tier')}: ${answers.riskTier}`
2546
- );
2547
- console.log(` ${chalk.cyan('Budget')}: ${answers.maxFiles} files, ${answers.maxLoc} lines`);
2548
- console.log(` ${chalk.cyan('Data Migration')}: ${answers.dataMigration ? 'Yes' : 'No'}`);
2549
- console.log(` ${chalk.cyan('Rollback SLO')}: ${answers.rollbackSlo}`);
2550
-
2551
- // Generate working spec
2552
- const workingSpecContent = generateWorkingSpec(answers);
2553
-
2554
- // Validate the generated spec
2555
- validateGeneratedSpec(workingSpecContent, answers);
2556
-
2557
- // Save the working spec
2558
- await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
2559
-
2560
- console.log(chalk.green('✅ Working spec generated and validated'));
2561
-
2562
- // Finalize project with provenance and git initialization
2563
- await finalizeProject(projectName, options, answers);
2564
-
2565
- continueToSuccess();
2566
- }
2567
- } catch (error) {
2568
- console.error(chalk.red('❌ Error during project initialization:'), error.message);
2569
-
2570
- // Cleanup on error
2571
- if (fs.existsSync(projectName)) {
2572
- console.log(chalk.cyan('🧹 Cleaning up failed initialization...'));
2573
- try {
2574
- await fs.remove(projectName);
2575
- console.log(chalk.green('✅ Cleanup completed'));
2576
- } catch (cleanupError) {
2577
- console.warn(
2578
- chalk.yellow('âš ī¸ Could not clean up:'),
2579
- cleanupError?.message || cleanupError
2580
- );
2581
- }
2582
- }
2583
-
2584
- process.exit(1);
2585
- }
2586
- }
2587
-
2588
- // Generate provenance manifest and git initialization (for both modes)
2589
- async function finalizeProject(projectName, options, answers) {
2590
- try {
2591
- // Detect and configure language support
2592
- if (languageSupport) {
2593
- console.log(chalk.cyan('🔍 Detecting project language...'));
2594
- const detectedLanguage = languageSupport.detectProjectLanguage();
2595
-
2596
- if (detectedLanguage !== 'unknown') {
2597
- console.log(chalk.green(`✅ Detected language: ${detectedLanguage}`));
2598
-
2599
- // Generate language-specific configuration
2600
- try {
2601
- const langConfig = languageSupport.generateLanguageConfig(
2602
- detectedLanguage,
2603
- '.caws/language-config.json'
2604
- );
2605
-
2606
- console.log(chalk.green('✅ Generated language-specific configuration'));
2607
- console.log(` Language: ${langConfig.name}`);
2608
- console.log(` Tier: ${langConfig.tier}`);
2609
- console.log(
2610
- ` Thresholds: Branch â‰Ĩ${langConfig.thresholds.min_branch * 100}%, Mutation â‰Ĩ${langConfig.thresholds.min_mutation * 100}%`
2611
- );
2612
- } catch (langError) {
2613
- console.warn(chalk.yellow('âš ī¸ Could not generate language config:'), langError.message);
2614
- }
2615
- } else {
2616
- console.log(
2617
- chalk.blue('â„šī¸ Could not detect project language - using default configuration')
2618
- );
2619
- }
2620
- }
2621
-
2622
- // Setup Cursor hooks if enabled
2623
- if (answers && answers.enableCursorHooks) {
2624
- console.log(chalk.cyan('📌 Setting up Cursor hooks...'));
2625
- await scaffoldCursorHooks(
2626
- process.cwd(),
2627
- answers.cursorHookLevels || ['safety', 'quality', 'scope', 'audit']
2628
- );
2629
- }
2630
-
2631
- // Generate provenance manifest
2632
- console.log(chalk.cyan('đŸ“Ļ Generating provenance manifest...'));
2633
-
2634
- const provenanceData = {
2635
- agent: 'caws-cli',
2636
- model: 'cli-interactive',
2637
- modelHash: CLI_VERSION,
2638
- toolAllowlist: [
2639
- 'node',
2640
- 'npm',
2641
- 'git',
2642
- 'fs-extra',
2643
- 'inquirer',
2644
- 'commander',
2645
- 'js-yaml',
2646
- 'ajv',
2647
- 'chalk',
2648
- ],
2649
- prompts: Object.keys(answers),
2650
- commit: null, // Will be set after git init
2651
- artifacts: ['.caws/working-spec.yaml'],
2652
- results: {
2653
- project_id: answers.projectId,
2654
- project_title: answers.projectTitle,
2655
- risk_tier: answers.riskTier,
2656
- mode: answers.projectMode,
2657
- change_budget: {
2658
- max_files: answers.maxFiles,
2659
- max_loc: answers.maxLoc,
2660
- },
2661
- },
2662
- approvals: [],
2663
- };
2664
-
2665
- // Generate provenance if tools are available
2666
- const tools = loadProvenanceTools();
2667
- if (
2668
- tools &&
2669
- typeof tools.generateProvenance === 'function' &&
2670
- typeof tools.saveProvenance === 'function'
2671
- ) {
2672
- const provenance = tools.generateProvenance(provenanceData);
2673
- await tools.saveProvenance(provenance, '.agent/provenance.json');
2674
- console.log(chalk.green('✅ Provenance manifest generated'));
2675
- } else {
2676
- console.log(
2677
- chalk.yellow('âš ī¸ Provenance tools not available - skipping manifest generation')
2678
- );
2679
- }
2680
-
2681
- // Initialize git repository
2682
- if (options.git) {
2683
- try {
2684
- console.log(chalk.cyan('🔧 Initializing git repository...'));
2685
-
2686
- // Check if git is available
2687
- try {
2688
- require('child_process').execSync('git --version', { stdio: 'ignore' });
2689
- } catch (error) {
2690
- console.warn(chalk.yellow('âš ī¸ Git not found. Skipping git initialization.'));
2691
- console.warn(chalk.blue('💡 Install git to enable automatic repository setup.'));
2692
- return;
2693
- }
2694
-
2695
- // Configure git author information
2696
- const gitConfig = answers.git_config || {};
2697
- const authorName = process.env.GIT_AUTHOR_NAME || gitConfig.author_name;
2698
- const authorEmail = process.env.GIT_AUTHOR_EMAIL || gitConfig.author_email;
2699
-
2700
- if (authorName && authorEmail) {
2701
- require('child_process').execSync(`git config user.name "${authorName}"`, {
2702
- stdio: 'inherit',
2703
- });
2704
- require('child_process').execSync(`git config user.email "${authorEmail}"`, {
2705
- stdio: 'inherit',
2706
- });
2707
- console.log(chalk.green(`✅ Git configured: ${authorName} <${authorEmail}>`));
2708
- }
2709
-
2710
- require('child_process').execSync('git init', { stdio: 'inherit' });
2711
- require('child_process').execSync('git add .', { stdio: 'inherit' });
2712
- require('child_process').execSync('git commit -m "Initial CAWS project setup"', {
2713
- stdio: 'inherit',
2714
- });
2715
- console.log(chalk.green('✅ Git repository initialized'));
2716
-
2717
- // Update provenance with commit hash
2718
- const commitHash = require('child_process')
2719
- .execSync('git rev-parse HEAD', { encoding: 'utf8' })
2720
- .trim();
2721
- const currentProvenance = JSON.parse(fs.readFileSync('.agent/provenance.json', 'utf8'));
2722
- currentProvenance.commit = commitHash;
2723
- currentProvenance.hash = require('crypto')
2724
- .createHash('sha256')
2725
- .update(JSON.stringify(currentProvenance, Object.keys(currentProvenance).sort()))
2726
- .digest('hex');
2727
- await fs.writeFile('.agent/provenance.json', JSON.stringify(currentProvenance, null, 2));
2728
-
2729
- console.log(chalk.green('✅ Provenance updated with commit hash'));
2730
- } catch (error) {
2731
- console.warn(
2732
- chalk.yellow('âš ī¸ Failed to initialize git repository:'),
2733
- error?.message || String(error)
2734
- );
2735
- console.warn(chalk.blue('💡 You can initialize git manually later with:'));
2736
- console.warn(" git init && git add . && git commit -m 'Initial CAWS project setup'");
2737
- }
2738
- }
2739
- } catch (error) {
2740
- console.error(
2741
- chalk.red('❌ Error during project finalization:'),
2742
- error?.message || String(error)
2743
- );
2744
- }
2745
- }
2746
-
2747
- function continueToSuccess() {
2748
- const isCurrentDir =
2749
- process.cwd() ===
2750
- path.resolve(process.argv[3] === '.' ? process.cwd() : process.argv[3] || 'caws-project');
2751
-
2752
- console.log(chalk.green('\n🎉 CAWS project initialized successfully!'));
2753
-
2754
- if (isCurrentDir) {
2755
- console.log(
2756
- `📁 ${chalk.cyan('Initialized in current directory')}: ${path.resolve(process.cwd())}`
2757
- );
2758
- console.log(chalk.gray(' (CAWS files added to your existing project)'));
2759
- } else {
2760
- console.log(`📁 ${chalk.cyan('Project location')}: ${path.resolve(process.cwd())}`);
2761
- console.log(chalk.gray(' (New subdirectory created with CAWS structure)'));
2762
- }
2763
-
2764
- console.log(chalk.bold('\nNext steps:'));
2765
- console.log('1. Customize .caws/working-spec.yaml');
2766
- console.log('2. Review added CAWS tools and documentation');
2767
- if (!isCurrentDir) {
2768
- console.log('3. Move CAWS files to your main project if needed');
2769
- }
2770
- console.log('4. npm install (if using Node.js)');
2771
- console.log('5. Set up your CI/CD pipeline');
2772
- console.log(chalk.blue('\nFor help: caws --help'));
2773
- }
2774
-
2775
- /**
2776
- * Scaffold Cursor hooks for a CAWS project
2777
- *
2778
- * @param {string} projectDir - Project directory path
2779
- * @param {string[]} levels - Hook levels to enable (safety, quality, scope, audit)
2780
- * @author @darianrosebrook
2781
- */
2782
- async function scaffoldCursorHooks(projectDir, levels = ['safety', 'quality', 'scope', 'audit']) {
2783
- try {
2784
- const cursorDir = path.join(projectDir, '.cursor');
2785
- const cursorHooksDir = path.join(cursorDir, 'hooks');
2786
-
2787
- // Create .cursor directory structure
2788
- await fs.ensureDir(cursorDir);
2789
- await fs.ensureDir(cursorHooksDir);
2790
- await fs.ensureDir(path.join(cursorDir, 'logs'));
2791
-
2792
- // Determine template directory
2793
- const setup = detectCAWSSetup(projectDir);
2794
- const templateDir = setup.templateDir || path.resolve(__dirname, '../templates');
2795
-
2796
- const cursorTemplateDir = path.join(templateDir, '.cursor');
2797
- const cursorHooksTemplateDir = path.join(cursorTemplateDir, 'hooks');
2798
-
2799
- if (!fs.existsSync(cursorTemplateDir)) {
2800
- console.warn(chalk.yellow('âš ī¸ Cursor hooks templates not found'));
2801
- console.warn(chalk.blue('💡 Skipping Cursor hooks setup'));
2802
- return;
2803
- }
2804
-
2805
- // Map levels to hook scripts
2806
- const hookMapping = {
2807
- safety: ['scan-secrets.sh', 'block-dangerous.sh'],
2808
- quality: ['format.sh', 'validate-spec.sh'],
2809
- scope: ['scope-guard.sh', 'naming-check.sh'],
2810
- audit: ['audit.sh'],
2811
- };
2812
-
2813
- // Determine which hooks to enable
2814
- const enabledHooks = new Set();
2815
- levels.forEach((level) => {
2816
- const hooks = hookMapping[level] || [];
2817
- hooks.forEach((hook) => enabledHooks.add(hook));
2818
- });
2819
-
2820
- // Always enable audit.sh if any hooks are enabled
2821
- if (enabledHooks.size > 0) {
2822
- enabledHooks.add('audit.sh');
2823
- }
2824
-
2825
- // Copy enabled hook scripts
2826
- const allHookScripts = [
2827
- 'audit.sh',
2828
- 'validate-spec.sh',
2829
- 'format.sh',
2830
- 'scan-secrets.sh',
2831
- 'block-dangerous.sh',
2832
- 'scope-guard.sh',
2833
- 'naming-check.sh',
2834
- ];
2835
-
2836
- for (const script of allHookScripts) {
2837
- if (enabledHooks.has(script)) {
2838
- const sourcePath = path.join(cursorHooksTemplateDir, script);
2839
- const destPath = path.join(cursorHooksDir, script);
2840
-
2841
- if (fs.existsSync(sourcePath)) {
2842
- await fs.copy(sourcePath, destPath);
2843
- // Make executable
2844
- await fs.chmod(destPath, 0o755);
2845
- }
2846
- }
2847
- }
2848
-
2849
- // Generate hooks.json based on enabled hooks
2850
- const hooksConfig = {
2851
- version: 1,
2852
- hooks: {},
2853
- };
2854
-
2855
- // Build hooks configuration based on enabled levels
2856
- if (levels.includes('safety')) {
2857
- hooksConfig.hooks.beforeShellExecution = [
2858
- { command: './.cursor/hooks/block-dangerous.sh' },
2859
- { command: './.cursor/hooks/audit.sh' },
2860
- ];
2861
- hooksConfig.hooks.beforeMCPExecution = [{ command: './.cursor/hooks/audit.sh' }];
2862
- hooksConfig.hooks.beforeReadFile = [{ command: './.cursor/hooks/scan-secrets.sh' }];
2863
- }
2864
-
2865
- if (levels.includes('quality')) {
2866
- hooksConfig.hooks.afterFileEdit = hooksConfig.hooks.afterFileEdit || [];
2867
- hooksConfig.hooks.afterFileEdit.push(
2868
- { command: './.cursor/hooks/format.sh' },
2869
- { command: './.cursor/hooks/validate-spec.sh' }
2870
- );
2871
- }
2872
-
2873
- if (levels.includes('scope')) {
2874
- hooksConfig.hooks.afterFileEdit = hooksConfig.hooks.afterFileEdit || [];
2875
- hooksConfig.hooks.afterFileEdit.push({ command: './.cursor/hooks/naming-check.sh' });
2876
- hooksConfig.hooks.beforeSubmitPrompt = [
2877
- { command: './.cursor/hooks/scope-guard.sh' },
2878
- { command: './.cursor/hooks/audit.sh' },
2879
- ];
2880
- }
2881
-
2882
- if (levels.includes('audit')) {
2883
- // Add audit to all events
2884
- if (!hooksConfig.hooks.afterFileEdit) {
2885
- hooksConfig.hooks.afterFileEdit = [];
2886
- }
2887
- hooksConfig.hooks.afterFileEdit.push({ command: './.cursor/hooks/audit.sh' });
2888
-
2889
- hooksConfig.hooks.stop = [{ command: './.cursor/hooks/audit.sh' }];
2890
- }
2891
-
2892
- // Write hooks.json
2893
- await fs.writeFile(path.join(cursorDir, 'hooks.json'), JSON.stringify(hooksConfig, null, 2));
2894
-
2895
- // Copy README
2896
- const readmePath = path.join(cursorTemplateDir, 'README.md');
2897
- if (fs.existsSync(readmePath)) {
2898
- await fs.copy(readmePath, path.join(cursorDir, 'README.md'));
2899
- }
2900
-
2901
- console.log(chalk.green('✅ Cursor hooks configured'));
2902
- console.log(chalk.gray(` Enabled: ${levels.join(', ')}`));
2903
- console.log(
2904
- chalk.gray(` Scripts: ${Array.from(enabledHooks).length} hook scripts installed`)
2905
- );
2906
- console.log(chalk.blue('💡 Restart Cursor to activate hooks'));
2907
- } catch (error) {
2908
- console.error(chalk.yellow('âš ī¸ Failed to setup Cursor hooks:'), error.message);
2909
- console.log(chalk.blue('💡 You can manually copy .cursor/ directory later'));
2910
- }
2911
- }
2912
-
2913
- /**
2914
- * Scaffold existing project with CAWS components
2915
- */
2916
- async function scaffoldProject(options) {
2917
- const currentDir = process.cwd();
2918
- const projectName = path.basename(currentDir);
2919
-
2920
- try {
2921
- // Detect existing CAWS setup FIRST before any logging
2922
- const setup = detectCAWSSetup(currentDir);
2923
-
2924
- // Check for CAWS setup immediately and exit with helpful message if not found
2925
- if (!setup.hasCAWSDir) {
2926
- console.log(chalk.red('❌ CAWS not initialized in this project'));
2927
- console.log(chalk.blue('\n💡 To get started:'));
2928
- console.log(` 1. Initialize CAWS: ${chalk.cyan('caws init <project-name>')}`);
2929
- console.log(` 2. Or initialize in current directory: ${chalk.cyan('caws init .')}`);
2930
- console.log(chalk.blue('\n📚 For more help:'));
2931
- console.log(` ${chalk.cyan('caws --help')}`);
2932
- process.exit(1);
2933
- }
2934
-
2935
- console.log(chalk.cyan(`🔧 Enhancing existing CAWS project: ${projectName}`));
2936
-
2937
- // Preserve the original template directory from global cawsSetup
2938
- // (needed because detectCAWSSetup from within a new project won't find the template)
2939
- if (cawsSetup?.templateDir && !setup.templateDir) {
2940
- setup.templateDir = cawsSetup.templateDir;
2941
- setup.hasTemplateDir = true;
2942
- } else if (!setup.templateDir) {
2943
- // Try to find template directory using absolute paths that work in CI
2944
- const possiblePaths = [
2945
- '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
2946
- '/workspace/packages/caws-template',
2947
- '/caws/packages/caws-template',
2948
- path.resolve(process.cwd(), '../../../packages/caws-template'),
2949
- path.resolve(process.cwd(), '../../packages/caws-template'),
2950
- path.resolve(process.cwd(), '../packages/caws-template'),
2951
- ];
2952
-
2953
- for (const testPath of possiblePaths) {
2954
- if (fs.existsSync(testPath)) {
2955
- setup.templateDir = testPath;
2956
- setup.hasTemplateDir = true;
2957
- break;
2958
- }
2959
- }
2960
-
2961
- if (!setup.templateDir) {
2962
- console.log(chalk.red(`❌ No template directory available!`));
2963
- console.log(chalk.blue('💡 To fix this issue:'));
2964
- console.log(` 1. Ensure caws-template package is installed`);
2965
- console.log(` 2. Run from the monorepo root directory`);
2966
- console.log(` 3. Check that CAWS CLI was installed correctly`);
2967
- console.log(chalk.blue('\n📚 For installation help:'));
2968
- console.log(` ${chalk.cyan('npm install -g @paths.design/caws-cli')}`);
2969
- }
2970
- }
2971
-
2972
- // Override global cawsSetup with current context for scaffold operations
2973
- cawsSetup = setup;
2974
-
2975
- if (!setup.hasCAWSDir) {
2976
- console.error(chalk.red('❌ No .caws directory found'));
2977
- console.error(chalk.blue('💡 Run "caws init <project-name>" first to create a CAWS project'));
2978
- process.exit(1);
2979
- }
2980
-
2981
- // Adapt behavior based on setup type
2982
- if (setup.isEnhanced) {
2983
- console.log(chalk.green('đŸŽ¯ Enhanced CAWS detected - adding automated publishing'));
2984
- } else if (setup.isAdvanced) {
2985
- console.log(chalk.blue('🔧 Advanced CAWS detected - adding missing capabilities'));
2986
- } else {
2987
- console.log(chalk.blue('📋 Basic CAWS detected - enhancing with additional tools'));
2988
- }
2989
-
2990
- // Generate provenance for scaffolding operation
2991
- const scaffoldProvenance = {
2992
- agent: 'caws-cli',
2993
- model: 'cli-scaffold',
2994
- modelHash: CLI_VERSION,
2995
- toolAllowlist: ['node', 'fs-extra'],
2996
- prompts: ['scaffold', options.force ? 'force' : 'normal'],
2997
- commit: null,
2998
- artifacts: [],
2999
- results: {
3000
- operation: 'scaffold',
3001
- force_mode: options.force,
3002
- target_directory: currentDir,
3003
- },
3004
- approvals: [],
3005
- timestamp: new Date().toISOString(),
3006
- version: CLI_VERSION,
3007
- };
3008
-
3009
- // Calculate hash after object is fully defined
3010
- scaffoldProvenance.hash = require('crypto')
3011
- .createHash('sha256')
3012
- .update(JSON.stringify(scaffoldProvenance))
3013
- .digest('hex');
3014
-
3015
- // Determine what enhancements to add based on setup type and options
3016
- const enhancements = [];
3017
-
3018
- // Add CAWS tools directory structure (matches test expectations)
3019
- enhancements.push({
3020
- name: 'apps/tools/caws',
3021
- description: 'CAWS tools directory',
3022
- required: true,
3023
- });
3024
-
3025
- // Add codemods if requested or not minimal
3026
- if (options.withCodemods || (!options.minimal && !options.withCodemods)) {
3027
- enhancements.push({
3028
- name: 'codemod',
3029
- description: 'Codemod transformation scripts',
3030
- required: true,
3031
- });
3032
- }
3033
-
3034
- // Also add automated publishing for enhanced setups
3035
- if (setup.isEnhanced) {
3036
- enhancements.push({
3037
- name: '.github/workflows/release.yml',
3038
- description: 'GitHub Actions workflow for automated publishing',
3039
- required: true,
3040
- });
3041
-
3042
- enhancements.push({
3043
- name: '.releaserc.json',
3044
- description: 'semantic-release configuration',
3045
- required: true,
3046
- });
3047
- }
3048
-
3049
- // Add commit conventions for setups that don't have them
3050
- if (!setup.hasTemplates || !fs.existsSync(path.join(currentDir, 'COMMIT_CONVENTIONS.md'))) {
3051
- enhancements.push({
3052
- name: 'COMMIT_CONVENTIONS.md',
3053
- description: 'Commit message guidelines',
3054
- required: false,
3055
- });
3056
- }
3057
-
3058
- // Add OIDC setup guide if requested or not minimal
3059
- if (
3060
- (options.withOidc || (!options.minimal && !options.withOidc)) &&
3061
- (!setup.isEnhanced || !fs.existsSync(path.join(currentDir, 'OIDC_SETUP.md')))
3062
- ) {
3063
- enhancements.push({
3064
- name: 'OIDC_SETUP.md',
3065
- description: 'OIDC trusted publisher setup guide',
3066
- required: false,
3067
- });
3068
- }
3069
-
3070
- // For enhanced setups, preserve existing tools
3071
- if (setup.isEnhanced) {
3072
- console.log(chalk.blue('â„šī¸ Preserving existing sophisticated CAWS tools'));
3073
- }
3074
-
3075
- let addedCount = 0;
3076
- let skippedCount = 0;
3077
- const addedFiles = [];
3078
-
3079
- for (const enhancement of enhancements) {
3080
- if (!setup?.templateDir) {
3081
- console.warn(
3082
- chalk.yellow(`âš ī¸ Template directory not available for enhancement: ${enhancement.name}`)
3083
- );
3084
- continue;
3085
- }
3086
- const sourcePath = path.join(setup.templateDir, enhancement.name);
3087
- const destPath = path.join(currentDir, enhancement.name);
3088
-
3089
- if (!fs.existsSync(destPath)) {
3090
- if (fs.existsSync(sourcePath)) {
3091
- try {
3092
- await fs.copy(sourcePath, destPath);
3093
- console.log(chalk.green(`✅ Added ${enhancement.description}`));
3094
- addedCount++;
3095
- addedFiles.push(enhancement.name);
3096
- } catch (copyError) {
3097
- console.warn(chalk.yellow(`âš ī¸ Failed to add ${enhancement.name}:`), copyError.message);
3098
- }
3099
- } else {
3100
- // If source doesn't exist in template, create the directory structure
3101
- try {
3102
- await fs.ensureDir(destPath);
3103
- console.log(chalk.green(`✅ Created ${enhancement.description}`));
3104
- addedCount++;
3105
- addedFiles.push(enhancement.name);
3106
- } catch (createError) {
3107
- console.warn(
3108
- chalk.yellow(`âš ī¸ Failed to create ${enhancement.name}:`),
3109
- createError.message
3110
- );
3111
- }
3112
- }
3113
- } else {
3114
- if (options.force) {
3115
- try {
3116
- await fs.remove(destPath);
3117
- if (fs.existsSync(sourcePath)) {
3118
- await fs.copy(sourcePath, destPath);
3119
- } else {
3120
- await fs.ensureDir(destPath);
3121
- }
3122
- console.log(chalk.blue(`🔄 Updated ${enhancement.description}`));
3123
- addedCount++;
3124
- addedFiles.push(enhancement.name);
3125
- } catch (overwriteError) {
3126
- console.warn(
3127
- chalk.yellow(`âš ī¸ Failed to update ${enhancement.name}:`),
3128
- overwriteError.message
3129
- );
3130
- }
3131
- } else {
3132
- console.log(`â­ī¸ Skipped ${enhancement.name} (already exists)`);
3133
- skippedCount++;
3134
- }
3135
- }
3136
- }
3137
-
3138
- // Update provenance with results
3139
- scaffoldProvenance.artifacts = addedFiles;
3140
- scaffoldProvenance.results.files_added = addedCount;
3141
- scaffoldProvenance.results.files_skipped = skippedCount;
3142
-
3143
- // Show summary
3144
- console.log(chalk.green(`\n🎉 Enhancement completed!`));
3145
- console.log(chalk.bold(`📊 Summary: ${addedCount} added, ${skippedCount} skipped`));
3146
-
3147
- if (addedCount > 0) {
3148
- console.log(chalk.bold('\n📝 Next steps:'));
3149
- console.log('1. Review the added files');
3150
-
3151
- // Check if OIDC was added
3152
- const oidcAdded = addedFiles.some((file) => file.includes('OIDC_SETUP'));
3153
- if (oidcAdded) {
3154
- console.log('2. Set up OIDC trusted publisher (see OIDC_SETUP.md)');
3155
- console.log('3. Push to trigger automated publishing');
3156
- console.log('4. Your existing CAWS tools remain unchanged');
3157
- } else {
3158
- console.log('2. Customize your working spec in .caws/working-spec.yaml');
3159
- console.log('3. Run validation: caws validate --suggestions');
3160
- console.log('4. Your existing CAWS tools remain unchanged');
3161
- }
3162
- }
3163
-
3164
- if (setup.isEnhanced) {
3165
- console.log(
3166
- chalk.blue('\nđŸŽ¯ Your enhanced CAWS setup has been improved with automated publishing!')
3167
- );
3168
- }
3169
-
3170
- if (options.force) {
3171
- console.log(chalk.yellow('\nâš ī¸ Force mode was used - review changes carefully'));
3172
- }
3173
-
3174
- // Save provenance manifest if tools are available
3175
- const tools = loadProvenanceTools();
3176
- if (tools && typeof tools.saveProvenance === 'function') {
3177
- await tools.saveProvenance(scaffoldProvenance, '.agent/scaffold-provenance.json');
3178
- console.log(chalk.green('✅ Scaffolding provenance saved'));
3179
- } else {
3180
- console.log(chalk.yellow('âš ī¸ Provenance tools not available - skipping manifest save'));
3181
- }
3182
- } catch (error) {
3183
- // Handle circular reference errors from Commander.js
3184
- if (error.message && error.message.includes('Converting circular structure to JSON')) {
3185
- console.log(
3186
- chalk.yellow('âš ī¸ Scaffolding completed with minor issues (circular reference handled)')
3187
- );
3188
- console.log(chalk.green('✅ CAWS components scaffolded successfully'));
3189
- } else {
3190
- console.error(chalk.red('❌ Error during scaffolding:'), error.message);
3191
- process.exit(1);
3192
- }
3193
- }
3194
- }
3195
-
3196
- /**
3197
- * Show version information
3198
- */
3199
- // function showVersion() {
3200
- // console.log(chalk.bold(`CAWS CLI v${CLI_VERSION}`));
3201
- // console.log(chalk.cyan('Coding Agent Workflow System - Scaffolding Tool'));
3202
- // console.log(chalk.gray('Author: @darianrosebrook'));
3203
- // console.log(chalk.gray('License: MIT'));
3204
- // }
3205
-
3206
- // Initialize CAWS Tool System
3207
- let toolLoader = null;
3208
- let toolValidator = null;
3209
-
3210
- async function initializeToolSystem() {
3211
- if (toolLoader) return toolLoader; // Already initialized
3212
-
3213
- try {
3214
- toolLoader = new ToolLoader({
3215
- toolsDir: path.join(process.cwd(), 'apps/tools/caws'),
3216
- });
3217
-
3218
- toolValidator = new ToolValidator();
3219
-
3220
- // Set up event listeners for tool system
3221
- toolLoader.on('discovery:complete', ({ tools: _tools, count }) => {
3222
- if (count > 0) {
3223
- console.log(chalk.blue(`🔧 Discovered ${count} tools`));
3224
- }
3225
- });
3226
-
3227
- toolLoader.on('tool:loaded', ({ id, metadata }) => {
3228
- console.log(chalk.gray(` ✓ Loaded tool: ${metadata.name} (${id})`));
3229
- });
3230
-
3231
- toolLoader.on('tool:error', ({ id, error }) => {
3232
- console.warn(chalk.yellow(`âš ī¸ Failed to load tool ${id}: ${error}`));
3233
- });
3234
-
3235
- // Auto-discover tools on initialization
3236
- await toolLoader.discoverTools();
3237
-
3238
- return toolLoader;
3239
- } catch (error) {
3240
- console.warn(chalk.yellow('âš ī¸ Tool system initialization failed:'), error.message);
3241
- console.warn(chalk.blue('💡 Continuing without dynamic tools'));
3242
- return null;
3243
- }
3244
- }
3245
-
3246
- // Agent-oriented helper functions
3247
- async function runQualityGatesForSpec(spec, strictMode = false) {
3248
- const loader = await initializeToolSystem();
3249
- const WaiversManager = require('./waivers-manager');
3250
- const waiversManager = new WaiversManager();
3251
-
3252
- const criteria = [];
3253
- let overallScore = 0;
3254
- let totalWeight = 0;
3255
-
3256
- // Spec completeness criteria
3257
- const specScore = calculateSpecCompleteness(spec);
3258
- criteria.push({
3259
- id: 'spec_completeness',
3260
- name: 'Specification Completeness',
3261
- status: specScore >= 0.8 ? 'passed' : 'failed',
3262
- score: specScore,
3263
- weight: 0.2,
3264
- feedback: `Spec completeness: ${(specScore * 100).toFixed(1)}%`,
3265
- });
3266
- overallScore += specScore * 0.2;
3267
- totalWeight += 0.2;
3268
-
3269
- // Risk-appropriate quality thresholds
3270
- const tierThresholds = {
3271
- 1: { coverage: 0.9, mutation: 0.7, contracts: true },
3272
- 2: { coverage: 0.8, mutation: 0.5, contracts: true },
3273
- 3: { coverage: 0.7, mutation: 0.3, contracts: false },
3274
- };
3275
-
3276
- const thresholds = tierThresholds[spec.risk_tier] || tierThresholds[2];
3277
- const appliedThresholds = strictMode
3278
- ? {
3279
- coverage: thresholds.coverage,
3280
- mutation: thresholds.mutation,
3281
- contracts: thresholds.contracts,
3282
- }
3283
- : {
3284
- coverage: Math.max(0.6, thresholds.coverage - 0.2),
3285
- mutation: Math.max(0.2, thresholds.mutation - 0.2),
3286
- contracts: thresholds.contracts,
3287
- };
3288
-
3289
- // Tool-based quality gates
3290
- if (loader) {
3291
- const tools = loader.getAllTools();
3292
-
3293
- for (const [toolId, tool] of tools) {
3294
- if (
3295
- tool.metadata.capabilities?.includes('quality-gates') ||
3296
- tool.metadata.capabilities?.includes('validation')
3297
- ) {
3298
- const gateName = `gate_${toolId}`;
3299
- let gateScore = 0.0;
3300
- let gateStatus = 'failed';
3301
- let gateFeedback = '';
3302
- let weight = toolId === 'validate' ? 0.3 : 0.15;
3303
-
3304
- // Check if this gate is waived
3305
- const waiverCoverage = await waiversManager.checkWaiverCoverage([gateName]);
3306
-
3307
- if (waiverCoverage.allCovered) {
3308
- // Gate is waived - mark as passed but note the waiver
3309
- gateScore = 1.0;
3310
- gateStatus = 'waived';
3311
- const waiver = waiverCoverage.waiverDetails[0];
3312
- gateFeedback = `WAIVED: ${waiver.waiver_id} (${waiver.reason}) - Expires: ${waiver.expires_at}`;
3313
- weight = weight * 0.8; // Reduce weight for waived gates
3314
- } else {
3315
- // Run the gate normally
3316
- try {
3317
- const result = await tool.module.execute(
3318
- {},
3319
- {
3320
- workingDirectory: process.cwd(),
3321
- spec: spec,
3322
- }
3323
- );
3324
-
3325
- gateScore = result.success ? 1.0 : 0.0;
3326
- gateStatus = result.success ? 'passed' : 'failed';
3327
- gateFeedback = result.success
3328
- ? `${tool.metadata.name} passed`
3329
- : `Failed: ${result.errors?.join(', ') || 'Unknown error'}`;
3330
- } catch (error) {
3331
- gateStatus = 'error';
3332
- gateScore = 0;
3333
- gateFeedback = `Gate execution failed: ${error.message}`;
3334
- weight = 0.1;
3335
- }
3336
- }
3337
-
3338
- criteria.push({
3339
- id: gateName,
3340
- name: `${tool.metadata.name} Gate`,
3341
- status: gateStatus,
3342
- score: gateScore,
3343
- weight: weight,
3344
- feedback: gateFeedback,
3345
- });
3346
-
3347
- overallScore += gateScore * weight;
3348
- totalWeight += weight;
3349
- }
3350
- }
3351
- }
3352
-
3353
- // Contract compliance (if applicable)
3354
- if (spec.contracts && spec.contracts.length > 0) {
3355
- const contractScore = spec.contracts.length > 0 ? 1.0 : 0.0;
3356
- criteria.push({
3357
- id: 'contract_compliance',
3358
- name: 'Contract Compliance',
3359
- status: contractScore >= (appliedThresholds.contracts ? 1.0 : 0.5) ? 'passed' : 'failed',
3360
- score: contractScore,
3361
- weight: 0.2,
3362
- feedback: `${spec.contracts.length} contracts defined`,
3363
- });
3364
- overallScore += contractScore * 0.2;
3365
- totalWeight += 0.2;
3366
- }
3367
-
3368
- const finalScore = totalWeight > 0 ? overallScore / totalWeight : 0;
3369
- const overallPassed = finalScore >= 0.75; // 75% quality threshold
3370
-
3371
- // Generate next actions based on results
3372
- const nextActions = [];
3373
- const failedCriteria = criteria.filter((c) => c.status === 'failed');
3374
-
3375
- if (failedCriteria.length > 0) {
3376
- nextActions.push('Address failed quality criteria:');
3377
- failedCriteria.forEach((criterion) => {
3378
- nextActions.push(` - ${criterion.name}: ${criterion.feedback}`);
3379
- });
3380
- } else if (overallPassed) {
3381
- nextActions.push('All quality gates passed! Ready for integration.');
3382
- nextActions.push('Consider: code review, additional testing, documentation updates');
3383
- } else {
3384
- nextActions.push('Improve overall quality score through:');
3385
- nextActions.push(' - Better test coverage and mutation scores');
3386
- nextActions.push(' - Contract testing implementation');
3387
- nextActions.push(' - Tool-based quality gate compliance');
3388
- }
3389
-
3390
- return {
3391
- overall_passed: overallPassed,
3392
- quality_score: Number(finalScore.toFixed(3)),
3393
- summary: overallPassed
3394
- ? `Quality standards met (${(finalScore * 100).toFixed(1)}% score)`
3395
- : `Quality standards not met (${(finalScore * 100).toFixed(1)}% score)`,
3396
- criteria,
3397
- progress_indicators: {
3398
- spec_complete: specScore >= 0.8,
3399
- quality_gates: criteria.filter((c) => c.id.startsWith('gate_') && c.status === 'passed')
3400
- .length,
3401
- contracts_ready: spec.contracts && spec.contracts.length > 0,
3402
- risk_appropriate: spec.risk_tier <= 2 || finalScore >= 0.6,
3403
- },
3404
- next_actions: nextActions,
3405
- risk_assessment: {
3406
- tier: spec.risk_tier,
3407
- applied_thresholds: appliedThresholds,
3408
- risk_level: spec.risk_tier === 1 ? 'high' : spec.risk_tier === 2 ? 'medium' : 'low',
3409
- recommendations: generateRiskRecommendations(spec, finalScore),
3410
- },
3411
- };
3412
- }
3413
-
3414
- function calculateSpecCompleteness(spec) {
3415
- let score = 0;
3416
- let totalChecks = 0;
3417
-
3418
- // Required fields
3419
- const requiredFields = [
3420
- 'id',
3421
- 'title',
3422
- 'risk_tier',
3423
- 'mode',
3424
- 'change_budget',
3425
- 'scope',
3426
- 'invariants',
3427
- 'acceptance',
3428
- ];
3429
- requiredFields.forEach((field) => {
3430
- totalChecks++;
3431
- if (spec[field]) score++;
3432
- });
3433
-
3434
- // Scope completeness
3435
- totalChecks++;
3436
- if (spec.scope && spec.scope.in && spec.scope.out) score++;
3437
-
3438
- // Acceptance criteria quality
3439
- totalChecks++;
3440
- if (spec.acceptance && spec.acceptance.length >= 1) {
3441
- const validCriteria = spec.acceptance.filter((a) => a.id && a.given && a.when && a.then);
3442
- score += validCriteria.length / Math.max(spec.acceptance.length, 1);
3443
- }
3444
-
3445
- // Invariants presence
3446
- totalChecks++;
3447
- if (spec.invariants && spec.invariants.length >= 2) score++;
3448
-
3449
- // Contracts for T1/T2
3450
- if (spec.risk_tier <= 2) {
3451
- totalChecks++;
3452
- if (spec.contracts && spec.contracts.length > 0) score++;
3453
- }
3454
-
3455
- return score / totalChecks;
3456
- }
3457
-
3458
- function generateRiskRecommendations(spec, qualityScore) {
3459
- const recommendations = [];
3460
-
3461
- if (spec.risk_tier === 1 && qualityScore < 0.9) {
3462
- recommendations.push('High-risk feature requires exceptional quality (>90%)');
3463
- recommendations.push('Consider breaking into smaller, lower-risk changes');
3464
- }
3465
-
3466
- if (spec.mode === 'feature' && (!spec.contracts || spec.contracts.length === 0)) {
3467
- recommendations.push('Features should define contracts before implementation');
3468
- }
3469
-
3470
- if (spec.change_budget && spec.change_budget.max_files > 25) {
3471
- recommendations.push('Large change budgets increase risk - consider splitting');
3472
- }
3473
-
3474
- return recommendations;
3475
- }
3476
-
3477
- async function generateIterativeGuidance(spec, currentState) {
3478
- const guidance = {
3479
- guidance: '',
3480
- next_steps: [],
3481
- confidence: 0,
3482
- focus_areas: [],
3483
- risk_mitigation: [],
3484
- };
3485
-
3486
- // Analyze current implementation state
3487
- const implementationStage = analyzeImplementationStage(spec, currentState);
3488
- guidance.guidance = getStageGuidance(implementationStage);
3489
-
3490
- // Generate specific next steps based on spec requirements
3491
- guidance.next_steps = generateNextSteps(spec, implementationStage, currentState);
3492
-
3493
- // Calculate confidence based on progress
3494
- guidance.confidence = calculateImplementationConfidence(spec, currentState);
3495
-
3496
- // Identify focus areas
3497
- guidance.focus_areas = identifyFocusAreas(spec, currentState);
3498
-
3499
- // Risk mitigation suggestions
3500
- guidance.risk_mitigation = generateRiskMitigation(spec, currentState);
3501
-
3502
- return guidance;
3503
- }
3504
-
3505
- function analyzeImplementationStage(spec, currentState) {
3506
- // Simple heuristic based on current state description
3507
- const stateDesc = currentState.description || '';
3508
-
3509
- if (stateDesc.includes('started') || stateDesc.includes('initial')) return 'planning';
3510
- if (stateDesc.includes('prototype') || stateDesc.includes('draft')) return 'prototyping';
3511
- if (stateDesc.includes('core') || stateDesc.includes('basic')) return 'core_implementation';
3512
- if (stateDesc.includes('testing') || stateDesc.includes('test')) return 'testing';
3513
- if (stateDesc.includes('integration') || stateDesc.includes('integrate')) return 'integration';
3514
- if (stateDesc.includes('complete') || stateDesc.includes('done')) return 'polishing';
3515
-
3516
- return 'early_planning';
3517
- }
3518
-
3519
- function getStageGuidance(stage) {
3520
- const guidance = {
3521
- early_planning: 'Focus on understanding requirements and creating a solid implementation plan.',
3522
- planning: 'Break down the feature into manageable tasks and establish success criteria.',
3523
- prototyping:
3524
- 'Build a working prototype to validate the approach and identify technical challenges.',
3525
- core_implementation:
3526
- 'Implement the core functionality with proper error handling and edge cases.',
3527
- testing: 'Add comprehensive tests and validate against acceptance criteria.',
3528
- integration: 'Ensure the feature integrates well with existing systems and contracts.',
3529
- polishing: 'Refine the implementation, add documentation, and optimize performance.',
3530
- };
3531
-
3532
- return guidance[stage] || 'Continue systematic implementation following the working spec.';
3533
- }
3534
-
3535
- function generateNextSteps(spec, stage, _currentState) {
3536
- const steps = [];
3537
-
3538
- switch (stage) {
3539
- case 'early_planning':
3540
- steps.push('Review and validate working spec completeness');
3541
- steps.push('Create detailed implementation plan');
3542
- steps.push('Set up development environment and dependencies');
3543
- break;
3544
-
3545
- case 'planning':
3546
- steps.push('Implement core functionality skeleton');
3547
- steps.push('Add basic error handling');
3548
- steps.push('Create initial test structure');
3549
- break;
3550
-
3551
- case 'prototyping':
3552
- steps.push('Refine core algorithms and logic');
3553
- steps.push('Add comprehensive input validation');
3554
- steps.push('Implement basic integration points');
3555
- break;
3556
-
3557
- case 'core_implementation':
3558
- steps.push('Add comprehensive test coverage');
3559
- steps.push('Implement contract testing if applicable');
3560
- steps.push('Add performance optimizations');
3561
- break;
3562
-
3563
- case 'testing':
3564
- steps.push('Run full quality gate evaluation');
3565
- steps.push('Address any failing tests or gates');
3566
- steps.push('Add integration tests');
3567
- break;
3568
-
3569
- case 'integration':
3570
- steps.push('Test with dependent systems');
3571
- steps.push('Validate contract compliance');
3572
- steps.push('Perform load and stress testing');
3573
- break;
3574
-
3575
- case 'polishing':
3576
- steps.push('Add comprehensive documentation');
3577
- steps.push('Final performance optimization');
3578
- steps.push('Code review and final validation');
3579
- break;
3580
- }
3581
-
3582
- // Add spec-specific steps
3583
- if (spec.mode === 'feature' && spec.contracts) {
3584
- steps.push('Ensure contract definitions are complete and tested');
3585
- }
3586
-
3587
- if (spec.risk_tier === 1) {
3588
- steps.push('Prioritize security and reliability measures');
3589
- }
3590
-
3591
- return steps;
3592
- }
3593
-
3594
- function calculateImplementationConfidence(spec, currentState) {
3595
- // Simple confidence calculation based on described progress
3596
- let confidence = 0.5; // Base confidence
3597
-
3598
- const stateDesc = (currentState.description || '').toLowerCase();
3599
-
3600
- if (stateDesc.includes('complete') || stateDesc.includes('done')) confidence += 0.3;
3601
- if (stateDesc.includes('tested') || stateDesc.includes('working')) confidence += 0.2;
3602
- if (stateDesc.includes('prototype') || stateDesc.includes('basic')) confidence += 0.1;
3603
-
3604
- if (stateDesc.includes('blocked') || stateDesc.includes('stuck')) confidence -= 0.2;
3605
- if (stateDesc.includes('issues') || stateDesc.includes('problems')) confidence -= 0.1;
3606
-
3607
- return Math.max(0, Math.min(1, confidence));
3608
- }
3609
-
3610
- function identifyFocusAreas(spec, _currentState) {
3611
- const areas = [];
3612
-
3613
- // Always include quality gates
3614
- areas.push('Quality Gates Compliance');
3615
-
3616
- if (spec.contracts && spec.contracts.length > 0) {
3617
- areas.push('Contract Implementation');
3618
- }
3619
-
3620
- if (spec.risk_tier <= 2) {
3621
- areas.push('Security & Reliability');
3622
- }
3623
-
3624
- if (spec.acceptance && spec.acceptance.length > 0) {
3625
- areas.push('Acceptance Criteria Validation');
3626
- }
3627
-
3628
- return areas;
3629
- }
3630
-
3631
- function generateRiskMitigation(spec, _currentState) {
3632
- const mitigation = [];
3633
-
3634
- if (spec.risk_tier === 1) {
3635
- mitigation.push('Implement comprehensive error handling');
3636
- mitigation.push('Add extensive logging and monitoring');
3637
- mitigation.push('Consider feature flags for gradual rollout');
3638
- }
3639
-
3640
- if (spec.change_budget && spec.change_budget.max_files > 15) {
3641
- mitigation.push('Regular commits and incremental validation');
3642
- mitigation.push('Consider breaking into smaller PRs');
3643
- }
3644
-
3645
- if (spec.mode === 'feature') {
3646
- mitigation.push('Validate contracts before full implementation');
3647
- mitigation.push('Implement feature flags for safe deployment');
3648
- }
3649
-
3650
- return mitigation;
3651
- }
3652
-
3653
- // CLI Commands
3654
- program
3655
- .name('caws')
3656
- .description('CAWS - Coding Agent Workflow System CLI')
3657
- .version(CLI_VERSION, '-v, --version', 'Show version information');
3658
-
3659
- program
3660
- .command('init')
3661
- .alias('i')
3662
- .description('Initialize a new project with CAWS')
3663
- .argument('<project-name>', 'Name of the new project')
3664
- .option('-i, --interactive', 'Run interactive setup wizard')
3665
- .option('-g, --git', 'Initialize git repository', true)
3666
- .option('-n, --non-interactive', 'Skip interactive prompts')
3667
- .option('--no-git', "Don't initialize git repository")
3668
- .option('-t, --template <type>', 'Use project template (extension|library|api|cli|monorepo)')
3669
- .action(initProject);
3670
-
3671
- program
3672
- .command('scaffold')
3673
- .alias('s')
3674
- .description('Add CAWS components to existing project')
3675
- .option('-f, --force', 'Overwrite existing files')
3676
- .option('--with-oidc', 'Include OIDC trusted publisher setup')
3677
- .option('--with-codemods', 'Include codemod transformation scripts')
3678
- .option('--minimal', 'Only essential components (no OIDC, no codemods)')
3679
- .action(scaffoldProject);
3680
-
3681
- program
3682
- .command('validate')
3683
- .alias('v')
3684
- .description('Validate CAWS working spec with suggestions')
3685
- .argument('[spec-file]', 'Path to working spec file', '.caws/working-spec.yaml')
3686
- .option('-s, --suggestions', 'Show helpful suggestions for issues', true)
3687
- .option('-f, --auto-fix', 'Automatically fix safe issues', false)
3688
- .option('-q, --quiet', 'Only show errors, no suggestions', false)
3689
- .action(async (specFile, options) => {
3690
- try {
3691
- // Check if spec file exists
3692
- if (!fs.existsSync(specFile)) {
3693
- console.error(chalk.red(`❌ Working spec file not found: ${specFile}`));
3694
- console.error(chalk.blue('💡 Initialize CAWS first:'));
3695
- console.error(` ${chalk.cyan('caws init .')}`);
3696
- process.exit(1);
3697
- }
3698
-
3699
- // Load and parse spec
3700
- const specContent = fs.readFileSync(specFile, 'utf8');
3701
- const spec = yaml.load(specContent);
3702
-
3703
- if (!spec) {
3704
- console.error(chalk.red('❌ Failed to parse working spec YAML'));
3705
- process.exit(1);
3706
- }
3707
-
3708
- // Validate spec with suggestions
3709
- const result = validateWorkingSpecWithSuggestions(spec, {
3710
- autoFix: options.autoFix,
3711
- suggestions: !options.quiet,
3712
- });
3713
-
3714
- // Save auto-fixed spec if changes were made
3715
- if (options.autoFix && result.errors.length === 0) {
3716
- const fixedContent = yaml.dump(spec, { indent: 2 });
3717
- fs.writeFileSync(specFile, fixedContent);
3718
- console.log(chalk.green(`✅ Saved auto-fixed spec to ${specFile}`));
3719
- }
3720
-
3721
- // Execute quality gate tools if validation passed
3722
- if (result.valid && !options.quiet) {
3723
- console.log('');
3724
- console.log(chalk.blue('🔍 Running quality gates...'));
3725
-
3726
- const loader = await initializeToolSystem();
3727
- if (loader) {
3728
- await loader.loadAllTools();
3729
- const tools = loader.getAllTools();
3730
-
3731
- let gatesPassed = 0;
3732
- let gatesTotal = 0;
3733
-
3734
- for (const [, tool] of tools) {
3735
- // Only run tools with quality-gates capability
3736
- if (
3737
- tool.metadata.capabilities?.includes('quality-gates') ||
3738
- tool.metadata.capabilities?.includes('validation')
3739
- ) {
3740
- gatesTotal++;
3741
- console.log(chalk.gray(` Running ${tool.metadata.name}...`));
3742
-
3743
- try {
3744
- const gateResult = await tool.module.execute(
3745
- {},
3746
- {
3747
- workingDirectory: process.cwd(),
3748
- spec: spec,
3749
- }
3750
- );
3751
-
3752
- if (gateResult.success) {
3753
- console.log(chalk.green(` ✅ ${tool.metadata.name} passed`));
3754
- gatesPassed++;
3755
- } else {
3756
- console.log(chalk.red(` ❌ ${tool.metadata.name} failed`));
3757
- gateResult.errors?.forEach((error) => {
3758
- console.log(chalk.red(` ${error}`));
3759
- });
3760
- }
3761
- } catch (error) {
3762
- console.log(chalk.red(` ❌ ${tool.metadata.name} error: ${error.message}`));
3763
- }
3764
- }
3765
- }
3766
-
3767
- if (gatesTotal > 0) {
3768
- console.log('');
3769
- console.log(chalk.blue(`đŸŽ¯ Quality Gates: ${gatesPassed}/${gatesTotal} passed`));
3770
-
3771
- if (gatesPassed < gatesTotal) {
3772
- console.log(chalk.yellow('âš ī¸ Some quality gates failed - review output above'));
3773
- process.exit(1);
3774
- } else {
3775
- console.log(chalk.green('🎉 All quality gates passed!'));
3776
- }
3777
- }
3778
- }
3779
- }
3780
-
3781
- // Exit with appropriate code
3782
- process.exit(result.valid ? 0 : 1);
3783
- } catch (error) {
3784
- console.error(chalk.red('❌ Error during validation:'), error.message);
3785
- process.exit(1);
3786
- }
3787
- });
3788
-
3789
- program
3790
- .command('agent')
3791
- .description('Agent-oriented commands for programmatic evaluation')
3792
- .addCommand(
3793
- new Command('evaluate')
3794
- .description('Evaluate work against CAWS quality standards')
3795
- .argument('<spec-file>', 'Path to working spec file')
3796
- .option('--json', 'Output results as structured JSON for agent parsing')
3797
- .option('--strict', 'Apply strict quality thresholds (for production use)')
3798
- .option('--feedback-only', 'Only return actionable feedback, no execution')
3799
- .action(async (specFile, options) => {
3800
- // Quiet mode for agent commands - suppress human-readable output
3801
- const _originalLog = console.log;
3802
- const _originalWarn = console.warn;
3803
- const _originalError = console.error;
3804
-
3805
- try {
3806
- if (!options.json) {
3807
- // Only suppress if not explicitly requesting JSON
3808
- console.log = () => {};
3809
- console.warn = () => {};
3810
- console.error = () => {};
3811
- }
3812
-
3813
- await initializeToolSystem();
3814
-
3815
- if (!fs.existsSync(specFile)) {
3816
- const result = {
3817
- success: false,
3818
- evaluation: {
3819
- overall_status: 'error',
3820
- message: `Working spec file not found: ${specFile}`,
3821
- criteria: [],
3822
- next_actions: [`Create working spec at ${specFile}`],
3823
- },
3824
- };
3825
- console.log(JSON.stringify(result, null, 2));
3826
- process.exit(1);
3827
- }
3828
-
3829
- const specContent = fs.readFileSync(specFile, 'utf8');
3830
- const spec = yaml.load(specContent);
3831
-
3832
- if (!spec) {
3833
- const result = {
3834
- success: false,
3835
- evaluation: {
3836
- overall_status: 'error',
3837
- message: 'Invalid YAML in working spec',
3838
- criteria: [],
3839
- next_actions: ['Fix YAML syntax in working spec'],
3840
- },
3841
- };
3842
- console.log(JSON.stringify(result, null, 2));
3843
- process.exit(1);
3844
- }
3845
-
3846
- // Validate spec structure
3847
- const validationResult = validateWorkingSpecWithSuggestions(spec, {
3848
- suggestions: false,
3849
- autoFix: false,
3850
- });
3851
-
3852
- if (!validationResult.valid) {
3853
- const result = {
3854
- success: false,
3855
- evaluation: {
3856
- overall_status: 'spec_invalid',
3857
- message: 'Working spec validation failed',
3858
- criteria: [
3859
- {
3860
- id: 'spec_validity',
3861
- name: 'Working Spec Validity',
3862
- status: 'failed',
3863
- score: 0,
3864
- weight: 1.0,
3865
- feedback: validationResult.errors.join('; '),
3866
- },
3867
- ],
3868
- next_actions: [
3869
- 'Fix working spec validation errors',
3870
- 'Run: caws validate --auto-fix for automatic fixes',
3871
- ],
3872
- },
3873
- };
3874
- console.log(JSON.stringify(result, null, 2));
3875
- process.exit(1);
3876
- }
3877
-
3878
- // If feedback-only, just return spec evaluation
3879
- if (options.feedbackOnly) {
3880
- const result = {
3881
- success: true,
3882
- evaluation: {
3883
- overall_status: 'spec_valid',
3884
- message: 'Working spec is valid and ready for implementation',
3885
- criteria: [
3886
- {
3887
- id: 'spec_completeness',
3888
- name: 'Specification Completeness',
3889
- status: 'passed',
3890
- score: 1.0,
3891
- weight: 1.0,
3892
- feedback: `Valid ${spec.mode} spec for ${spec.title} (${spec.risk_tier})`,
3893
- },
3894
- ],
3895
- spec_summary: {
3896
- id: spec.id,
3897
- mode: spec.mode,
3898
- tier: spec.risk_tier,
3899
- title: spec.title,
3900
- acceptance_criteria: spec.acceptance?.length || 0,
3901
- invariants: spec.invariants?.length || 0,
3902
- },
3903
- next_actions: [
3904
- `Begin ${spec.mode} implementation`,
3905
- 'Run: caws agent evaluate <spec> to check progress',
3906
- 'Run: caws agent iterate <spec> for guided development',
3907
- ],
3908
- },
3909
- };
3910
- console.log(JSON.stringify(result, null, 2));
3911
- return;
3912
- }
3913
-
3914
- // Run quality gates
3915
- const qualityGates = await runQualityGatesForSpec(spec, options.strict);
3916
-
3917
- const result = {
3918
- success: qualityGates.overall_passed,
3919
- evaluation: {
3920
- overall_status: qualityGates.overall_passed ? 'quality_passed' : 'quality_failed',
3921
- message: qualityGates.summary,
3922
- criteria: qualityGates.criteria,
3923
- spec_summary: {
3924
- id: spec.id,
3925
- mode: spec.mode,
3926
- tier: spec.risk_tier,
3927
- title: spec.title,
3928
- progress_indicators: qualityGates.progress_indicators,
3929
- },
3930
- next_actions: qualityGates.next_actions,
3931
- quality_score: qualityGates.quality_score,
3932
- risk_assessment: qualityGates.risk_assessment,
3933
- },
3934
- };
3935
-
3936
- console.log(JSON.stringify(result, null, 2));
3937
- process.exit(qualityGates.overall_passed ? 0 : 1);
3938
- } catch (error) {
3939
- // Restore console functions for error output
3940
- console.log = _originalLog;
3941
- console.warn = _originalWarn;
3942
- console.error = _originalError;
3943
-
3944
- const result = {
3945
- success: false,
3946
- evaluation: {
3947
- overall_status: 'error',
3948
- message: `Evaluation failed: ${error.message}`,
3949
- criteria: [],
3950
- next_actions: ['Check CAWS setup and try again'],
3951
- },
3952
- };
3953
- console.log(JSON.stringify(result, null, 2));
3954
- process.exit(1);
3955
- }
3956
- })
3957
- )
3958
- .addCommand(
3959
- new Command('iterate')
3960
- .description('Provide iterative development guidance based on current progress')
3961
- .argument('<spec-file>', 'Path to working spec file')
3962
- .option('--current-state <json>', 'JSON description of current implementation state')
3963
- .option('--json', 'Output as structured JSON')
3964
- .action(async (specFile, options) => {
3965
- // Quiet mode for agent commands - suppress human-readable output
3966
- const _originalLog = console.log;
3967
- const _originalWarn = console.warn;
3968
- const _originalError = console.error;
3969
-
3970
- try {
3971
- console.log = () => {};
3972
- console.warn = () => {};
3973
- console.error = () => {};
3974
-
3975
- await initializeToolSystem();
3976
-
3977
- if (!fs.existsSync(specFile)) {
3978
- const result = {
3979
- success: false,
3980
- iteration: {
3981
- guidance: 'Working spec not found',
3982
- next_steps: [`Create working spec at ${specFile}`],
3983
- confidence: 0,
3984
- },
3985
- };
3986
- console.log(JSON.stringify(result, null, 2));
3987
- process.exit(1);
3988
- }
3989
-
3990
- const specContent = fs.readFileSync(specFile, 'utf8');
3991
- const spec = yaml.load(specContent);
3992
-
3993
- let currentState = {};
3994
- if (options.currentState) {
3995
- try {
3996
- currentState = JSON.parse(options.currentState);
3997
- } catch (error) {
3998
- currentState = { description: options.currentState };
3999
- }
4000
- }
4001
-
4002
- const guidance = await generateIterativeGuidance(spec, currentState);
4003
-
4004
- const result = {
4005
- success: true,
4006
- iteration: guidance,
4007
- };
4008
-
4009
- console.log(JSON.stringify(result, null, 2));
4010
- } catch (error) {
4011
- // Restore console functions for error output
4012
- console.log = _originalLog;
4013
- console.warn = _originalWarn;
4014
- console.error = _originalError;
4015
-
4016
- const result = {
4017
- success: false,
4018
- iteration: {
4019
- guidance: 'Iteration guidance failed',
4020
- error: error.message,
4021
- next_steps: ['Check CAWS setup and working spec validity'],
4022
- },
4023
- };
4024
- console.log(JSON.stringify(result, null, 2));
4025
- process.exit(1);
4026
- }
4027
- })
4028
- );
4029
-
4030
- program
4031
- .command('cicd')
4032
- .description('CI/CD pipeline optimization and generation')
4033
- .addCommand(
4034
- new Command('analyze')
4035
- .description('Analyze project and recommend CI/CD optimizations')
4036
- .argument('[spec-file]', 'Path to working spec file', '.caws/working-spec.yaml')
4037
- .action(async (specFile) => {
4038
- try {
4039
- const CICDOptimizer = require('./cicd-optimizer');
4040
- const optimizer = new CICDOptimizer();
4041
-
4042
- console.log('🔍 Analyzing project for CI/CD optimizations...\n');
4043
-
4044
- const analysis = await optimizer.analyzeProject(specFile);
4045
-
4046
- console.log(`📊 Project Tier: ${analysis.project_tier}`);
4047
- console.log(
4048
- `âąī¸ Estimated Savings: ${analysis.estimated_savings.savings_percent}% faster builds`
4049
- );
4050
- console.log(
4051
- `💰 Monthly Time Savings: ${analysis.estimated_savings.monthly_savings_hours} hours\n`
4052
- );
4053
-
4054
- console.log('đŸŽ¯ Recommended Optimizations:');
4055
- analysis.recommended_optimizations.forEach((opt, i) => {
4056
- console.log(` ${i + 1}. ${opt.description}`);
4057
- console.log(` Impact: ${opt.impact} | Effort: ${opt.effort}`);
4058
- });
4059
-
4060
- console.log('\nâš™ī¸ Conditional Execution Rules:');
4061
- Object.entries(analysis.conditional_execution).forEach(([rule, enabled]) => {
4062
- console.log(` ${enabled ? '✅' : '❌'} ${rule.replace(/_/g, ' ')}`);
4063
- });
4064
-
4065
- console.log('\nđŸ“Ļ Cache Strategy:');
4066
- Object.entries(analysis.cache_strategy).forEach(([cache, config]) => {
4067
- console.log(` ${cache}: ${config.paths.join(', ')}`);
4068
- });
4069
-
4070
- console.log('\n🔄 Parallel Execution Groups:');
4071
- analysis.parallel_groups.forEach((group) => {
4072
- console.log(
4073
- ` ${group.name}: ${group.jobs.join(', ')} (max ${group.max_parallel} parallel, ${group.timeout}min timeout)`
4074
- );
4075
- });
4076
- } catch (error) {
4077
- console.error('❌ Failed to analyze CI/CD optimizations:', error.message);
4078
- process.exit(1);
4079
- }
4080
- })
4081
- )
4082
- .addCommand(
4083
- new Command('generate')
4084
- .description('Generate optimized CI/CD configuration')
4085
- .argument('[platform]', 'CI/CD platform (github, gitlab, jenkins)', 'github')
4086
- .option('-o, --output <file>', 'Output file path')
4087
- .action(async (platform, options) => {
4088
- try {
4089
- const CICDOptimizer = require('./cicd-optimizer');
4090
- const optimizer = new CICDOptimizer();
4091
-
4092
- console.log(`🔧 Generating optimized ${platform} CI/CD configuration...\n`);
4093
-
4094
- const config = await optimizer.generateOptimizedConfig(platform);
4095
-
4096
- if (options.output) {
4097
- const fs = require('fs');
4098
- const yaml = require('js-yaml');
4099
-
4100
- if (platform === 'github') {
4101
- // GitHub Actions uses YAML
4102
- const yamlConfig = yaml.dump(config, { indent: 2 });
4103
- fs.writeFileSync(options.output, yamlConfig);
4104
- console.log(`✅ Generated GitHub Actions workflow: ${options.output}`);
4105
- } else if (platform === 'gitlab') {
4106
- // GitLab CI uses YAML
4107
- const yamlConfig = yaml.dump(config, { indent: 2 });
4108
- fs.writeFileSync(options.output, yamlConfig);
4109
- console.log(`✅ Generated GitLab CI config: ${options.output}`);
4110
- } else if (platform === 'jenkins') {
4111
- // Jenkins uses Groovy
4112
- fs.writeFileSync(options.output, config);
4113
- console.log(`✅ Generated Jenkins pipeline: ${options.output}`);
4114
- }
4115
- } else {
4116
- // Print to console
4117
- console.log('Generated configuration:');
4118
- console.log('='.repeat(50));
4119
- if (platform === 'github' || platform === 'gitlab') {
4120
- console.log(JSON.stringify(config, null, 2));
4121
- } else {
4122
- console.log(config);
4123
- }
4124
- }
4125
- } catch (error) {
4126
- console.error('❌ Failed to generate CI/CD config:', error.message);
4127
- process.exit(1);
4128
- }
4129
- })
4130
- )
4131
- .addCommand(
4132
- new Command('test-selection')
4133
- .description('Analyze changed files and recommend test execution')
4134
- .option('--changed-files <files>', 'Comma-separated list of changed files')
4135
- .option('--from-commit <commit>', 'Analyze changes from specific commit')
4136
- .action(async (options) => {
4137
- try {
4138
- const CICDOptimizer = require('./cicd-optimizer');
4139
- const optimizer = new CICDOptimizer();
4140
-
4141
- let changedFiles = [];
4142
-
4143
- if (options.changedFiles) {
4144
- changedFiles = options.changedFiles.split(',').map((f) => f.trim());
4145
- } else if (options.fromCommit) {
4146
- // Use git to get changed files
4147
- const { execSync } = require('child_process');
4148
- try {
4149
- const output = execSync(`git diff --name-only ${options.fromCommit}`, {
4150
- encoding: 'utf8',
4151
- });
4152
- changedFiles = output
4153
- .trim()
4154
- .split('\n')
4155
- .filter((f) => f.length > 0);
4156
- } catch (error) {
4157
- console.error('❌ Failed to get changed files from git:', error.message);
4158
- process.exit(1);
4159
- }
4160
- } else {
4161
- console.error('❌ Must specify either --changed-files or --from-commit');
4162
- process.exit(1);
4163
- }
4164
-
4165
- console.log(`🔍 Analyzing ${changedFiles.length} changed files...\n`);
4166
-
4167
- const affectedTests = await optimizer.analyzeChangedFiles(changedFiles);
4168
-
4169
- console.log('📋 Recommended Test Execution:');
4170
-
4171
- if (affectedTests.unit.length > 0) {
4172
- console.log('đŸ§Ē Unit Tests:');
4173
- affectedTests.unit.forEach((test) => console.log(` â€ĸ ${test}`));
4174
- }
4175
-
4176
- if (affectedTests.integration.length > 0) {
4177
- console.log('🔗 Integration Tests:');
4178
- affectedTests.integration.forEach((test) => console.log(` â€ĸ ${test}`));
4179
- }
4180
-
4181
- if (affectedTests.contract.length > 0) {
4182
- console.log('📄 Contract Tests:');
4183
- affectedTests.contract.forEach((test) => console.log(` â€ĸ ${test}`));
4184
- }
4185
-
4186
- if (affectedTests.e2e.length > 0) {
4187
- console.log('🌐 E2E Tests:');
4188
- affectedTests.e2e.forEach((test) => console.log(` â€ĸ ${test}`));
4189
- }
4190
-
4191
- const totalTests = Object.values(affectedTests).flat().length;
4192
- console.log(`\n📊 Total recommended tests: ${totalTests}`);
4193
-
4194
- if (totalTests === 0) {
4195
- console.log('â„šī¸ No specific tests recommended - consider running full test suite');
4196
- }
4197
- } catch (error) {
4198
- console.error('❌ Failed to analyze test selection:', error.message);
4199
- process.exit(1);
4200
- }
4201
- })
4202
- );
4203
-
4204
- program
4205
- .command('experimental')
4206
- .description('Experimental features and dry-run capabilities')
4207
- .option('--dry-run', 'Run in dry-run mode without making actual changes', false)
4208
- .addCommand(
4209
- new Command('validate')
4210
- .description('Validate working spec with experimental features')
4211
- .argument('<spec-file>', 'Path to working spec file')
4212
- .option('--enhanced-analysis', 'Use enhanced analysis features', false)
4213
- .option('--predictive-scoring', 'Enable predictive quality scoring', false)
4214
- .action(async (specFile, options, cmd) => {
4215
- const isDryRun = cmd.parent.opts().dryRun;
4216
-
4217
- if (isDryRun) {
4218
- console.log('đŸœī¸ EXPERIMENTAL MODE - DRY RUN');
4219
- console.log('No actual validation will be performed\n');
4220
- }
4221
-
4222
- console.log('đŸ§Ē Experimental Features Enabled:');
4223
- if (options.enhancedAnalysis) console.log(' â€ĸ Enhanced analysis features');
4224
- if (options.predictiveScoring) console.log(' â€ĸ Predictive quality scoring');
4225
- console.log('');
4226
-
4227
- if (isDryRun) {
4228
- console.log('📋 Would validate:', specFile);
4229
- console.log('đŸŽ¯ Would check enhanced analysis:', options.enhancedAnalysis);
4230
- console.log('🔮 Would enable predictive scoring:', options.predictiveScoring);
4231
- console.log('\n✅ Dry run completed - no changes made');
4232
- return;
4233
- }
4234
-
4235
- // Implement experimental validation logic here
4236
- try {
4237
- console.log('đŸ”Ŧ Running experimental validation...');
4238
-
4239
- // For now, fall back to standard validation
4240
- const result = await validateWorkingSpec(specFile);
4241
-
4242
- if (result.valid) {
4243
- console.log('✅ Experimental validation passed');
4244
- if (options.enhancedAnalysis) {
4245
- console.log('đŸ§Ē Enhanced analysis: Spec structure is optimal');
4246
- }
4247
- if (options.predictiveScoring) {
4248
- console.log('🔮 Predictive score: High confidence in success');
4249
- }
4250
- } else {
4251
- console.log('❌ Experimental validation failed');
4252
- result.errors.forEach((error) => console.log(` ${error}`));
4253
- }
4254
- } catch (error) {
4255
- console.error('❌ Experimental validation error:', error.message);
4256
- process.exit(1);
4257
- }
4258
- })
4259
- )
4260
- .addCommand(
4261
- new Command('quality-gates')
4262
- .description('Run quality gates with experimental features')
4263
- .argument('<spec-file>', 'Path to working spec file')
4264
- .option('--smart-selection', 'Use smart test selection based on changes', false)
4265
- .option('--parallel-execution', 'Enable parallel gate execution', false)
4266
- .option('--predictive-failures', 'Predict and skip likely failures', false)
4267
- .action(async (specFile, options, cmd) => {
4268
- const isDryRun = cmd.parent.opts().dryRun;
4269
-
4270
- if (isDryRun) {
4271
- console.log('đŸœī¸ EXPERIMENTAL MODE - DRY RUN');
4272
- console.log('No quality gates will actually execute\n');
4273
- }
4274
-
4275
- console.log('đŸ§Ē Experimental Quality Gate Features:');
4276
- if (options.smartSelection) console.log(' â€ĸ Smart test selection');
4277
- if (options.parallelExecution) console.log(' â€ĸ Parallel execution');
4278
- if (options.predictiveFailures) console.log(' â€ĸ Predictive failure detection');
4279
- console.log('');
4280
-
4281
- if (isDryRun) {
4282
- console.log('đŸŽ¯ Would run quality gates for:', specFile);
4283
- console.log('🧠 Smart selection:', options.smartSelection);
4284
- console.log('⚡ Parallel execution:', options.parallelExecution);
4285
- console.log('🔮 Predictive failures:', options.predictiveFailures);
4286
- console.log('\n✅ Dry run completed - no gates executed');
4287
- return;
4288
- }
4289
-
4290
- // Implement experimental quality gate logic
4291
- try {
4292
- console.log('🚀 Running experimental quality gates...');
4293
-
4294
- // For now, fall back to standard quality gates
4295
- await runQualityGatesForSpec(require('fs').readFileSync(specFile, 'utf8'), false);
4296
-
4297
- console.log('✅ Experimental quality gates completed');
4298
- if (options.parallelExecution) {
4299
- console.log('⚡ Parallel execution: Simulated parallel processing');
4300
- }
4301
- if (options.smartSelection) {
4302
- console.log('🧠 Smart selection: Optimized test execution');
4303
- }
4304
- } catch (error) {
4305
- console.error('❌ Experimental quality gates failed:', error.message);
4306
- process.exit(1);
4307
- }
4308
- })
4309
- );
4310
-
4311
- program
4312
- .command('waivers')
4313
- .description('Manage CAWS waivers (fast-lane escape hatches)')
4314
- .addCommand(
4315
- new Command('create')
4316
- .description('Create a new waiver')
4317
- .requiredOption('-t, --title <title>', 'Waiver title (10-200 characters)')
4318
- .requiredOption(
4319
- '-r, --reason <reason>',
4320
- 'Waiver reason (emergency_hotfix, legacy_integration, experimental_feature, third_party_constraint, performance_critical, security_patch, infrastructure_limitation, other)'
4321
- )
4322
- .requiredOption('-d, --description <desc>', 'Detailed description (50-1000 characters)')
4323
- .requiredOption('-g, --gates <gates>', 'Comma-separated list of gates to waive')
4324
- .requiredOption('--expires-at <datetime>', 'Expiration date (ISO 8601 format)')
4325
- .requiredOption('--approved-by <approver>', 'Person/entity approving the waiver')
4326
- .requiredOption('--impact-level <level>', 'Risk impact level (low, medium, high, critical)')
4327
- .requiredOption('--mitigation-plan <plan>', 'Risk mitigation plan (50+ characters)')
4328
- .option('--review-required', 'Flag waiver as requiring manual review', false)
4329
- .option('--environment <env>', 'Environment restriction (development, staging, production)')
4330
- .option('--urgency <level>', 'Urgency level (low, normal, high, critical)', 'normal')
4331
- .option('--related-pr <pr>', 'Related pull request URL')
4332
- .option('--related-issue <issue>', 'Related issue URL')
4333
- .action(async (options) => {
4334
- try {
4335
- const WaiversManager = require('./waivers-manager');
4336
- const waiversManager = new WaiversManager();
4337
-
4338
- const waiverData = {
4339
- title: options.title,
4340
- reason: options.reason,
4341
- description: options.description,
4342
- gates: options.gates.split(',').map((g) => g.trim()),
4343
- expires_at: options.expiresAt,
4344
- approved_by: options.approvedBy,
4345
- risk_assessment: {
4346
- impact_level: options.impactLevel,
4347
- mitigation_plan: options.mitigationPlan,
4348
- review_required: options.reviewRequired || false,
4349
- },
4350
- metadata: {
4351
- environment: options.environment,
4352
- urgency: options.urgency,
4353
- related_pr: options.relatedPr,
4354
- related_issue: options.relatedIssue,
4355
- },
4356
- };
4357
-
4358
- const waiver = await waiversManager.createWaiver(waiverData);
4359
-
4360
- console.log(`✅ Waiver created successfully: ${waiver.id}`);
4361
- console.log(`📋 Title: ${waiver.title}`);
4362
- console.log(`⏰ Expires: ${waiver.expires_at}`);
4363
- console.log(`đŸŽ¯ Gates waived: ${waiver.gates.join(', ')}`);
4364
-
4365
- if (
4366
- waiver.risk_assessment.impact_level === 'critical' ||
4367
- waiver.risk_assessment.review_required
4368
- ) {
4369
- console.log('\nâš ī¸ HIGH RISK WAIVER - Manual review required');
4370
- console.log(`📄 Review file created: .caws/waivers/review-${waiver.id}.md`);
4371
- console.log('🔍 Please have code owners review this waiver before use');
4372
- }
4373
- } catch (error) {
4374
- console.error('❌ Failed to create waiver:', error.message);
4375
- process.exit(1);
4376
- }
4377
- })
4378
- )
4379
- .addCommand(
4380
- new Command('list')
4381
- .alias('ls')
4382
- .description('List active waivers')
4383
- .option('-v, --verbose', 'Show detailed waiver information')
4384
- .option('--expiring-soon', 'Show only waivers expiring within 7 days')
4385
- .option('--high-risk', 'Show only high/critical risk waivers')
4386
- .action(async (options) => {
4387
- try {
4388
- const WaiversManager = require('./waivers-manager');
4389
- const waiversManager = new WaiversManager();
4390
-
4391
- let waivers = await waiversManager.getActiveWaivers();
4392
-
4393
- // Apply filters
4394
- if (options.expiringSoon) {
4395
- const sevenDaysFromNow = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
4396
- waivers = waivers.filter((w) => new Date(w.expires_at) <= sevenDaysFromNow);
4397
- }
4398
-
4399
- if (options.highRisk) {
4400
- waivers = waivers.filter(
4401
- (w) =>
4402
- w.risk_assessment.impact_level === 'high' ||
4403
- w.risk_assessment.impact_level === 'critical'
4404
- );
4405
- }
4406
-
4407
- if (waivers.length === 0) {
4408
- console.log('â„šī¸ No active waivers found');
4409
- return;
4410
- }
4411
-
4412
- console.log(`📋 Active waivers: ${waivers.length}`);
4413
- console.log('');
4414
-
4415
- for (const waiver of waivers) {
4416
- const expiresAt = new Date(waiver.expires_at);
4417
- const now = new Date();
4418
- const daysRemaining = Math.ceil((expiresAt - now) / (24 * 60 * 60 * 1000));
4419
-
4420
- console.log(`🔖 ${waiver.id}: ${waiver.title}`);
4421
- console.log(` ⏰ Expires: ${waiver.expires_at} (${daysRemaining} days remaining)`);
4422
- console.log(` đŸŽ¯ Gates: ${waiver.gates.join(', ')}`);
4423
- console.log(` âš ī¸ Risk: ${waiver.risk_assessment.impact_level}`);
4424
- console.log(` 👤 Approved by: ${waiver.approved_by}`);
4425
-
4426
- if (options.verbose) {
4427
- console.log(` 📝 Reason: ${waiver.reason}`);
4428
- console.log(
4429
- ` 📄 Description: ${waiver.description.substring(0, 100)}${waiver.description.length > 100 ? '...' : ''}`
4430
- );
4431
- console.log(
4432
- ` đŸ›Ąī¸ Mitigation: ${waiver.risk_assessment.mitigation_plan.substring(0, 100)}${waiver.risk_assessment.mitigation_plan.length > 100 ? '...' : ''}`
4433
- );
4434
-
4435
- if (waiver.metadata) {
4436
- const metadata = [];
4437
- if (waiver.metadata.environment)
4438
- metadata.push(`Env: ${waiver.metadata.environment}`);
4439
- if (waiver.metadata.urgency) metadata.push(`Urgency: ${waiver.metadata.urgency}`);
4440
- if (waiver.metadata.related_pr) metadata.push(`PR: ${waiver.metadata.related_pr}`);
4441
- if (metadata.length > 0) {
4442
- console.log(` 📊 Metadata: ${metadata.join(', ')}`);
4443
- }
4444
- }
4445
- }
4446
-
4447
- console.log('');
4448
- }
4449
- } catch (error) {
4450
- console.error('❌ Failed to list waivers:', error.message);
4451
- process.exit(1);
4452
- }
4453
- })
4454
- )
4455
- .addCommand(
4456
- new Command('revoke')
4457
- .description('Revoke an active waiver')
4458
- .argument('<waiver-id>', 'Waiver ID to revoke')
4459
- .option('-r, --reason <reason>', 'Reason for revocation', 'Manual revocation')
4460
- .action(async (waiverId, options) => {
4461
- try {
4462
- const WaiversManager = require('./waivers-manager');
4463
- const waiversManager = new WaiversManager();
4464
-
4465
- const waiver = await waiversManager.revokeWaiver(waiverId, options.reason);
4466
-
4467
- console.log(`✅ Waiver revoked: ${waiverId}`);
4468
- console.log(`📝 Reason: ${options.reason}`);
4469
- console.log(`đŸŽ¯ Gates no longer waived: ${waiver.gates.join(', ')}`);
4470
- } catch (error) {
4471
- console.error('❌ Failed to revoke waiver:', error.message);
4472
- process.exit(1);
4473
- }
4474
- })
4475
- )
4476
- .addCommand(
4477
- new Command('extend')
4478
- .description('Extend waiver expiration date')
4479
- .argument('<waiver-id>', 'Waiver ID to extend')
4480
- .requiredOption('--new-expiry <datetime>', 'New expiration date (ISO 8601 format)')
4481
- .requiredOption('--approved-by <approver>', 'Person approving the extension')
4482
- .action(async (waiverId, options) => {
4483
- try {
4484
- const WaiversManager = require('./waivers-manager');
4485
- const waiversManager = new WaiversManager();
4486
-
4487
- const waiver = await waiversManager.extendWaiver(
4488
- waiverId,
4489
- options.newExpiry,
4490
- options.approvedBy
4491
- );
4492
-
4493
- console.log(`✅ Waiver extended: ${waiverId}`);
4494
- console.log(`⏰ New expiry: ${waiver.expires_at}`);
4495
- console.log(`👤 Extended by: ${options.approvedBy}`);
4496
- } catch (error) {
4497
- console.error('❌ Failed to extend waiver:', error.message);
4498
- process.exit(1);
4499
- }
4500
- })
4501
- )
4502
- .addCommand(
4503
- new Command('stats')
4504
- .description('Show waiver statistics and health metrics')
4505
- .action(async () => {
4506
- try {
4507
- const WaiversManager = require('./waivers-manager');
4508
- const waiversManager = new WaiversManager();
4509
-
4510
- const stats = await waiversManager.getWaiverStats();
4511
-
4512
- console.log('📊 Waiver Statistics');
4513
- console.log('==================');
4514
-
4515
- console.log(`📋 Total active waivers: ${stats.total_active}`);
4516
- console.log(`đŸŽ¯ Total gates waived: ${stats.total_gates_waived}`);
4517
- console.log(`📅 Average lifespan: ${stats.average_lifespan_days.toFixed(1)} days`);
4518
-
4519
- if (stats.expiring_soon.length > 0) {
4520
- console.log(`\n⏰ Expiring soon (${stats.expiring_soon.length}):`);
4521
- stats.expiring_soon.forEach((w) => {
4522
- console.log(` ${w.id}: ${w.days_remaining} days (${w.title.substring(0, 50)}...)`);
4523
- });
4524
- }
4525
-
4526
- if (stats.high_risk.length > 0) {
4527
- console.log(`\nâš ī¸ High risk waivers (${stats.high_risk.length}):`);
4528
- stats.high_risk.forEach((w) => {
4529
- console.log(
4530
- ` ${w.id}: ${w.risk_level} - ${w.reason} (${w.title.substring(0, 40)}...)`
4531
- );
4532
- });
4533
- }
4534
-
4535
- console.log(`\n📈 By reason:`);
4536
- Object.entries(stats.by_reason).forEach(([reason, count]) => {
4537
- console.log(` ${reason}: ${count}`);
4538
- });
4539
-
4540
- console.log(`\nâš ī¸ By risk level:`);
4541
- Object.entries(stats.by_risk_level).forEach(([level, count]) => {
4542
- console.log(` ${level}: ${count}`);
4543
- });
4544
- } catch (error) {
4545
- console.error('❌ Failed to get waiver stats:', error.message);
4546
- process.exit(1);
4547
- }
4548
- })
4549
- );
4550
-
4551
- program
4552
- .command('tools')
4553
- .description('Manage CAWS tools')
4554
- .addCommand(
4555
- new Command('list')
4556
- .alias('ls')
4557
- .description('List available tools')
4558
- .option('-v, --verbose', 'Show detailed tool information')
4559
- .action(async (options) => {
4560
- try {
4561
- const loader = await initializeToolSystem();
4562
- if (!loader) {
4563
- console.error(chalk.red('❌ Tool system not available'));
4564
- process.exit(1);
4565
- }
4566
-
4567
- const tools = loader.getAllTools();
4568
-
4569
- if (tools.size === 0) {
4570
- console.log(chalk.yellow('âš ī¸ No tools loaded'));
4571
- console.log(chalk.blue('💡 Add tools to apps/tools/caws/ directory'));
4572
- return;
4573
- }
4574
-
4575
- console.log(chalk.blue(`🔧 Available Tools (${tools.size}):`));
4576
- console.log('');
4577
-
4578
- for (const [id, tool] of tools) {
4579
- const metadata = tool.metadata;
4580
- console.log(chalk.green(`đŸ“Ļ ${metadata.name} (${id})`));
4581
- console.log(` ${metadata.description || 'No description'}`);
4582
- if (metadata.version) {
4583
- console.log(chalk.gray(` Version: ${metadata.version}`));
4584
- }
4585
- if (metadata.capabilities && metadata.capabilities.length > 0) {
4586
- console.log(chalk.gray(` Capabilities: ${metadata.capabilities.join(', ')}`));
4587
- }
4588
- if (options.verbose && metadata.dependencies) {
4589
- console.log(chalk.gray(` Dependencies: ${metadata.dependencies.join(', ')}`));
4590
- }
4591
- console.log('');
4592
- }
4593
- } catch (error) {
4594
- console.error(chalk.red('❌ Error listing tools:'), error.message);
4595
- process.exit(1);
4596
- }
4597
- })
4598
- )
4599
- .addCommand(
4600
- new Command('run')
4601
- .description('Execute a specific tool')
4602
- .argument('<tool-id>', 'Tool identifier to execute')
4603
- .option('-p, --params <json>', 'JSON parameters for tool execution')
4604
- .option('-t, --timeout <ms>', 'Execution timeout in milliseconds', parseInt, 30000)
4605
- .action(async (toolId, options) => {
4606
- try {
4607
- const loader = await initializeToolSystem();
4608
- if (!loader) {
4609
- console.error(chalk.red('❌ Tool system not available'));
4610
- process.exit(1);
4611
- }
4612
-
4613
- // Load all tools first
4614
- await loader.loadAllTools();
4615
- const tool = loader.getTool(toolId);
4616
-
4617
- if (!tool) {
4618
- console.error(chalk.red(`❌ Tool '${toolId}' not found`));
4619
- console.log(chalk.blue('💡 Available tools:'));
4620
- const tools = loader.getAllTools();
4621
- for (const [id, t] of tools) {
4622
- console.log(` - ${id}: ${t.metadata.name}`);
4623
- }
4624
- process.exit(1);
4625
- }
4626
-
4627
- // Validate tool before execution
4628
- const validation = await toolValidator.validateTool(tool);
4629
- if (!validation.valid) {
4630
- console.error(chalk.red('❌ Tool validation failed:'));
4631
- validation.errors.forEach((error) => {
4632
- console.error(` ${chalk.red('✗')} ${error}`);
4633
- });
4634
- process.exit(1);
4635
- }
4636
-
4637
- // Parse parameters
4638
- let params = {};
4639
- if (options.params) {
4640
- try {
4641
- params = JSON.parse(options.params);
4642
- } catch (error) {
4643
- console.error(chalk.red('❌ Invalid JSON parameters:'), error.message);
4644
- process.exit(1);
4645
- }
4646
- }
4647
-
4648
- console.log(chalk.blue(`🚀 Executing tool: ${tool.metadata.name}`));
4649
-
4650
- // Execute tool
4651
- const result = await tool.module.execute(params, {
4652
- workingDirectory: process.cwd(),
4653
- timeout: options.timeout,
4654
- });
4655
-
4656
- // Display results
4657
- if (result.success) {
4658
- console.log(chalk.green('✅ Tool execution successful'));
4659
- if (result.output && typeof result.output === 'object') {
4660
- console.log(chalk.gray('Output:'), JSON.stringify(result.output, null, 2));
4661
- }
4662
- } else {
4663
- console.error(chalk.red('❌ Tool execution failed'));
4664
- result.errors.forEach((error) => {
4665
- console.error(` ${chalk.red('✗')} ${error}`);
4666
- });
4667
- process.exit(1);
4668
- }
4669
- } catch (error) {
4670
- console.error(chalk.red(`❌ Error executing tool ${toolId}:`), error.message);
4671
- process.exit(1);
4672
- }
4673
- })
4674
- );
4675
-
4676
- // Error handling
4677
- program.exitOverride((err) => {
4678
- if (
4679
- err.code === 'commander.help' ||
4680
- err.code === 'commander.version' ||
4681
- err.message.includes('outputHelp')
4682
- ) {
4683
- process.exit(0);
4684
- }
4685
- console.error(chalk.red('❌ Error:'), err.message);
4686
- process.exit(1);
4687
- });
4688
-
4689
- // Parse and run (only when run directly, not when required as module)
4690
- if (require.main === module) {
4691
- try {
4692
- program.parse();
4693
- } catch (error) {
4694
- if (
4695
- error.code === 'commander.help' ||
4696
- error.code === 'commander.version' ||
4697
- error.message.includes('outputHelp')
4698
- ) {
4699
- process.exit(0);
4700
- } else {
4701
- console.error(chalk.red('❌ Error:'), error.message);
4702
- process.exit(1);
4703
- }
4704
- }
4705
- }
4706
-
4707
- // Export functions for testing
4708
- module.exports = {
4709
- generateWorkingSpec,
4710
- validateGeneratedSpec,
4711
- };