@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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
1396
|
-
|
|
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.
|
|
1400
|
-
|
|
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
|
|
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
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
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
|
|
1530
|
-
if (
|
|
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
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
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'
|
|
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 (
|