@paths.design/caws-cli 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -37,7 +37,9 @@ try {
37
37
  }
38
38
  }
39
39
  } catch (error) {
40
- console.warn('⚠️ Language support tools not available');
40
+ console.warn(chalk.yellow('⚠️ Language support tools not available'));
41
+ console.warn(chalk.blue('💡 This may limit language-specific configuration features'));
42
+ console.warn(chalk.blue('💡 For full functionality, ensure caws-template package is available'));
41
43
  }
42
44
 
43
45
  const program = new Command();
@@ -125,41 +127,59 @@ function detectCAWSSetup(cwd = process.cwd()) {
125
127
  let templateDir = null;
126
128
  const possibleTemplatePaths = [
127
129
  // FIRST: Try bundled templates (for npm-installed CLI)
128
- path.resolve(__dirname, '../templates'),
129
- path.resolve(__dirname, 'templates'),
130
+ { path: path.resolve(__dirname, '../templates'), source: 'bundled with CLI' },
131
+ { path: path.resolve(__dirname, 'templates'), source: 'bundled with CLI (fallback)' },
130
132
  // Try relative to current working directory (for monorepo setups)
131
- path.resolve(cwd, '../caws-template'),
132
- path.resolve(cwd, '../../caws-template'),
133
- path.resolve(cwd, '../../../caws-template'),
134
- path.resolve(cwd, 'packages/caws-template'),
135
- path.resolve(cwd, 'caws-template'),
133
+ { path: path.resolve(cwd, '../caws-template'), source: 'monorepo parent directory' },
134
+ { path: path.resolve(cwd, '../../caws-template'), source: 'monorepo grandparent' },
135
+ { path: path.resolve(cwd, '../../../caws-template'), source: 'workspace root' },
136
+ { path: path.resolve(cwd, 'packages/caws-template'), source: 'packages/ subdirectory' },
137
+ { path: path.resolve(cwd, 'caws-template'), source: 'caws-template/ subdirectory' },
136
138
  // Try relative to CLI location (for installed CLI)
137
- path.resolve(__dirname, '../caws-template'),
138
- path.resolve(__dirname, '../../caws-template'),
139
- path.resolve(__dirname, '../../../caws-template'),
139
+ { path: path.resolve(__dirname, '../caws-template'), source: 'CLI installation' },
140
+ { path: path.resolve(__dirname, '../../caws-template'), source: 'CLI parent directory' },
141
+ { path: path.resolve(__dirname, '../../../caws-template'), source: 'CLI workspace root' },
140
142
  // Try absolute paths for CI environments
141
- path.resolve(process.cwd(), 'packages/caws-template'),
142
- path.resolve(process.cwd(), '../packages/caws-template'),
143
- path.resolve(process.cwd(), '../../packages/caws-template'),
144
- path.resolve(process.cwd(), '../../../packages/caws-template'),
143
+ { path: path.resolve(process.cwd(), 'packages/caws-template'), source: 'current packages/' },
144
+ { path: path.resolve(process.cwd(), '../packages/caws-template'), source: 'parent packages/' },
145
+ {
146
+ path: path.resolve(process.cwd(), '../../packages/caws-template'),
147
+ source: 'grandparent packages/',
148
+ },
149
+ {
150
+ path: path.resolve(process.cwd(), '../../../packages/caws-template'),
151
+ source: 'workspace packages/',
152
+ },
145
153
  // Try from workspace root
146
- path.resolve(process.cwd(), 'caws-template'),
154
+ { path: path.resolve(process.cwd(), 'caws-template'), source: 'workspace caws-template/' },
147
155
  // Try various other common locations
148
- '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
149
- '/workspace/packages/caws-template',
150
- '/caws/packages/caws-template',
156
+ {
157
+ path: '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
158
+ source: 'GitHub Actions CI',
159
+ },
160
+ { path: '/workspace/packages/caws-template', source: 'Docker workspace' },
161
+ { path: '/caws/packages/caws-template', source: 'Container workspace' },
151
162
  ];
152
163
 
153
- for (const testPath of possibleTemplatePaths) {
164
+ for (const { path: testPath, source } of possibleTemplatePaths) {
154
165
  if (fs.existsSync(testPath)) {
155
166
  templateDir = testPath;
156
167
  if (!isQuietCommand) {
157
- console.log(`✅ Found template directory: ${testPath}`);
168
+ console.log(`✅ Found CAWS templates in ${source}:`);
169
+ console.log(` ${chalk.gray(testPath)}`);
158
170
  }
159
171
  break;
160
172
  }
161
173
  }
162
174
 
175
+ if (!templateDir && !isQuietCommand) {
176
+ console.warn(chalk.yellow('⚠️ CAWS templates not found in standard locations'));
177
+ console.warn(chalk.blue('💡 This may limit available scaffolding features'));
178
+ console.warn(
179
+ chalk.blue('💡 For full functionality, ensure caws-template package is available')
180
+ );
181
+ }
182
+
163
183
  const hasTemplateDir = templateDir !== null;
164
184
 
165
185
  return {
@@ -377,6 +397,514 @@ const validateWorkingSpec = (spec) => {
377
397
  }
378
398
  };
379
399
 
400
+ /**
401
+ * Enhanced validation with suggestions and auto-fix
402
+ */
403
+ function validateWorkingSpecWithSuggestions(spec, options = {}) {
404
+ const { autoFix = false, suggestions = true } = options;
405
+
406
+ try {
407
+ // Basic structural validation for essential fields
408
+ const requiredFields = [
409
+ 'id',
410
+ 'title',
411
+ 'risk_tier',
412
+ 'mode',
413
+ 'change_budget',
414
+ 'blast_radius',
415
+ 'operational_rollback_slo',
416
+ 'scope',
417
+ 'invariants',
418
+ 'acceptance',
419
+ 'non_functional',
420
+ 'contracts',
421
+ ];
422
+
423
+ let errors = [];
424
+ let warnings = [];
425
+ let fixes = [];
426
+
427
+ for (const field of requiredFields) {
428
+ if (!spec[field]) {
429
+ errors.push({
430
+ instancePath: `/${field}`,
431
+ message: `Missing required field: ${field}`,
432
+ suggestion: getFieldSuggestion(field, spec),
433
+ canAutoFix: canAutoFixField(field, spec),
434
+ });
435
+ }
436
+ }
437
+
438
+ // Validate specific field formats
439
+ if (spec.id && !/^[A-Z]+-\d+$/.test(spec.id)) {
440
+ errors.push({
441
+ instancePath: '/id',
442
+ message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
443
+ suggestion: 'Use format like: PROJ-001, FEAT-002, FIX-003',
444
+ canAutoFix: false,
445
+ });
446
+ }
447
+
448
+ // Validate risk tier
449
+ if (spec.risk_tier !== undefined && (spec.risk_tier < 1 || spec.risk_tier > 3)) {
450
+ errors.push({
451
+ instancePath: '/risk_tier',
452
+ message: 'Risk tier must be 1, 2, or 3',
453
+ suggestion:
454
+ 'Tier 1: Critical (auth, billing), Tier 2: Standard (features), Tier 3: Low risk (UI)',
455
+ canAutoFix: true,
456
+ });
457
+ fixes.push({ field: 'risk_tier', value: Math.max(1, Math.min(3, spec.risk_tier || 2)) });
458
+ }
459
+
460
+ // Validate scope.in is not empty
461
+ if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
462
+ errors.push({
463
+ instancePath: '/scope/in',
464
+ message: 'Scope IN must not be empty',
465
+ suggestion: 'Specify directories/files that are included in changes',
466
+ canAutoFix: false,
467
+ });
468
+ }
469
+
470
+ // Check for common issues
471
+ if (!spec.invariants || spec.invariants.length === 0) {
472
+ warnings.push({
473
+ instancePath: '/invariants',
474
+ message: 'No system invariants defined',
475
+ suggestion: 'Add 1-3 statements about what must always remain true',
476
+ });
477
+ }
478
+
479
+ if (!spec.acceptance || spec.acceptance.length === 0) {
480
+ warnings.push({
481
+ instancePath: '/acceptance',
482
+ message: 'No acceptance criteria defined',
483
+ suggestion: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
484
+ });
485
+ }
486
+
487
+ // Validate experimental mode
488
+ if (spec.experimental_mode) {
489
+ if (typeof spec.experimental_mode !== 'object') {
490
+ errors.push({
491
+ instancePath: '/experimental_mode',
492
+ message:
493
+ 'Experimental mode must be an object with enabled, rationale, and expires_at fields',
494
+ suggestion: 'Fix experimental_mode structure',
495
+ canAutoFix: false,
496
+ });
497
+ } else {
498
+ const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
499
+ for (const field of requiredExpFields) {
500
+ if (!(field in spec.experimental_mode)) {
501
+ errors.push({
502
+ instancePath: `/experimental_mode/${field}`,
503
+ message: `Missing required experimental mode field: ${field}`,
504
+ suggestion: `Add ${field} to experimental_mode`,
505
+ canAutoFix: field === 'enabled' ? true : false,
506
+ });
507
+ if (field === 'enabled') {
508
+ fixes.push({ field: `experimental_mode.${field}`, value: true });
509
+ }
510
+ }
511
+ }
512
+
513
+ if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
514
+ warnings.push({
515
+ instancePath: '/experimental_mode',
516
+ message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
517
+ suggestion: 'Either set risk_tier to 3 or disable experimental mode',
518
+ });
519
+ }
520
+ }
521
+ }
522
+
523
+ // Apply auto-fixes if requested
524
+ if (autoFix && fixes.length > 0) {
525
+ console.log(chalk.cyan('🔧 Applying auto-fixes...'));
526
+ for (const fix of fixes) {
527
+ if (fix.field.includes('.')) {
528
+ const [parent, child] = fix.field.split('.');
529
+ if (!spec[parent]) spec[parent] = {};
530
+ spec[parent][child] = fix.value;
531
+ } else {
532
+ spec[fix.field] = fix.value;
533
+ }
534
+ console.log(` Fixed: ${fix.field} = ${JSON.stringify(fix.value)}`);
535
+ }
536
+ }
537
+
538
+ // Display results
539
+ if (errors.length > 0) {
540
+ console.error(chalk.red('❌ Validation failed with errors:'));
541
+ errors.forEach((error, index) => {
542
+ console.error(`${index + 1}. ${error.instancePath || 'root'}: ${error.message}`);
543
+ if (suggestions && error.suggestion) {
544
+ console.error(` 💡 ${error.suggestion}`);
545
+ }
546
+ if (error.canAutoFix) {
547
+ console.error(` 🔧 Can auto-fix: ${autoFix ? 'applied' : 'run with --auto-fix'}`);
548
+ }
549
+ });
550
+ return { valid: false, errors, warnings };
551
+ }
552
+
553
+ if (warnings.length > 0 && suggestions) {
554
+ console.warn(chalk.yellow('⚠️ Validation passed with warnings:'));
555
+ warnings.forEach((warning, index) => {
556
+ console.warn(`${index + 1}. ${warning.instancePath || 'root'}: ${warning.message}`);
557
+ if (warning.suggestion) {
558
+ console.warn(` 💡 ${warning.suggestion}`);
559
+ }
560
+ });
561
+ }
562
+
563
+ console.log(chalk.green('✅ Working specification is valid'));
564
+ return { valid: true, errors: [], warnings };
565
+ } catch (error) {
566
+ console.error(chalk.red('❌ Error during validation:'), error.message);
567
+ return {
568
+ valid: false,
569
+ errors: [{ instancePath: '', message: `Validation error: ${error.message}` }],
570
+ warnings: [],
571
+ };
572
+ }
573
+ }
574
+
575
+ function getFieldSuggestion(field, _spec) {
576
+ const suggestions = {
577
+ id: 'Use format like: PROJ-001, FEAT-002, FIX-003',
578
+ title: 'Add a descriptive project title',
579
+ risk_tier: 'Choose: 1 (critical), 2 (standard), or 3 (low risk)',
580
+ mode: 'Choose: feature, refactor, fix, doc, or chore',
581
+ change_budget: 'Set max_files and max_loc based on risk tier',
582
+ blast_radius: 'List affected modules and data migration needs',
583
+ operational_rollback_slo: 'Choose: 1m, 5m, 15m, or 1h',
584
+ scope: "Define what's included (in) and excluded (out) from changes",
585
+ invariants: 'Add 1-3 statements about what must always remain true',
586
+ acceptance: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
587
+ non_functional: 'Define accessibility, performance, and security requirements',
588
+ contracts: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
589
+ };
590
+ return suggestions[field] || `Add the ${field} field`;
591
+ }
592
+
593
+ function canAutoFixField(field, _spec) {
594
+ const autoFixable = ['risk_tier'];
595
+ return autoFixable.includes(field);
596
+ }
597
+
598
+ /**
599
+ * Generate a getting started guide based on project analysis
600
+ */
601
+ function generateGettingStartedGuide(analysis) {
602
+ const { projectType, packageJson, hasTests, hasLinting } = analysis;
603
+
604
+ const projectName = packageJson.name || 'your-project';
605
+ const capitalizedType = projectType.charAt(0).toUpperCase() + projectType.slice(1);
606
+
607
+ let guide = `# Getting Started with CAWS - ${capitalizedType} Project
608
+
609
+ **Project**: ${projectName}
610
+ **Type**: ${capitalizedType}
611
+ **Generated**: ${new Date().toLocaleDateString()}
612
+
613
+ ---
614
+
615
+ ## Phase 1: Setup Verification (15 mins)
616
+
617
+ Complete these steps to ensure your CAWS setup is working:
618
+
619
+ ### ✅ Already Done
620
+ - [x] Initialize CAWS project
621
+ - [x] Generate working spec
622
+ - [x] Set up basic structure
623
+
624
+ ### Next Steps
625
+ - [ ] Review \`.caws/working-spec.yaml\` - customize for your needs
626
+ - [ ] Run validation: \`caws validate --suggestions\`
627
+ - [ ] Review tier policy in \`.caws/policy/\` (if applicable)
628
+ - [ ] Update \`.caws/templates/\` with project-specific examples
629
+
630
+ ---
631
+
632
+ ## Phase 2: First Feature (30 mins)
633
+
634
+ Time to create your first CAWS-managed feature:
635
+
636
+ ### Steps
637
+ 1. **Copy a template**:
638
+ \`\`\`bash
639
+ cp .caws/templates/feature.plan.md docs/plans/FEATURE-001.md
640
+ \`\`\`
641
+
642
+ 2. **Customize the plan**:
643
+ - Update title and description
644
+ - Fill in acceptance criteria (GIVEN/WHEN/THEN format)
645
+ - Set appropriate risk tier
646
+ - Define scope and invariants
647
+
648
+ 3. **Write tests first** (TDD approach):
649
+ \`\`\`bash
650
+ # For ${projectType} projects, focus on:
651
+ ${getTestingGuidance(projectType)}
652
+ \`\`\`
653
+
654
+ 4. **Implement the feature**:
655
+ - Stay within change budget limits
656
+ - Follow acceptance criteria
657
+ - Maintain system invariants
658
+
659
+ 5. **Run full verification**:
660
+ \`\`\`bash
661
+ caws validate --suggestions
662
+ ${hasTests ? 'npm test' : '# Add tests when ready'}
663
+ ${hasLinting ? 'npm run lint' : '# Add linting when ready'}
664
+ \`\`\`
665
+
666
+ ---
667
+
668
+ ## Phase 3: CI/CD Setup (20 mins)
669
+
670
+ Set up automated quality gates:
671
+
672
+ ### GitHub Actions (Recommended)
673
+ 1. **Create workflow**: \`.github/workflows/caws.yml\`
674
+ \`\`\`yaml
675
+ name: CAWS Quality Gates
676
+ on: [pull_request]
677
+
678
+ jobs:
679
+ validate:
680
+ runs-on: ubuntu-latest
681
+ steps:
682
+ - uses: actions/checkout@v4
683
+ - uses: actions/setup-node@v4
684
+ with:
685
+ node-version: '18'
686
+ - run: npm ci
687
+ - run: npx caws validate --quiet
688
+ - run: npm test # Add when ready
689
+ \`\`\`
690
+
691
+ 2. **Configure branch protection**:
692
+ - Require PR validation
693
+ - Require tests to pass
694
+ - Require CAWS spec validation
695
+
696
+ ### Other CI Systems
697
+ - **GitLab CI**: Use \`caws validate\` in \`.gitlab-ci.yml\`
698
+ - **Jenkins**: Add validation step to pipeline
699
+ - **CircleCI**: Include in \`.circleci/config.yml\`
700
+
701
+ ---
702
+
703
+ ## Phase 4: Team Onboarding (ongoing)
704
+
705
+ ### For Team Members
706
+ 1. **Read the basics**: Start with this guide
707
+ 2. **Learn by example**: Review completed features
708
+ 3. **Practice**: Create small features following the process
709
+ 4. **Contribute**: Help improve templates and processes
710
+
711
+ ### For Project Leads
712
+ 1. **Customize templates**: Adapt to team preferences
713
+ 2. **Set standards**: Define project-specific conventions
714
+ 3. **Monitor quality**: Review metrics and adjust gates
715
+ 4. **Scale practices**: Apply CAWS to more complex work
716
+
717
+ ---
718
+
719
+ ## Key Concepts Quick Reference
720
+
721
+ ### Risk Tiers
722
+ - **Tier 1**: Critical (auth, billing, migrations) - Max rigor
723
+ - **Tier 2**: Standard (features, APIs) - Standard rigor
724
+ - **Tier 3**: Low risk (UI, tooling) - Basic rigor
725
+
726
+ ### Change Budget
727
+ - Limits help maintain quality and reviewability
728
+ - Adjust based on risk tier and team experience
729
+ - Track actual vs. budgeted changes
730
+
731
+ ### System Invariants
732
+ - Core guarantees that must always hold true
733
+ - Examples: "Data integrity maintained", "API contracts honored"
734
+ - Define 2-4 key invariants for your system
735
+
736
+ ### Acceptance Criteria
737
+ - Use GIVEN/WHEN/THEN format
738
+ - Focus on observable behavior
739
+ - Include edge cases and error conditions
740
+
741
+ ---
742
+
743
+ ## Common Pitfalls to Avoid
744
+
745
+ ### For ${capitalizedType} Projects
746
+ ${getProjectSpecificPitfalls(projectType)}
747
+
748
+ ### General Issues
749
+ 1. **Over-customization**: Start with defaults, customize gradually
750
+ 2. **Missing invariants**: Define what must never break
751
+ 3. **Vague acceptance**: Make criteria measurable and testable
752
+ 4. **Large changes**: Break big features into smaller, reviewable pieces
753
+
754
+ ---
755
+
756
+ ## Resources
757
+
758
+ ### Documentation
759
+ - **Quick Reference**: This guide
760
+ - **Templates**: \`.caws/templates/\`
761
+ - **Examples**: \`.caws/examples/\` (when available)
762
+
763
+ ### Commands
764
+ - \`caws validate --suggestions\` - Get help with issues
765
+ - \`caws validate --auto-fix\` - Fix safe problems automatically
766
+ - \`caws init --interactive\` - Customize existing setup
767
+
768
+ ### Community
769
+ - **GitHub Issues**: Report problems and request features
770
+ - **Discussions**: Share experiences and best practices
771
+ - **Wiki**: Growing collection of examples and guides
772
+
773
+ ---
774
+
775
+ ## Next Steps
776
+
777
+ 1. **Right now**: Review your working spec and customize it
778
+ 2. **Today**: Create your first feature plan
779
+ 3. **This week**: Set up CI/CD and branch protection
780
+ 4. **Ongoing**: Refine processes based on team feedback
781
+
782
+ Remember: CAWS is a framework, not a straightjacket. Adapt it to your team's needs while maintaining the core principles of determinism and quality.
783
+
784
+ **Happy coding! 🎯**
785
+ `;
786
+
787
+ return guide;
788
+ }
789
+
790
+ function getTestingGuidance(projectType) {
791
+ const guidance = {
792
+ extension: `- Webview rendering tests\n- Command registration tests\n- Extension activation tests`,
793
+ library: `- Component rendering tests\n- API function tests\n- Type export tests`,
794
+ api: `- Endpoint response tests\n- Error handling tests\n- Authentication tests`,
795
+ cli: `- Command parsing tests\n- Output formatting tests\n- Error code tests`,
796
+ monorepo: `- Cross-package integration tests\n- Shared module tests\n- Build pipeline tests`,
797
+ application: `- User interaction tests\n- State management tests\n- Integration tests`,
798
+ };
799
+ return (
800
+ guidance[projectType] || `- Unit tests for core functions\n- Integration tests for workflows`
801
+ );
802
+ }
803
+
804
+ function getProjectSpecificPitfalls(projectType) {
805
+ const pitfalls = {
806
+ extension: `1. **Webview security**: Never use \`vscode.executeCommand\` from untrusted content
807
+ 2. **Activation timing**: Test cold start performance
808
+ 3. **API compatibility**: Check VS Code API version compatibility`,
809
+ library: `1. **Bundle size**: Monitor and limit package size
810
+ 2. **Type exports**: Ensure all public APIs are typed
811
+ 3. **Peer dependencies**: Handle React/Angular versions carefully`,
812
+ api: `1. **Backward compatibility**: Version APIs carefully
813
+ 2. **Rate limiting**: Test and document limits
814
+ 3. **Data validation**: Validate all inputs thoroughly`,
815
+ cli: `1. **Exit codes**: Use standard codes (0=success, 1=error)
816
+ 2. **Help text**: Keep it concise and helpful
817
+ 3. **Error messages**: Make them actionable`,
818
+ monorepo: `1. **Dependency cycles**: Avoid circular imports
819
+ 2. **Version consistency**: Keep package versions aligned
820
+ 3. **Build order**: Ensure correct build dependencies`,
821
+ application: `1. **State consistency**: Prevent invalid state transitions
822
+ 2. **Performance**: Monitor and optimize critical paths
823
+ 3. **Accessibility**: Test with screen readers and keyboard navigation`,
824
+ };
825
+ return (
826
+ pitfalls[projectType] ||
827
+ `1. **Test coverage**: Maintain adequate test coverage
828
+ 2. **Documentation**: Keep code and APIs documented
829
+ 3. **Dependencies**: Review and update regularly`
830
+ );
831
+ }
832
+
833
+ /**
834
+ * Generate smart .gitignore patterns for CAWS projects
835
+ */
836
+ function generateGitignorePatterns(existingGitignore = '') {
837
+ const cawsPatterns = `
838
+ # CAWS Configuration (tracked - these should be versioned)
839
+ # Note: .caws/ and .agent/ are tracked for provenance
840
+ # But we exclude temporary/generated files:
841
+
842
+ # CAWS temporary files (ignored)
843
+ .agent/temp/
844
+ .agent/cache/
845
+ .caws/.cache/
846
+ .caws/tmp/
847
+
848
+ # Build outputs (common patterns)
849
+ dist/
850
+ build/
851
+ *.tsbuildinfo
852
+ .next/
853
+ .nuxt/
854
+ .vite/
855
+
856
+ # Dependencies
857
+ node_modules/
858
+ .pnpm-store/
859
+
860
+ # Environment files
861
+ .env
862
+ .env.local
863
+ .env.development.local
864
+ .env.test.local
865
+ .env.production.local
866
+
867
+ # IDE files
868
+ .vscode/settings.json
869
+ .idea/
870
+ *.swp
871
+ *.swo
872
+
873
+ # OS files
874
+ .DS_Store
875
+ Thumbs.db
876
+
877
+ # Logs
878
+ logs/
879
+ *.log
880
+ npm-debug.log*
881
+ yarn-debug.log*
882
+ yarn-error.log*
883
+
884
+ # Coverage reports
885
+ coverage/
886
+ .nyc_output/
887
+
888
+ # Test results
889
+ test-results/
890
+ playwright-report/
891
+ `;
892
+
893
+ // If there's an existing .gitignore, merge intelligently
894
+ if (existingGitignore.trim()) {
895
+ // Check if CAWS patterns are already present
896
+ if (existingGitignore.includes('# CAWS Configuration')) {
897
+ console.log(chalk.blue('ℹ️ .gitignore already contains CAWS patterns - skipping'));
898
+ return existingGitignore;
899
+ }
900
+
901
+ // Append CAWS patterns to existing .gitignore
902
+ return existingGitignore.trim() + '\n\n' + cawsPatterns.trim() + '\n';
903
+ }
904
+
905
+ return cawsPatterns.trim();
906
+ }
907
+
380
908
  // Only log schema validation if not running quiet commands
