@paths.design/caws-cli 2.0.1 → 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.d.ts.map +1 -1
- package/dist/index.js +1463 -121
- package/package.json +3 -2
- package/templates/agents.md +820 -0
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +331 -0
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +360 -0
- package/templates/apps/tools/caws/README.md +463 -0
- package/templates/apps/tools/caws/TEST_STATUS.md +365 -0
- package/templates/apps/tools/caws/attest.js +357 -0
- package/templates/apps/tools/caws/ci-optimizer.js +642 -0
- package/templates/apps/tools/caws/config.ts +245 -0
- package/templates/apps/tools/caws/cross-functional.js +876 -0
- package/templates/apps/tools/caws/dashboard.js +1112 -0
- package/templates/apps/tools/caws/flake-detector.ts +362 -0
- package/templates/apps/tools/caws/gates.js +198 -0
- package/templates/apps/tools/caws/gates.ts +237 -0
- package/templates/apps/tools/caws/language-adapters.ts +381 -0
- package/templates/apps/tools/caws/language-support.d.ts +367 -0
- package/templates/apps/tools/caws/language-support.d.ts.map +1 -0
- package/templates/apps/tools/caws/language-support.js +585 -0
- package/templates/apps/tools/caws/legacy-assessment.ts +408 -0
- package/templates/apps/tools/caws/legacy-assessor.js +764 -0
- package/templates/apps/tools/caws/mutant-analyzer.js +734 -0
- package/templates/apps/tools/caws/perf-budgets.ts +349 -0
- package/templates/apps/tools/caws/prompt-lint.js.backup +274 -0
- package/templates/apps/tools/caws/property-testing.js +707 -0
- package/templates/apps/tools/caws/provenance.d.ts +14 -0
- package/templates/apps/tools/caws/provenance.d.ts.map +1 -0
- package/templates/apps/tools/caws/provenance.js +132 -0
- package/templates/apps/tools/caws/provenance.js.backup +73 -0
- package/templates/apps/tools/caws/provenance.ts +211 -0
- package/templates/apps/tools/caws/schemas/waivers.schema.json +30 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +115 -0
- package/templates/apps/tools/caws/scope-guard.js +208 -0
- package/templates/apps/tools/caws/security-provenance.ts +483 -0
- package/templates/apps/tools/caws/shared/base-tool.ts +281 -0
- package/templates/apps/tools/caws/shared/config-manager.ts +366 -0
- package/templates/apps/tools/caws/shared/gate-checker.ts +597 -0
- package/templates/apps/tools/caws/shared/types.ts +444 -0
- package/templates/apps/tools/caws/shared/validator.ts +305 -0
- package/templates/apps/tools/caws/shared/waivers-manager.ts +174 -0
- package/templates/apps/tools/caws/spec-test-mapper.ts +391 -0
- package/templates/apps/tools/caws/templates/working-spec.template.yml +60 -0
- package/templates/apps/tools/caws/test-quality.js +578 -0
- package/templates/apps/tools/caws/tools-allow.json +331 -0
- package/templates/apps/tools/caws/validate.js +76 -0
- package/templates/apps/tools/caws/validate.ts +228 -0
- package/templates/apps/tools/caws/waivers.js +344 -0
- package/templates/apps/tools/caws/waivers.yml +19 -0
- package/templates/codemod/README.md +1 -0
- package/templates/codemod/test.js +1 -0
- package/templates/docs/README.md +150 -0
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();
|
|
@@ -124,39 +126,60 @@ function detectCAWSSetup(cwd = process.cwd()) {
|
|
|
124
126
|
// Check for template directory - try multiple possible locations
|
|
125
127
|
let templateDir = null;
|
|
126
128
|
const possibleTemplatePaths = [
|
|
129
|
+
// FIRST: Try bundled templates (for npm-installed CLI)
|
|
130
|
+
{ path: path.resolve(__dirname, '../templates'), source: 'bundled with CLI' },
|
|
131
|
+
{ path: path.resolve(__dirname, 'templates'), source: 'bundled with CLI (fallback)' },
|
|
127
132
|
// Try relative to current working directory (for monorepo setups)
|
|
128
|
-
path.resolve(cwd, '../caws-template'),
|
|
129
|
-
path.resolve(cwd, '../../caws-template'),
|
|
130
|
-
path.resolve(cwd, '../../../caws-template'),
|
|
131
|
-
path.resolve(cwd, 'packages/caws-template'),
|
|
132
|
-
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' },
|
|
133
138
|
// Try relative to CLI location (for installed CLI)
|
|
134
|
-
path.resolve(__dirname, '../caws-template'),
|
|
135
|
-
path.resolve(__dirname, '../../caws-template'),
|
|
136
|
-
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' },
|
|
137
142
|
// Try absolute paths for CI environments
|
|
138
|
-
path.resolve(process.cwd(), 'packages/caws-template'),
|
|
139
|
-
path.resolve(process.cwd(), '../packages/caws-template'),
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
},
|
|
142
153
|
// Try from workspace root
|
|
143
|
-
path.resolve(process.cwd(), 'caws-template'),
|
|
154
|
+
{ path: path.resolve(process.cwd(), 'caws-template'), source: 'workspace caws-template/' },
|
|
144
155
|
// Try various other common locations
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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' },
|
|
148
162
|
];
|
|
149
163
|
|
|
150
|
-
for (const testPath of possibleTemplatePaths) {
|
|
164
|
+
for (const { path: testPath, source } of possibleTemplatePaths) {
|
|
151
165
|
if (fs.existsSync(testPath)) {
|
|
152
166
|
templateDir = testPath;
|
|
153
167
|
if (!isQuietCommand) {
|
|
154
|
-
console.log(`✅ Found
|
|
168
|
+
console.log(`✅ Found CAWS templates in ${source}:`);
|
|
169
|
+
console.log(` ${chalk.gray(testPath)}`);
|
|
155
170
|
}
|
|
156
171
|
break;
|
|
157
172
|
}
|
|
158
173
|
}
|
|
159
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
|
+
|
|
160
183
|
const hasTemplateDir = templateDir !== null;
|
|
161
184
|
|
|
162
185
|
return {
|
|
@@ -183,6 +206,22 @@ let cawsSetup = null;
|
|
|
183
206
|
// Initialize global setup detection
|
|
184
207
|
try {
|
|
185
208
|
cawsSetup = detectCAWSSetup();
|
|
209
|
+
|
|
210
|
+
// If no template dir found in current directory, check CLI installation location
|
|
211
|
+
if (!cawsSetup.hasTemplateDir) {
|
|
212
|
+
const cliTemplatePaths = [
|
|
213
|
+
path.resolve(__dirname, '../templates'),
|
|
214
|
+
path.resolve(__dirname, 'templates'),
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
for (const testPath of cliTemplatePaths) {
|
|
218
|
+
if (fs.existsSync(testPath)) {
|
|
219
|
+
cawsSetup.templateDir = testPath;
|
|
220
|
+
cawsSetup.hasTemplateDir = true;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
186
225
|
} catch (error) {
|
|
187
226
|
console.warn('⚠️ Failed to detect CAWS setup globally:', error.message);
|
|
188
227
|
cawsSetup = {
|
|
@@ -358,76 +397,517 @@ const validateWorkingSpec = (spec) => {
|
|
|
358
397
|
}
|
|
359
398
|
};
|
|
360
399
|
|
|
361
|
-
// Only log schema validation if not running quiet commands
|
|
362
|
-
if (!process.argv.includes('--version') && !process.argv.includes('-V')) {
|
|
363
|
-
console.log(chalk.green('✅ Schema validation initialized successfully'));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
400
|
/**
|
|
367
|
-
*
|
|
368
|
-
* @param {string} templatePath - Source template path
|
|
369
|
-
* @param {string} destPath - Destination path
|
|
370
|
-
* @param {Object} replacements - Template variable replacements
|
|
401
|
+
* Enhanced validation with suggestions and auto-fix
|
|
371
402
|
*/
|
|
372
|
-
|
|
403
|
+
function validateWorkingSpecWithSuggestions(spec, options = {}) {
|
|
404
|
+
const { autoFix = false, suggestions = true } = options;
|
|
405
|
+
|
|
373
406
|
try {
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
}
|
|
382
436
|
}
|
|
383
437
|
|
|
384
|
-
//
|
|
385
|
-
|
|
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
|
+
}
|
|
386
447
|
|
|
387
|
-
//
|
|
388
|
-
|
|
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
|
+
}
|
|
389
459
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
+
}
|
|
393
469
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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',
|
|
402
518
|
});
|
|
403
|
-
await fs.writeFile(filePath, content);
|
|
404
|
-
} catch (fileError) {
|
|
405
|
-
console.warn(
|
|
406
|
-
chalk.yellow(`⚠️ Warning: Could not process template file ${file}:`),
|
|
407
|
-
fileError.message
|
|
408
|
-
);
|
|
409
519
|
}
|
|
410
520
|
}
|
|
411
521
|
}
|
|
412
522
|
|
|
413
|
-
|
|
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 };
|
|
414
565
|
} catch (error) {
|
|
415
|
-
console.error(chalk.red('❌ 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
|
+
}
|
|
416
574
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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;
|
|
427
899
|
}
|
|
428
900
|
|
|
429
|
-
|
|
901
|
+
// Append CAWS patterns to existing .gitignore
|
|
902
|
+
return existingGitignore.trim() + '\n\n' + cawsPatterns.trim() + '\n';
|
|
430
903
|
}
|
|
904
|
+
|
|
905
|
+
return cawsPatterns.trim();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Only log schema validation if not running quiet commands
|
|
909
|
+
if (!process.argv.includes('--version') && !process.argv.includes('-V')) {
|
|
910
|
+
console.log(chalk.green('✅ Schema validation initialized successfully'));
|
|
431
911
|
}
|
|
432
912
|
|
|
433
913
|
/**
|
|
@@ -618,11 +1098,317 @@ function validateGeneratedSpec(specContent, _answers) {
|
|
|
618
1098
|
}
|
|
619
1099
|
}
|
|
620
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
|
+
|
|
621
1396
|
/**
|
|
622
1397
|
* Initialize a new project with CAWS
|
|
623
1398
|
*/
|
|
624
1399
|
async function initProject(projectName, options) {
|
|
625
|
-
|
|
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
|
+
}
|
|
626
1412
|
|
|
627
1413
|
let answers; // Will be set either interactively or with defaults
|
|
628
1414
|
|
|
@@ -634,11 +1420,14 @@ async function initProject(projectName, options) {
|
|
|
634
1420
|
process.exit(1);
|
|
635
1421
|
}
|
|
636
1422
|
|
|
637
|
-
//
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
projectName
|
|
1423
|
+
// Special case: '.' means current directory, don't sanitize
|
|
1424
|
+
if (projectName !== '.') {
|
|
1425
|
+
// Sanitize project name
|
|
1426
|
+
const sanitizedName = projectName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
|
|
1427
|
+
if (sanitizedName !== projectName) {
|
|
1428
|
+
console.warn(chalk.yellow(`⚠️ Project name sanitized to: ${sanitizedName}`));
|
|
1429
|
+
projectName = sanitizedName;
|
|
1430
|
+
}
|
|
642
1431
|
}
|
|
643
1432
|
|
|
644
1433
|
// Validate project name length
|
|
@@ -663,53 +1452,478 @@ async function initProject(projectName, options) {
|
|
|
663
1452
|
process.exit(1);
|
|
664
1453
|
}
|
|
665
1454
|
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1455
|
+
// Determine if initializing in current directory
|
|
1456
|
+
const initInCurrentDir = projectName === '.';
|
|
1457
|
+
const targetDir = initInCurrentDir ? process.cwd() : path.resolve(process.cwd(), projectName);
|
|
1458
|
+
|
|
1459
|
+
// Check if target directory already exists and has content (skip check for current directory)
|
|
1460
|
+
if (!initInCurrentDir && fs.existsSync(projectName)) {
|
|
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
|
+
}
|
|
671
1469
|
}
|
|
672
1470
|
|
|
673
|
-
//
|
|
674
|
-
|
|
675
|
-
|
|
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
|
+
}
|
|
1486
|
+
}
|
|
676
1487
|
|
|
677
|
-
|
|
1488
|
+
// Save the original template directory before changing directories
|
|
1489
|
+
const originalTemplateDir = cawsSetup?.hasTemplateDir ? cawsSetup.templateDir : null;
|
|
1490
|
+
|
|
1491
|
+
// Check for existing agents.md/caws.md in target directory
|
|
1492
|
+
const existingAgentsMd = fs.existsSync(path.join(targetDir, 'agents.md'));
|
|
1493
|
+
const existingCawsMd = fs.existsSync(path.join(targetDir, 'caws.md'));
|
|
1494
|
+
|
|
1495
|
+
// Create project directory and change to it (unless already in current directory)
|
|
1496
|
+
if (!initInCurrentDir) {
|
|
1497
|
+
await fs.ensureDir(projectName);
|
|
1498
|
+
process.chdir(projectName);
|
|
1499
|
+
console.log(chalk.green(`📁 Created project directory: ${projectName}`));
|
|
1500
|
+
} else {
|
|
1501
|
+
console.log(chalk.green(`📁 Initializing in current directory`));
|
|
1502
|
+
}
|
|
678
1503
|
|
|
679
1504
|
// Detect and adapt to existing setup
|
|
680
1505
|
const currentSetup = detectCAWSSetup(process.cwd());
|
|
681
1506
|
|
|
682
1507
|
if (currentSetup.type === 'new') {
|
|
683
|
-
//
|
|
684
|
-
|
|
1508
|
+
// Create minimal CAWS structure
|
|
1509
|
+
await fs.ensureDir('.caws');
|
|
1510
|
+
await fs.ensureDir('.agent');
|
|
1511
|
+
console.log(chalk.blue('ℹ️ Created basic CAWS structure'));
|
|
1512
|
+
|
|
1513
|
+
// Copy agents.md guide if templates are available
|
|
1514
|
+
if (originalTemplateDir) {
|
|
685
1515
|
try {
|
|
686
|
-
|
|
687
|
-
|
|
1516
|
+
const agentsMdSource = path.join(originalTemplateDir, 'agents.md');
|
|
1517
|
+
let targetFile = 'agents.md';
|
|
1518
|
+
|
|
1519
|
+
if (fs.existsSync(agentsMdSource)) {
|
|
1520
|
+
// Use the pre-checked values for conflicts
|
|
1521
|
+
if (existingAgentsMd) {
|
|
1522
|
+
// Conflict: user already has agents.md
|
|
1523
|
+
if (options.interactive && !options.nonInteractive) {
|
|
1524
|
+
// Interactive mode: ask user
|
|
1525
|
+
const overwriteAnswer = await inquirer.prompt([
|
|
1526
|
+
{
|
|
1527
|
+
type: 'confirm',
|
|
1528
|
+
name: 'overwrite',
|
|
1529
|
+
message: '⚠️ agents.md already exists. Overwrite with CAWS guide?',
|
|
1530
|
+
default: false,
|
|
1531
|
+
},
|
|
1532
|
+
]);
|
|
1533
|
+
|
|
1534
|
+
if (overwriteAnswer.overwrite) {
|
|
1535
|
+
targetFile = 'agents.md';
|
|
1536
|
+
} else {
|
|
1537
|
+
targetFile = 'caws.md';
|
|
1538
|
+
}
|
|
1539
|
+
} else {
|
|
1540
|
+
// Non-interactive mode: use caws.md instead
|
|
1541
|
+
targetFile = 'caws.md';
|
|
1542
|
+
console.log(chalk.blue('ℹ️ agents.md exists, using caws.md for CAWS guide'));
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// If caws.md also exists and that's our target, skip
|
|
1547
|
+
if (targetFile === 'caws.md' && existingCawsMd) {
|
|
1548
|
+
console.log(
|
|
1549
|
+
chalk.yellow('⚠️ Both agents.md and caws.md exist, skipping guide copy')
|
|
1550
|
+
);
|
|
1551
|
+
} else {
|
|
1552
|
+
const agentsMdDest = path.join(process.cwd(), targetFile);
|
|
1553
|
+
await fs.copyFile(agentsMdSource, agentsMdDest);
|
|
1554
|
+
console.log(chalk.green(`✅ Added ${targetFile} guide`));
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
688
1557
|
} catch (templateError) {
|
|
1558
|
+
console.warn(chalk.yellow('⚠️ Could not copy agents guide:'), templateError.message);
|
|
689
1559
|
console.warn(
|
|
690
|
-
chalk.
|
|
1560
|
+
chalk.blue('💡 You can manually copy the guide from the caws-template package')
|
|
691
1561
|
);
|
|
692
|
-
// Create minimal CAWS structure
|
|
693
|
-
await fs.ensureDir('.caws');
|
|
694
|
-
await fs.ensureDir('.agent');
|
|
695
|
-
console.log(chalk.blue('ℹ️ Created basic CAWS structure'));
|
|
696
1562
|
}
|
|
697
|
-
} else {
|
|
698
|
-
// Create minimal CAWS structure
|
|
699
|
-
await fs.ensureDir('.caws');
|
|
700
|
-
await fs.ensureDir('.agent');
|
|
701
|
-
console.log(chalk.blue('ℹ️ Created basic CAWS structure'));
|
|
702
1563
|
}
|
|
703
1564
|
} else {
|
|
704
1565
|
// Already has CAWS setup
|
|
705
1566
|
console.log(chalk.green('✅ CAWS project detected - skipping template copy'));
|
|
706
1567
|
}
|
|
707
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
|
+
|
|
708
1919
|
// Set default answers for non-interactive mode
|
|
709
1920
|
if (!options.interactive || options.nonInteractive) {
|
|
1921
|
+
// Use directory name for current directory init
|
|
1922
|
+
const displayName = initInCurrentDir ? path.basename(process.cwd()) : projectName;
|
|
1923
|
+
|
|
710
1924
|
answers = {
|
|
711
|
-
projectId:
|
|
712
|
-
projectTitle:
|
|
1925
|
+
projectId: displayName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
|
|
1926
|
+
projectTitle: displayName.charAt(0).toUpperCase() + displayName.slice(1).replace(/-/g, ' '),
|
|
713
1927
|
riskTier: 2,
|
|
714
1928
|
projectMode: 'feature',
|
|
715
1929
|
maxFiles: 25,
|
|
@@ -757,6 +1971,32 @@ async function initProject(projectName, options) {
|
|
|
757
1971
|
|
|
758
1972
|
console.log(chalk.green('✅ Working spec generated and validated'));
|
|
759
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
|
+
|
|
760
2000
|
// Finalize project with provenance and git initialization
|
|
761
2001
|
await finalizeProject(projectName, options, answers);
|
|
762
2002
|
|
|
@@ -1387,12 +2627,30 @@ async function finalizeProject(projectName, options, answers) {
|
|
|
1387
2627
|
}
|
|
1388
2628
|
|
|
1389
2629
|
function continueToSuccess() {
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
+
|
|
1392
2646
|
console.log(chalk.bold('\nNext steps:'));
|
|
1393
2647
|
console.log('1. Customize .caws/working-spec.yaml');
|
|
1394
|
-
console.log('2.
|
|
1395
|
-
|
|
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');
|
|
1396
2654
|
console.log(chalk.blue('\nFor help: caws --help'));
|
|
1397
2655
|
}
|
|
1398
2656
|
|
|
@@ -1403,12 +2661,23 @@ async function scaffoldProject(options) {
|
|
|
1403
2661
|
const currentDir = process.cwd();
|
|
1404
2662
|
const projectName = path.basename(currentDir);
|
|
1405
2663
|
|
|
1406
|
-
console.log(chalk.cyan(`🔧 Enhancing existing project with CAWS: ${projectName}`));
|
|
1407
|
-
|
|
1408
2664
|
try {
|
|
1409
|
-
// Detect existing CAWS setup
|
|
2665
|
+
// Detect existing CAWS setup FIRST before any logging
|
|
1410
2666
|
const setup = detectCAWSSetup(currentDir);
|
|
1411
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
|
+
|
|
1412
2681
|
// Preserve the original template directory from global cawsSetup
|
|
1413
2682
|
// (needed because detectCAWSSetup from within a new project won't find the template)
|
|
1414
2683
|
if (cawsSetup?.templateDir && !setup.templateDir) {
|
|
@@ -1435,6 +2704,12 @@ async function scaffoldProject(options) {
|
|
|
1435
2704
|
|
|
1436
2705
|
if (!setup.templateDir) {
|
|
1437
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')}`);
|
|
1438
2713
|
}
|
|
1439
2714
|
}
|
|
1440
2715
|
|
|
@@ -1481,7 +2756,7 @@ async function scaffoldProject(options) {
|
|
|
1481
2756
|
.update(JSON.stringify(scaffoldProvenance))
|
|
1482
2757
|
.digest('hex');
|
|
1483
2758
|
|
|
1484
|
-
// Determine what enhancements to add based on setup type
|
|
2759
|
+
// Determine what enhancements to add based on setup type and options
|
|
1485
2760
|
const enhancements = [];
|
|
1486
2761
|
|
|
1487
2762
|
// Add CAWS tools directory structure (matches test expectations)
|
|
@@ -1491,11 +2766,14 @@ async function scaffoldProject(options) {
|
|
|
1491
2766
|
required: true,
|
|
1492
2767
|
});
|
|
1493
2768
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
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
|
+
}
|
|
1499
2777
|
|
|
1500
2778
|
// Also add automated publishing for enhanced setups
|
|
1501
2779
|
if (setup.isEnhanced) {
|
|
@@ -1521,8 +2799,11 @@ async function scaffoldProject(options) {
|
|
|
1521
2799
|
});
|
|
1522
2800
|
}
|
|
1523
2801
|
|
|
1524
|
-
// Add OIDC setup guide
|
|
1525
|
-
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
|
+
) {
|
|
1526
2807
|
enhancements.push({
|
|
1527
2808
|
name: 'OIDC_SETUP.md',
|
|
1528
2809
|
description: 'OIDC trusted publisher setup guide',
|
|
@@ -1610,9 +2891,18 @@ async function scaffoldProject(options) {
|
|
|
1610
2891
|
if (addedCount > 0) {
|
|
1611
2892
|
console.log(chalk.bold('\n📝 Next steps:'));
|
|
1612
2893
|
console.log('1. Review the added files');
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
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
|
+
}
|
|
1616
2906
|
}
|
|
1617
2907
|
|
|
1618
2908
|
if (setup.isEnhanced) {
|
|
@@ -1668,10 +2958,11 @@ program
|
|
|
1668
2958
|
.alias('i')
|
|
1669
2959
|
.description('Initialize a new project with CAWS')
|
|
1670
2960
|
.argument('<project-name>', 'Name of the new project')
|
|
1671
|
-
.option('-i, --interactive', 'Run interactive setup'
|
|
2961
|
+
.option('-i, --interactive', 'Run interactive setup wizard')
|
|
1672
2962
|
.option('-g, --git', 'Initialize git repository', true)
|
|
1673
2963
|
.option('-n, --non-interactive', 'Skip interactive prompts')
|
|
1674
2964
|
.option('--no-git', "Don't initialize git repository")
|
|
2965
|
+
.option('-t, --template <type>', 'Use project template (extension|library|api|cli|monorepo)')
|
|
1675
2966
|
.action(initProject);
|
|
1676
2967
|
|
|
1677
2968
|
program
|
|
@@ -1679,8 +2970,59 @@ program
|
|
|
1679
2970
|
.alias('s')
|
|
1680
2971
|
.description('Add CAWS components to existing project')
|
|
1681
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)')
|
|
1682
2976
|
.action(scaffoldProject);
|
|
1683
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
|
+
|
|
1684
3026
|
// Error handling
|
|
1685
3027
|
program.exitOverride((err) => {
|
|
1686
3028
|
if (
|