381
909
  if (!process.argv.includes('--version') && !process.argv.includes('-V')) {
382
910
  console.log(chalk.green('✅ Schema validation initialized successfully'));
@@ -570,11 +1098,317 @@ function validateGeneratedSpec(specContent, _answers) {
570
1098
  }
571
1099
  }
572
1100
 
1101
+ /**
1102
+ * Detect project type from existing files and structure
1103
+ */
1104
+ function detectProjectType(cwd = process.cwd()) {
1105
+ const files = fs.readdirSync(cwd);
1106
+
1107
+ // Check for various project indicators
1108
+ const hasPackageJson = files.includes('package.json');
1109
+ const hasPnpm = files.includes('pnpm-workspace.yaml');
1110
+ const hasYarn = files.includes('yarn.lock');
1111
+
1112
+ let packageJson = {};
1113
+ if (hasPackageJson) {
1114
+ try {
1115
+ packageJson = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
1116
+ } catch (e) {
1117
+ // Ignore parse errors
1118
+ }
1119
+ }
1120
+
1121
+ // VS Code Extension detection
1122
+ const isVscodeExtension =
1123
+ packageJson.engines?.vscode ||
1124
+ packageJson.contributes ||
1125
+ packageJson.activationEvents ||
1126
+ packageJson.main?.includes('extension.js');
1127
+
1128
+ // Monorepo detection
1129
+ const isMonorepo = hasPnpm || hasYarn || files.includes('packages') || files.includes('apps');
1130
+
1131
+ // Library detection
1132
+ const isLibrary = packageJson.main || packageJson.module || packageJson.exports;
1133
+
1134
+ // CLI detection
1135
+ const isCli = packageJson.bin || packageJson.name?.startsWith('@') === false;
1136
+
1137
+ // API detection
1138
+ const isApi =
1139
+ packageJson.scripts?.start ||
1140
+ packageJson.dependencies?.express ||
1141
+ packageJson.dependencies?.fastify ||
1142
+ packageJson.dependencies?.['@types/express'];
1143
+
1144
+ // Determine primary type
1145
+ if (isVscodeExtension) return 'extension';
1146
+ if (isMonorepo) return 'monorepo';
1147
+ if (isApi) return 'api';
1148
+ if (isLibrary) return 'library';
1149
+ if (isCli) return 'cli';
1150
+
1151
+ // Default fallback
1152
+ return 'application';
1153
+ }
1154
+
1155
+ /**
1156
+ * Generate working spec from project analysis
1157
+ */
1158
+ function generateWorkingSpecFromAnalysis(analysis) {
1159
+ const { projectType, packageJson } = analysis;
1160
+
1161
+ const templates = {
1162
+ extension: {
1163
+ risk_tier: 2,
1164
+ mode: 'feature',
1165
+ change_budget: { max_files: 25, max_loc: 1000 },
1166
+ invariants: [
1167
+ 'Webview only accesses workspace files via VS Code API',
1168
+ 'Extension activates in <1s on typical machine',
1169
+ 'All commands have keyboard shortcuts',
1170
+ ],
1171
+ scope: {
1172
+ in: ['src/', 'package.json', 'tsconfig.json'],
1173
+ out: ['node_modules/', '*.vsix'],
1174
+ },
1175
+ acceptance: [
1176
+ {
1177
+ id: 'A1',
1178
+ given: 'User has workspace open',
1179
+ when: 'Extension activates',
1180
+ then: 'Webview loads within 1 second',
1181
+ },
1182
+ ],
1183
+ non_functional: {
1184
+ a11y: ['keyboard navigation', 'screen reader support', 'high contrast theme'],
1185
+ perf: { api_p95_ms: 100 },
1186
+ security: ['CSP enforcement for webviews', 'No arbitrary filesystem access'],
1187
+ },
1188
+ },
1189
+ library: {
1190
+ risk_tier: 2,
1191
+ mode: 'feature',
1192
+ change_budget: { max_files: 20, max_loc: 800 },
1193
+ invariants: [
1194
+ 'No runtime dependencies except React',
1195
+ 'Tree-shakeable exports',
1196
+ 'TypeScript types exported',
1197
+ ],
1198
+ scope: {
1199
+ in: ['src/', 'lib/', 'package.json'],
1200
+ out: ['examples/', 'docs/', 'node_modules/'],
1201
+ },
1202
+ acceptance: [
1203
+ {
1204
+ id: 'A1',
1205
+ given: 'Library is imported',
1206
+ when: 'Component is rendered',
1207
+ then: 'No runtime errors occur',
1208
+ },
1209
+ ],
1210
+ non_functional: {
1211
+ a11y: ['WCAG 2.1 AA compliance', 'Semantic HTML'],
1212
+ perf: { bundle_size_kb: 50 },
1213
+ security: ['Input validation', 'XSS prevention'],
1214
+ },
1215
+ },
1216
+ api: {
1217
+ risk_tier: 1,
1218
+ mode: 'feature',
1219
+ change_budget: { max_files: 40, max_loc: 1500 },
1220
+ invariants: [
1221
+ 'API maintains backward compatibility',
1222
+ 'All endpoints respond within 100ms',
1223
+ 'Data consistency maintained across requests',
1224
+ ],
1225
+ scope: {
1226
+ in: ['src/', 'routes/', 'models/', 'tests/'],
1227
+ out: ['node_modules/', 'logs/', 'temp/'],
1228
+ },
1229
+ acceptance: [
1230
+ {
1231
+ id: 'A1',
1232
+ given: 'Valid request is made',
1233
+ when: 'Endpoint is called',
1234
+ then: 'Correct response returned within SLO',
1235
+ },
1236
+ ],
1237
+ non_functional: {
1238
+ a11y: ['API documentation accessible'],
1239
+ perf: { api_p95_ms: 100 },
1240
+ security: ['Input validation', 'Rate limiting', 'Authentication'],
1241
+ },
1242
+ },
1243
+ cli: {
1244
+ risk_tier: 3,
1245
+ mode: 'feature',
1246
+ change_budget: { max_files: 15, max_loc: 600 },
1247
+ invariants: [
1248
+ 'CLI exits with appropriate codes',
1249
+ 'Help text is informative',
1250
+ 'Error messages are clear',
1251
+ ],
1252
+ scope: {
1253
+ in: ['src/', 'bin/', 'lib/', 'tests/'],
1254
+ out: ['node_modules/', 'dist/'],
1255
+ },
1256
+ acceptance: [
1257
+ {
1258
+ id: 'A1',
1259
+ given: 'User runs command with --help',
1260
+ when: 'Help flag is provided',
1261
+ then: 'Help text displays clearly',
1262
+ },
1263
+ ],
1264
+ non_functional: {
1265
+ a11y: ['Color contrast in terminal output'],
1266
+ perf: { api_p95_ms: 50 },
1267
+ security: ['Input validation', 'No arbitrary execution'],
1268
+ },
1269
+ },
1270
+ monorepo: {
1271
+ risk_tier: 1,
1272
+ mode: 'feature',
1273
+ change_budget: { max_files: 50, max_loc: 2000 },
1274
+ invariants: [
1275
+ 'All packages remain compatible',
1276
+ 'Cross-package dependencies work',
1277
+ 'Build system remains stable',
1278
+ ],
1279
+ scope: {
1280
+ in: ['packages/', 'apps/', 'tools/', 'scripts/'],
1281
+ out: ['node_modules/', 'dist/', 'build/'],
1282
+ },
1283
+ acceptance: [
1284
+ {
1285
+ id: 'A1',
1286
+ given: 'Change is made to shared package',
1287
+ when: 'All dependent packages build',
1288
+ then: 'No breaking changes introduced',
1289
+ },
1290
+ ],
1291
+ non_functional: {
1292
+ a11y: ['Documentation accessible across packages'],
1293
+ perf: { api_p95_ms: 200 },
1294
+ security: ['Dependency audit passes', 'No vulnerable packages'],
1295
+ },
1296
+ },
1297
+ application: {
1298
+ risk_tier: 2,
1299
+ mode: 'feature',
1300
+ change_budget: { max_files: 30, max_loc: 1200 },
1301
+ invariants: [
1302
+ 'Application remains functional',
1303
+ 'User data is preserved',
1304
+ 'Performance does not degrade',
1305
+ ],
1306
+ scope: {
1307
+ in: ['src/', 'components/', 'pages/', 'lib/'],
1308
+ out: ['node_modules/', 'build/', 'dist/'],
1309
+ },
1310
+ acceptance: [
1311
+ {
1312
+ id: 'A1',
1313
+ given: 'User interacts with application',
1314
+ when: 'Feature is used',
1315
+ then: 'Expected behavior occurs',
1316
+ },
1317
+ ],
1318
+ non_functional: {
1319
+ a11y: ['WCAG 2.1 AA compliance', 'Keyboard navigation'],
1320
+ perf: { api_p95_ms: 250 },
1321
+ security: ['Input validation', 'Authentication', 'Authorization'],
1322
+ },
1323
+ },
1324
+ };
1325
+
1326
+ const baseSpec = templates[projectType] || templates.application;
1327
+
1328
+ return {
1329
+ id: `${packageJson.name?.toUpperCase().replace(/[^A-Z0-9]/g, '-') || 'PROJECT'}-001`,
1330
+ title: packageJson.name || 'Project',
1331
+ risk_tier: baseSpec.risk_tier,
1332
+ mode: baseSpec.mode,
1333
+ change_budget: baseSpec.change_budget,
1334
+ blast_radius: {
1335
+ modules: ['core', 'api', 'ui'],
1336
+ data_migration: false,
1337
+ },
1338
+ operational_rollback_slo: '5m',
1339
+ scope: baseSpec.scope,
1340
+ invariants: baseSpec.invariants,
1341
+ acceptance: baseSpec.acceptance,
1342
+ non_functional: baseSpec.non_functional,
1343
+ contracts: [
1344
+ {
1345
+ type: projectType === 'api' ? 'openapi' : 'none',
1346
+ path: projectType === 'api' ? 'docs/api.yaml' : '',
1347
+ },
1348
+ ],
1349
+ observability: {
1350
+ logs: ['error', 'warn', 'info'],
1351
+ metrics: ['requests_total', 'errors_total'],
1352
+ traces: ['request_flow'],
1353
+ },
1354
+ ai_assessment: {
1355
+ confidence_level: 8,
1356
+ uncertainty_areas: [],
1357
+ complexity_factors: [],
1358
+ risk_factors: [],
1359
+ },
1360
+ };
1361
+ }
1362
+
1363
+ /**
1364
+ * Detect if current directory appears to be a project that should be initialized directly
1365
+ */
1366
+ function shouldInitInCurrentDirectory(projectName, currentDir) {
1367
+ // If explicitly '.', always init in current directory
1368
+ if (projectName === '.') return true;
1369
+
1370
+ // Check for common project indicators
1371
+ const projectIndicators = [
1372
+ 'package.json',
1373
+ 'tsconfig.json',
1374
+ 'jest.config.js',
1375
+ 'eslint.config.js',
1376
+ 'README.md',
1377
+ 'src/',
1378
+ 'lib/',
1379
+ 'app/',
1380
+ 'packages/',
1381
+ '.git/',
1382
+ 'node_modules/', // Even if empty, suggests intent to be a project
1383
+ ];
1384
+
1385
+ const files = fs.readdirSync(currentDir);
1386
+ const hasProjectIndicators = projectIndicators.some((indicator) => {
1387
+ if (indicator.endsWith('/')) {
1388
+ return files.includes(indicator.slice(0, -1));
1389
+ }
1390
+ return files.includes(indicator);
1391
+ });
1392
+
1393
+ return hasProjectIndicators;
1394
+ }
1395
+
573
1396
  /**
574
1397
  * Initialize a new project with CAWS
575
1398
  */
576
1399
  async function initProject(projectName, options) {
577
- console.log(chalk.cyan(`🚀 Initializing new CAWS project: ${projectName}`));
1400
+ const currentDir = process.cwd();
1401
+ const isCurrentDirInit = shouldInitInCurrentDirectory(projectName, currentDir);
1402
+
1403
+ if (!isCurrentDirInit && projectName !== '.') {
1404
+ console.log(chalk.cyan(`🚀 Initializing new CAWS project: ${projectName}`));
1405
+ console.log(chalk.gray(` (Creating subdirectory: ${projectName}/)`));
1406
+ } else {
1407
+ console.log(
1408
+ chalk.cyan(`🚀 Initializing CAWS in current project: ${path.basename(currentDir)}`)
1409
+ );
1410
+ console.log(chalk.gray(` (Adding CAWS files to existing project)`));
1411
+ }
578
1412
 
579
1413
  let answers; // Will be set either interactively or with defaults
580
1414
 
@@ -622,11 +1456,33 @@ async function initProject(projectName, options) {
622
1456
  const initInCurrentDir = projectName === '.';
623
1457
  const targetDir = initInCurrentDir ? process.cwd() : path.resolve(process.cwd(), projectName);
624
1458
 
625
- // Check if directory already exists (skip check for current directory)
1459
+ // Check if target directory already exists and has content (skip check for current directory)
626
1460
  if (!initInCurrentDir && fs.existsSync(projectName)) {
627
- console.error(chalk.red(`❌ Directory ${projectName} already exists`));
628
- console.error(chalk.blue('💡 Choose a different name or remove the existing directory'));
629
- process.exit(1);
1461
+ const existingFiles = fs.readdirSync(projectName);
1462
+ if (existingFiles.length > 0) {
1463
+ console.error(chalk.red(`❌ Directory '${projectName}' already exists and contains files`));
1464
+ console.error(chalk.blue('💡 To initialize CAWS in current directory instead:'));
1465
+ console.error(` ${chalk.cyan('caws init .')}`);
1466
+ console.error(chalk.blue('💡 Or choose a different name/remove existing directory'));
1467
+ process.exit(1);
1468
+ }
1469
+ }
1470
+
1471
+ // Check if current directory has project files when trying to init in subdirectory
1472
+ if (!initInCurrentDir) {
1473
+ const currentDirFiles = fs.readdirSync(process.cwd());
1474
+ const hasProjectFiles = currentDirFiles.some(
1475
+ (file) => !file.startsWith('.') && file !== 'node_modules' && file !== '.git'
1476
+ );
1477
+
1478
+ if (hasProjectFiles) {
1479
+ console.warn(chalk.yellow('⚠️ Current directory contains project files'));
1480
+ console.warn(
1481
+ chalk.blue('💡 You might want to initialize CAWS in current directory instead:')
1482
+ );
1483
+ console.warn(` ${chalk.cyan('caws init .')}`);
1484
+ console.warn(chalk.blue(' Or continue to create subdirectory (Ctrl+C to cancel)'));
1485
+ }
630
1486
  }
631
1487
 
632
1488
  // Save the original template directory before changing directories
@@ -700,6 +1556,9 @@ async function initProject(projectName, options) {
700
1556
  }
701
1557
  } catch (templateError) {
702
1558
  console.warn(chalk.yellow('⚠️ Could not copy agents guide:'), templateError.message);
1559
+ console.warn(
1560
+ chalk.blue('💡 You can manually copy the guide from the caws-template package')
1561
+ );
703
1562
  }
704
1563
  }
705
1564
  } else {
@@ -707,6 +1566,356 @@ async function initProject(projectName, options) {
707
1566
  console.log(chalk.green('✅ CAWS project detected - skipping template copy'));
708
1567
  }
709
1568
 
1569
+ // Handle interactive wizard or template-based setup
1570
+ if (options.interactive && !options.nonInteractive) {
1571
+ console.log(chalk.cyan('🎯 CAWS Interactive Setup Wizard'));
1572
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1573
+ console.log(chalk.gray('This wizard will guide you through creating a CAWS working spec\n'));
1574
+
1575
+ // Detect project type
1576
+ const detectedType = detectProjectType(process.cwd());
1577
+ console.log(chalk.blue(`📦 Detected project type: ${chalk.cyan(detectedType)}`));
1578
+
1579
+ // Get package.json info if available
1580
+ let packageJson = {};
1581
+ try {
1582
+ packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
1583
+ } catch (e) {
1584
+ // No package.json, that's fine
1585
+ }
1586
+
1587
+ const wizardQuestions = [
1588
+ {
1589
+ type: 'list',
1590
+ name: 'projectType',
1591
+ message: '❓ What type of project is this?',
1592
+ choices: [
1593
+ {
1594
+ name: '🔌 VS Code Extension (webview, commands, integrations)',
1595
+ value: 'extension',
1596
+ short: 'VS Code Extension',
1597
+ },
1598
+ {
1599
+ name: '📚 Library/Package (reusable components, utilities)',
1600
+ value: 'library',
1601
+ short: 'Library',
1602
+ },
1603
+ {
1604
+ name: '🌐 API Service (REST, GraphQL, microservices)',
1605
+ value: 'api',
1606
+ short: 'API Service',
1607
+ },
1608
+ {
1609
+ name: '💻 CLI Tool (command-line interface)',
1610
+ value: 'cli',
1611
+ short: 'CLI Tool',
1612
+ },
1613
+ {
1614
+ name: '🏗️ Monorepo (multiple packages/apps)',
1615
+ value: 'monorepo',
1616
+ short: 'Monorepo',
1617
+ },
1618
+ {
1619
+ name: '📱 Application (standalone app)',
1620
+ value: 'application',
1621
+ short: 'Application',
1622
+ },
1623
+ ],
1624
+ default: detectedType,
1625
+ },
1626
+ {
1627
+ type: 'input',
1628
+ name: 'projectTitle',
1629
+ message: '📝 Project Title (descriptive name):',
1630
+ default:
1631
+ packageJson.name ||
1632
+ projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
1633
+ },
1634
+ {
1635
+ type: 'list',
1636
+ name: 'riskTier',
1637
+ message: '⚠️ Risk Tier (higher tier = more rigor):',
1638
+ choices: [
1639
+ {
1640
+ name: '🔴 Tier 1 - Critical (auth, billing, migrations) - Max rigor',
1641
+ value: 1,
1642
+ short: 'Critical',
1643
+ },
1644
+ {
1645
+ name: '🟡 Tier 2 - Standard (features, APIs) - Standard rigor',
1646
+ value: 2,
1647
+ short: 'Standard',
1648
+ },
1649
+ {
1650
+ name: '🟢 Tier 3 - Low Risk (UI, tooling) - Basic rigor',
1651
+ value: 3,
1652
+ short: 'Low Risk',
1653
+ },
1654
+ ],
1655
+ default: (answers) => {
1656
+ const typeDefaults = {
1657
+ extension: 2,
1658
+ library: 2,
1659
+ api: 1,
1660
+ cli: 3,
1661
+ monorepo: 1,
1662
+ application: 2,
1663
+ };
1664
+ return typeDefaults[answers.projectType] || 2;
1665
+ },
1666
+ },
1667
+ {
1668
+ type: 'list',
1669
+ name: 'projectMode',
1670
+ message: '🎯 Primary development mode:',
1671
+ choices: [
1672
+ { name: '✨ feature (new functionality)', value: 'feature' },
1673
+ { name: '🔄 refactor (code restructuring)', value: 'refactor' },
1674
+ { name: '🐛 fix (bug fixes)', value: 'fix' },
1675
+ { name: '📚 doc (documentation)', value: 'doc' },
1676
+ { name: '🧹 chore (maintenance)', value: 'chore' },
1677
+ ],
1678
+ default: 'feature',
1679
+ },
1680
+ {
1681
+ type: 'number',
1682
+ name: 'maxFiles',
1683
+ message: '📊 Max files to change per feature:',
1684
+ default: (answers) => {
1685
+ const tierDefaults = { 1: 40, 2: 25, 3: 15 };
1686
+ const typeAdjustments = {
1687
+ extension: -5,
1688
+ library: -10,
1689
+ api: 10,
1690
+ cli: -10,
1691
+ monorepo: 25,
1692
+ application: 0,
1693
+ };
1694
+ return Math.max(
1695
+ 5,
1696
+ tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
1697
+ );
1698
+ },
1699
+ },
1700
+ {
1701
+ type: 'number',
1702
+ name: 'maxLoc',
1703
+ message: '📏 Max lines of code to change per feature:',
1704
+ default: (answers) => {
1705
+ const tierDefaults = { 1: 1500, 2: 1000, 3: 600 };
1706
+ const typeAdjustments = {
1707
+ extension: -200,
1708
+ library: -300,
1709
+ api: 500,
1710
+ cli: -400,
1711
+ monorepo: 1000,
1712
+ application: 0,
1713
+ };
1714
+ return Math.max(
1715
+ 50,
1716
+ tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
1717
+ );
1718
+ },
1719
+ },
1720
+ {
1721
+ type: 'input',
1722
+ name: 'blastModules',
1723
+ message: '💥 Affected modules (comma-separated):',
1724
+ default: (answers) => {
1725
+ const typeDefaults = {
1726
+ extension: 'core,webview',
1727
+ library: 'components,utils',
1728
+ api: 'routes,models,controllers',
1729
+ cli: 'commands,utils',
1730
+ monorepo: 'shared,packages',
1731
+ application: 'ui,logic,data',
1732
+ };
1733
+ return typeDefaults[answers.projectType] || 'core,ui';
1734
+ },
1735
+ },
1736
+ {
1737
+ type: 'confirm',
1738
+ name: 'dataMigration',
1739
+ message: '🗄️ Requires data migration?',
1740
+ default: false,
1741
+ },
1742
+ {
1743
+ type: 'list',
1744
+ name: 'rollbackSlo',
1745
+ message: '⏱️ Operational rollback SLO:',
1746
+ choices: [
1747
+ { name: '⚡ 1 minute (critical systems)', value: '1m' },
1748
+ { name: '🟡 5 minutes (standard)', value: '5m' },
1749
+ { name: '🟠 15 minutes (complex)', value: '15m' },
1750
+ { name: '🔴 1 hour (data migration)', value: '1h' },
1751
+ ],
1752
+ default: '5m',
1753
+ },
1754
+ ];
1755
+
1756
+ console.log(chalk.cyan('⏳ Gathering project requirements...'));
1757
+
1758
+ let wizardAnswers;
1759
+ try {
1760
+ wizardAnswers = await inquirer.prompt(wizardQuestions);
1761
+ } catch (error) {
1762
+ if (error.isTtyError) {
1763
+ console.error(chalk.red('❌ Interactive prompts not supported in this environment'));
1764
+ console.error(chalk.blue('💡 Run with --non-interactive flag to use defaults'));
1765
+ process.exit(1);
1766
+ } else {
1767
+ console.error(chalk.red('❌ Error during interactive setup:'), error.message);
1768
+ process.exit(1);
1769
+ }
1770
+ }
1771
+
1772
+ console.log(chalk.green('✅ Project requirements gathered successfully!'));
1773
+
1774
+ // Show summary before generating spec
1775
+ console.log(chalk.bold('\n📋 Configuration Summary:'));
1776
+ console.log(` ${chalk.cyan('Type')}: ${wizardAnswers.projectType}`);
1777
+ console.log(` ${chalk.cyan('Project')}: ${wizardAnswers.projectTitle}`);
1778
+ console.log(
1779
+ ` ${chalk.cyan('Mode')}: ${wizardAnswers.projectMode} | ${chalk.cyan('Tier')}: ${wizardAnswers.riskTier}`
1780
+ );
1781
+ console.log(
1782
+ ` ${chalk.cyan('Budget')}: ${wizardAnswers.maxFiles} files, ${wizardAnswers.maxLoc} lines`
1783
+ );
1784
+ console.log(
1785
+ ` ${chalk.cyan('Data Migration')}: ${wizardAnswers.dataMigration ? 'Yes' : 'No'}`
1786
+ );
1787
+ console.log(` ${chalk.cyan('Rollback SLO')}: ${wizardAnswers.rollbackSlo}`);
1788
+
1789
+ // Generate working spec using the template system
1790
+ const analysis = {
1791
+ projectType: wizardAnswers.projectType,
1792
+ packageJson: { name: wizardAnswers.projectTitle },
1793
+ hasTests: false,
1794
+ hasLinting: false,
1795
+ hasCi: false,
1796
+ };
1797
+
1798
+ const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
1799
+
1800
+ // Override template-generated values with wizard answers
1801
+ const spec = yaml.load(workingSpecContent);
1802
+ spec.title = wizardAnswers.projectTitle;
1803
+ spec.risk_tier = wizardAnswers.riskTier;
1804
+ spec.mode = wizardAnswers.projectMode;
1805
+ spec.change_budget = {
1806
+ max_files: wizardAnswers.maxFiles,
1807
+ max_loc: wizardAnswers.maxLoc,
1808
+ };
1809
+ spec.blast_radius = {
1810
+ modules: wizardAnswers.blastModules
1811
+ .split(',')
1812
+ .map((m) => m.trim())
1813
+ .filter((m) => m),
1814
+ data_migration: wizardAnswers.dataMigration,
1815
+ };
1816
+ spec.operational_rollback_slo = wizardAnswers.rollbackSlo;
1817
+
1818
+ // Validate the generated spec
1819
+ validateGeneratedSpec(yaml.dump(spec), wizardAnswers);
1820
+
1821
+ // Save the working spec
1822
+ await fs.writeFile('.caws/working-spec.yaml', yaml.dump(spec, { indent: 2 }));
1823
+
1824
+ console.log(chalk.green('✅ Working spec generated and validated'));
1825
+
1826
+ // Generate getting started guide
1827
+ const wizardAnalysis = {
1828
+ projectType: wizardAnswers.projectType,
1829
+ packageJson: { name: wizardAnswers.projectTitle },
1830
+ hasTests: false,
1831
+ hasLinting: false,
1832
+ hasCi: false,
1833
+ };
1834
+
1835
+ const guideContent = generateGettingStartedGuide(wizardAnalysis);
1836
+ await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
1837
+ console.log(chalk.green('✅ Getting started guide created'));
1838
+
1839
+ // Generate or update .gitignore with CAWS patterns
1840
+ const existingGitignore = fs.existsSync('.gitignore')
1841
+ ? fs.readFileSync('.gitignore', 'utf8')
1842
+ : '';
1843
+
1844
+ const updatedGitignore = generateGitignorePatterns(existingGitignore);
1845
+ if (updatedGitignore !== existingGitignore) {
1846
+ await fs.writeFile('.gitignore', updatedGitignore);
1847
+ const action = existingGitignore.trim() ? 'updated' : 'created';
1848
+ console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
1849
+ }
1850
+
1851
+ // Finalize project with provenance and git initialization
1852
+ await finalizeProject(projectName, options, wizardAnswers);
1853
+
1854
+ continueToSuccess();
1855
+ return;
1856
+ }
1857
+
1858
+ // Handle template-based setup
1859
+ if (options.template) {
1860
+ console.log(chalk.cyan(`🎯 Using ${options.template} template`));
1861
+
1862
+ const validTemplates = ['extension', 'library', 'api', 'cli', 'monorepo'];
1863
+ if (!validTemplates.includes(options.template)) {
1864
+ console.error(chalk.red(`❌ Invalid template: ${options.template}`));
1865
+ console.error(chalk.blue(`💡 Valid templates: ${validTemplates.join(', ')}`));
1866
+ process.exit(1);
1867
+ }
1868
+
1869
+ const analysis = {
1870
+ projectType: options.template,
1871
+ packageJson: { name: projectName },
1872
+ hasTests: false,
1873
+ hasLinting: false,
1874
+ hasCi: false,
1875
+ };
1876
+
1877
+ const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
1878
+
1879
+ // Validate the generated spec
1880
+ validateGeneratedSpec(workingSpecContent, { projectType: options.template });
1881
+
1882
+ // Save the working spec
1883
+ await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
1884
+
1885
+ console.log(chalk.green('✅ Working spec generated from template'));
1886
+
1887
+ // Generate getting started guide
1888
+ const templateAnalysis = {
1889
+ projectType: options.template,
1890
+ packageJson: { name: projectName },
1891
+ hasTests: false,
1892
+ hasLinting: false,
1893
+ hasCi: false,
1894
+ };
1895
+
1896
+ const guideContent = generateGettingStartedGuide(templateAnalysis);
1897
+ await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
1898
+ console.log(chalk.green('✅ Getting started guide created'));
1899
+
1900
+ // Generate or update .gitignore with CAWS patterns
1901
+ const existingGitignore = fs.existsSync('.gitignore')
1902
+ ? fs.readFileSync('.gitignore', 'utf8')
1903
+ : '';
1904
+
1905
+ const updatedGitignore = generateGitignorePatterns(existingGitignore);
1906
+ if (updatedGitignore !== existingGitignore) {
1907
+ await fs.writeFile('.gitignore', updatedGitignore);
1908
+ const action = existingGitignore.trim() ? 'updated' : 'created';
1909
+ console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
1910
+ }
1911
+
1912
+ // Finalize project
1913
+ await finalizeProject(projectName, options, { projectType: options.template });
1914
+
1915
+ continueToSuccess();
1916
+ return;
1917
+ }
1918
+
710
1919
  // Set default answers for non-interactive mode
711
1920
  if (!options.interactive || options.nonInteractive) {
712
1921
  // Use directory name for current directory init
@@ -762,6 +1971,32 @@ async function initProject(projectName, options) {
762
1971
 
763
1972
  console.log(chalk.green('✅ Working spec generated and validated'));
764
1973
 
1974
+ // Generate getting started guide (detect project type)
1975
+ const detectedType = detectProjectType(process.cwd());
1976
+ const defaultAnalysis = {
1977
+ projectType: detectedType,
1978
+ packageJson: { name: displayName },
1979
+ hasTests: false,
1980
+ hasLinting: false,
1981
+ hasCi: false,
1982
+ };
1983
+
1984
+ const guideContent = generateGettingStartedGuide(defaultAnalysis);
1985
+ await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
1986
+ console.log(chalk.green('✅ Getting started guide created'));
1987
+
1988
+ // Generate or update .gitignore with CAWS patterns
1989
+ const existingGitignore = fs.existsSync('.gitignore')
1990
+ ? fs.readFileSync('.gitignore', 'utf8')
1991
+ : '';
1992
+
1993
+ const updatedGitignore = generateGitignorePatterns(existingGitignore);
1994
+ if (updatedGitignore !== existingGitignore) {
1995
+ await fs.writeFile('.gitignore', updatedGitignore);
1996
+ const action = existingGitignore.trim() ? 'updated' : 'created';
1997
+ console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
1998
+ }
1999
+
765
2000
  // Finalize project with provenance and git initialization
766
2001
  await finalizeProject(projectName, options, answers);
767
2002
 
@@ -1392,12 +2627,30 @@ async function finalizeProject(projectName, options, answers) {
1392
2627
  }
1393
2628
 
1394
2629
  function continueToSuccess() {
1395
- console.log(chalk.green('\n🎉 Project initialized successfully!'));
1396
- console.log(`📁 ${chalk.cyan('Project location')}: ${path.resolve(process.cwd())}`);
2630
+ const isCurrentDir =
2631
+ process.cwd() ===
2632
+ path.resolve(process.argv[3] === '.' ? process.cwd() : process.argv[3] || 'caws-project');
2633
+
2634
+ console.log(chalk.green('\n🎉 CAWS project initialized successfully!'));
2635
+
2636
+ if (isCurrentDir) {
2637
+ console.log(
2638
+ `📁 ${chalk.cyan('Initialized in current directory')}: ${path.resolve(process.cwd())}`
2639
+ );
2640
+ console.log(chalk.gray(' (CAWS files added to your existing project)'));
2641
+ } else {
2642
+ console.log(`📁 ${chalk.cyan('Project location')}: ${path.resolve(process.cwd())}`);
2643
+ console.log(chalk.gray(' (New subdirectory created with CAWS structure)'));
2644
+ }
2645
+
1397
2646
  console.log(chalk.bold('\nNext steps:'));
1398
2647
  console.log('1. Customize .caws/working-spec.yaml');
1399
- console.log('2. npm install (if using Node.js)');
1400
- console.log('3. Set up your CI/CD pipeline');
2648
+ console.log('2. Review added CAWS tools and documentation');
2649
+ if (!isCurrentDir) {
2650
+ console.log('3. Move CAWS files to your main project if needed');
2651
+ }
2652
+ console.log('4. npm install (if using Node.js)');
2653
+ console.log('5. Set up your CI/CD pipeline');
1401
2654
  console.log(chalk.blue('\nFor help: caws --help'));
1402
2655
  }
1403
2656
 
@@ -1408,12 +2661,23 @@ async function scaffoldProject(options) {
1408
2661
  const currentDir = process.cwd();
1409
2662
  const projectName = path.basename(currentDir);
1410
2663
 
1411
- console.log(chalk.cyan(`🔧 Enhancing existing project with CAWS: ${projectName}`));
1412
-
1413
2664
  try {
1414
- // Detect existing CAWS setup with current directory context
2665
+ // Detect existing CAWS setup FIRST before any logging
1415
2666
  const setup = detectCAWSSetup(currentDir);
1416
2667
 
2668
+ // Check for CAWS setup immediately and exit with helpful message if not found
2669
+ if (!setup.hasCAWSDir) {
2670
+ console.log(chalk.red('❌ CAWS not initialized in this project'));
2671
+ console.log(chalk.blue('\n💡 To get started:'));
2672
+ console.log(` 1. Initialize CAWS: ${chalk.cyan('caws init <project-name>')}`);
2673
+ console.log(` 2. Or initialize in current directory: ${chalk.cyan('caws init .')}`);
2674
+ console.log(chalk.blue('\n📚 For more help:'));
2675
+ console.log(` ${chalk.cyan('caws --help')}`);
2676
+ process.exit(1);
2677
+ }
2678
+
2679
+ console.log(chalk.cyan(`🔧 Enhancing existing CAWS project: ${projectName}`));
2680
+
1417
2681
  // Preserve the original template directory from global cawsSetup
1418
2682
  // (needed because detectCAWSSetup from within a new project won't find the template)
1419
2683
  if (cawsSetup?.templateDir && !setup.templateDir) {
@@ -1440,6 +2704,12 @@ async function scaffoldProject(options) {
1440
2704
 
1441
2705
  if (!setup.templateDir) {
1442
2706
  console.log(chalk.red(`❌ No template directory available!`));
2707
+ console.log(chalk.blue('💡 To fix this issue:'));
2708
+ console.log(` 1. Ensure caws-template package is installed`);
2709
+ console.log(` 2. Run from the monorepo root directory`);
2710
+ console.log(` 3. Check that CAWS CLI was installed correctly`);
2711
+ console.log(chalk.blue('\n📚 For installation help:'));
2712
+ console.log(` ${chalk.cyan('npm install -g @paths.design/caws-cli')}`);
1443
2713
  }
1444
2714
  }
1445
2715
 
@@ -1486,7 +2756,7 @@ async function scaffoldProject(options) {
1486
2756
  .update(JSON.stringify(scaffoldProvenance))
1487
2757
  .digest('hex');
1488
2758
 
1489
- // Determine what enhancements to add based on setup type
2759
+ // Determine what enhancements to add based on setup type and options
1490
2760
  const enhancements = [];
1491
2761
 
1492
2762
  // Add CAWS tools directory structure (matches test expectations)
@@ -1496,11 +2766,14 @@ async function scaffoldProject(options) {
1496
2766
  required: true,
1497
2767
  });
1498
2768
 
1499
- enhancements.push({
1500
- name: 'codemod',
1501
- description: 'Codemod transformation scripts',
1502
- required: true,
1503
- });
2769
+ // Add codemods if requested or not minimal
2770
+ if (options.withCodemods || (!options.minimal && !options.withCodemods)) {
2771
+ enhancements.push({
2772
+ name: 'codemod',
2773
+ description: 'Codemod transformation scripts',
2774
+ required: true,
2775
+ });
2776
+ }
1504
2777
 
1505
2778
  // Also add automated publishing for enhanced setups
1506
2779
  if (setup.isEnhanced) {
@@ -1526,8 +2799,11 @@ async function scaffoldProject(options) {
1526
2799
  });
1527
2800
  }
1528
2801
 
1529
- // Add OIDC setup guide for setups that need it
1530
- if (!setup.isEnhanced || !fs.existsSync(path.join(currentDir, 'OIDC_SETUP.md'))) {
2802
+ // Add OIDC setup guide if requested or not minimal
2803
+ if (
2804
+ (options.withOidc || (!options.minimal && !options.withOidc)) &&
2805
+ (!setup.isEnhanced || !fs.existsSync(path.join(currentDir, 'OIDC_SETUP.md')))
2806
+ ) {
1531
2807
  enhancements.push({
1532
2808
  name: 'OIDC_SETUP.md',
1533
2809
  description: 'OIDC trusted publisher setup guide',
@@ -1615,9 +2891,18 @@ async function scaffoldProject(options) {
1615
2891
  if (addedCount > 0) {
1616
2892
  console.log(chalk.bold('\n📝 Next steps:'));
1617
2893
  console.log('1. Review the added files');
1618
- console.log('2. Set up OIDC trusted publisher (see OIDC_SETUP.md)');
1619
- console.log('3. Push to trigger automated publishing');
1620
- console.log('4. Your existing CAWS tools remain unchanged');
2894
+
2895
+ // Check if OIDC was added
2896
+ const oidcAdded = addedFiles.some((file) => file.includes('OIDC_SETUP'));
2897
+ if (oidcAdded) {
2898
+ console.log('2. Set up OIDC trusted publisher (see OIDC_SETUP.md)');
2899
+ console.log('3. Push to trigger automated publishing');
2900
+ console.log('4. Your existing CAWS tools remain unchanged');
2901
+ } else {
2902
+ console.log('2. Customize your working spec in .caws/working-spec.yaml');
2903
+ console.log('3. Run validation: caws validate --suggestions');
2904
+ console.log('4. Your existing CAWS tools remain unchanged');
2905
+ }
1621
2906
  }
1622
2907
 
1623
2908
  if (setup.isEnhanced) {
@@ -1673,10 +2958,11 @@ program
1673
2958
  .alias('i')
1674
2959
  .description('Initialize a new project with CAWS')
1675
2960
  .argument('<project-name>', 'Name of the new project')
1676
- .option('-i, --interactive', 'Run interactive setup', true)
2961
+ .option('-i, --interactive', 'Run interactive setup wizard')
1677
2962
  .option('-g, --git', 'Initialize git repository', true)
1678
2963
  .option('-n, --non-interactive', 'Skip interactive prompts')
1679
2964
  .option('--no-git', "Don't initialize git repository")
2965
+ .option('-t, --template <type>', 'Use project template (extension|library|api|cli|monorepo)')
1680
2966
  .action(initProject);
1681
2967
 
1682
2968
  program
@@ -1684,8 +2970,59 @@ program
1684
2970
  .alias('s')
1685
2971
  .description('Add CAWS components to existing project')
1686
2972
  .option('-f, --force', 'Overwrite existing files')
2973
+ .option('--with-oidc', 'Include OIDC trusted publisher setup')
2974
+ .option('--with-codemods', 'Include codemod transformation scripts')
2975
+ .option('--minimal', 'Only essential components (no OIDC, no codemods)')
1687
2976
  .action(scaffoldProject);
1688
2977
 
2978
+ program
2979
+ .command('validate')
2980
+ .alias('v')
2981
+ .description('Validate CAWS working spec with suggestions')
2982
+ .argument('[spec-file]', 'Path to working spec file', '.caws/working-spec.yaml')
2983
+ .option('-s, --suggestions', 'Show helpful suggestions for issues', true)
2984
+ .option('-f, --auto-fix', 'Automatically fix safe issues', false)
2985
+ .option('-q, --quiet', 'Only show errors, no suggestions', false)
2986
+ .action(async (specFile, options) => {
2987
+ try {
2988
+ // Check if spec file exists
2989
+ if (!fs.existsSync(specFile)) {
2990
+ console.error(chalk.red(`❌ Working spec file not found: ${specFile}`));
2991
+ console.error(chalk.blue('💡 Initialize CAWS first:'));
2992
+ console.error(` ${chalk.cyan('caws init .')}`);
2993
+ process.exit(1);
2994
+ }
2995
+
2996
+ // Load and parse spec
2997
+ const specContent = fs.readFileSync(specFile, 'utf8');
2998
+ const spec = yaml.load(specContent);
2999
+
3000
+ if (!spec) {
3001
+ console.error(chalk.red('❌ Failed to parse working spec YAML'));
3002
+ process.exit(1);
3003
+ }
3004
+
3005
+ // Validate with suggestions
3006
+ const result = validateWorkingSpecWithSuggestions(spec, {
3007
+ autoFix: options.autoFix,
3008
+ suggestions: !options.quiet,
3009
+ });
3010
+
3011
+ // Save auto-fixed spec if changes were made
3012
+ if (options.autoFix && result.errors.length === 0) {
3013
+ const fixedContent = yaml.dump(spec, { indent: 2 });
3014
+ fs.writeFileSync(specFile, fixedContent);
3015
+ console.log(chalk.green(`✅ Saved auto-fixed spec to ${specFile}`));
3016
+ }
3017
+
3018
+ // Exit with appropriate code
3019
+ process.exit(result.valid ? 0 : 1);
3020
+ } catch (error) {
3021
+ console.error(chalk.red('❌ Error during validation:'), error.message);
3022
+ process.exit(1);
3023
+ }
3024
+ });
3025
+
1689
3026
  // Error handling
1690
3027
  program.exitOverride((err) => {
1691
3028
  if (