@paths.design/caws-cli 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +295 -150
  2. package/dist/budget-derivation.d.ts +35 -0
  3. package/dist/budget-derivation.d.ts.map +1 -0
  4. package/dist/budget-derivation.js +204 -0
  5. package/dist/cicd-optimizer.d.ts +142 -0
  6. package/dist/cicd-optimizer.d.ts.map +1 -0
  7. package/dist/cicd-optimizer.js +504 -0
  8. package/dist/commands/burnup.d.ts +6 -0
  9. package/dist/commands/burnup.d.ts.map +1 -0
  10. package/dist/commands/burnup.js +90 -0
  11. package/dist/commands/init.d.ts +5 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +514 -0
  14. package/dist/commands/provenance.d.ts +32 -0
  15. package/dist/commands/provenance.d.ts.map +1 -0
  16. package/dist/commands/provenance.js +979 -0
  17. package/dist/commands/tool.d.ts +13 -0
  18. package/dist/commands/tool.d.ts.map +1 -0
  19. package/dist/commands/tool.js +138 -0
  20. package/dist/commands/validate.d.ts +7 -0
  21. package/dist/commands/validate.d.ts.map +1 -0
  22. package/dist/commands/validate.js +80 -0
  23. package/dist/config/index.d.ts +29 -0
  24. package/dist/config/index.d.ts.map +1 -0
  25. package/dist/config/index.js +132 -0
  26. package/dist/error-handler.d.ts +50 -0
  27. package/dist/error-handler.d.ts.map +1 -0
  28. package/dist/error-handler.js +253 -0
  29. package/dist/generators/working-spec.d.ts +13 -0
  30. package/dist/generators/working-spec.d.ts.map +1 -0
  31. package/dist/generators/working-spec.js +204 -0
  32. package/dist/index.d.ts +3 -12
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +193 -2983
  35. package/dist/scaffold/cursor-hooks.d.ts +7 -0
  36. package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
  37. package/dist/scaffold/cursor-hooks.js +152 -0
  38. package/dist/scaffold/git-hooks.d.ts +20 -0
  39. package/dist/scaffold/git-hooks.d.ts.map +1 -0
  40. package/dist/scaffold/git-hooks.js +417 -0
  41. package/dist/scaffold/index.d.ts +20 -0
  42. package/dist/scaffold/index.d.ts.map +1 -0
  43. package/dist/scaffold/index.js +486 -0
  44. package/dist/test-analysis.d.ts +182 -0
  45. package/dist/test-analysis.d.ts.map +1 -0
  46. package/dist/test-analysis.js +580 -0
  47. package/dist/tool-interface.d.ts +236 -0
  48. package/dist/tool-interface.d.ts.map +1 -0
  49. package/dist/tool-interface.js +314 -0
  50. package/dist/tool-loader.d.ts +77 -0
  51. package/dist/tool-loader.d.ts.map +1 -0
  52. package/dist/tool-loader.js +298 -0
  53. package/dist/tool-validator.d.ts +72 -0
  54. package/dist/tool-validator.d.ts.map +1 -0
  55. package/dist/tool-validator.js +387 -0
  56. package/dist/utils/detection.d.ts +7 -0
  57. package/dist/utils/detection.d.ts.map +1 -0
  58. package/dist/utils/detection.js +174 -0
  59. package/dist/utils/finalization.d.ts +17 -0
  60. package/dist/utils/finalization.d.ts.map +1 -0
  61. package/dist/utils/finalization.js +229 -0
  62. package/dist/utils/project-analysis.d.ts +14 -0
  63. package/dist/utils/project-analysis.d.ts.map +1 -0
  64. package/dist/utils/project-analysis.js +105 -0
  65. package/dist/validation/spec-validation.d.ts +29 -0
  66. package/dist/validation/spec-validation.d.ts.map +1 -0
  67. package/dist/validation/spec-validation.js +376 -0
  68. package/dist/waivers-manager.d.ts +167 -0
  69. package/dist/waivers-manager.d.ts.map +1 -0
  70. package/dist/waivers-manager.js +549 -0
  71. package/package.json +10 -12
  72. package/templates/.cursor/README.md +311 -0
  73. package/templates/.cursor/hooks/audit.sh +55 -0
  74. package/templates/.cursor/hooks/block-dangerous.sh +77 -0
  75. package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
  76. package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
  77. package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
  78. package/templates/.cursor/hooks/format.sh +38 -0
  79. package/templates/.cursor/hooks/naming-check.sh +64 -0
  80. package/templates/.cursor/hooks/scan-secrets.sh +46 -0
  81. package/templates/.cursor/hooks/scope-guard.sh +52 -0
  82. package/templates/.cursor/hooks/validate-spec.sh +38 -0
  83. package/templates/.cursor/hooks.json +59 -0
  84. package/templates/.github/copilot/instructions.md +311 -0
  85. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
  86. package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
  87. package/templates/.vscode/launch.json +56 -0
  88. package/templates/.vscode/settings.json +93 -0
  89. package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
  90. package/templates/apps/tools/caws/README.md +1 -1
  91. package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
  92. package/templates/codemod/test.js +93 -1
  93. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  94. package/templates/apps/tools/caws/provenance.js.backup +0 -73
package/dist/index.js CHANGED
@@ -7,3018 +7,228 @@
7
7
  */
8
8
 
9
9
  const { Command } = require('commander');
10
+ // eslint-disable-next-line no-unused-vars
10
11
  const fs = require('fs-extra');
12
+ // eslint-disable-next-line no-unused-vars
11
13
  const path = require('path');
12
- const inquirer = require('inquirer').default || require('inquirer');
14
+ // eslint-disable-next-line no-unused-vars
13
15
  const yaml = require('js-yaml');
14
16
  const chalk = require('chalk');
15
17
 
16
- // Import language support (with fallback for when tools aren't available)
17
- let languageSupport = null;
18
- try {
19
- // Try multiple possible locations for language support
20
- const possiblePaths = [
21
- path.join(__dirname, '../../caws-template/apps/tools/caws/language-support.js'),
22
- path.join(__dirname, '../../../caws-template/apps/tools/caws/language-support.js'),
23
- path.join(process.cwd(), 'packages/caws-template/apps/tools/caws/language-support.js'),
24
- path.join(process.cwd(), 'caws-template/apps/tools/caws/language-support.js'),
25
- ];
26
-
27
- for (const testPath of possiblePaths) {
28
- try {
29
- languageSupport = require(testPath);
30
- // Only log if not running version command
31
- if (!process.argv.includes('--version') && !process.argv.includes('-V')) {
32
- console.log(`✅ Loaded language support from: ${testPath}`);
33
- }
34
- break;
35
- } catch (pathError) {
36
- // Continue to next path
37
- }
38
- }
39
- } catch (error) {
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'));
43
- }
44
-
45
- const program = new Command();
46
-
47
- // CAWS Detection and Configuration
48
- function detectCAWSSetup(cwd = process.cwd()) {
49
- // Skip logging for version/help commands
50
- const isQuietCommand =
51
- process.argv.includes('--version') ||
52
- process.argv.includes('-V') ||
53
- process.argv.includes('--help');
54
-
55
- if (!isQuietCommand) {
56
- console.log(chalk.blue('🔍 Detecting CAWS setup...'));
57
- }
58
-
59
- // Check for existing CAWS setup
60
- const cawsDir = path.join(cwd, '.caws');
61
- const hasCAWSDir = fs.existsSync(cawsDir);
62
-
63
- if (!hasCAWSDir) {
64
- if (!isQuietCommand) {
65
- console.log(chalk.gray('ℹ️ No .caws directory found - new project setup'));
66
- }
67
- return {
68
- type: 'new',
69
- hasCAWSDir: false,
70
- cawsDir: null,
71
- capabilities: [],
72
- hasTemplateDir: false,
73
- templateDir: null,
74
- };
75
- }
76
-
77
- // Analyze existing setup
78
- const files = fs.readdirSync(cawsDir);
79
- const hasWorkingSpec = fs.existsSync(path.join(cawsDir, 'working-spec.yaml'));
80
- const hasValidateScript = fs.existsSync(path.join(cawsDir, 'validate.js'));
81
- const hasPolicy = fs.existsSync(path.join(cawsDir, 'policy'));
82
- const hasSchemas = fs.existsSync(path.join(cawsDir, 'schemas'));
83
- const hasTemplates = fs.existsSync(path.join(cawsDir, 'templates'));
84
-
85
- // Check for multiple spec files (enhanced project pattern)
86
- const specFiles = files.filter((f) => f.endsWith('-spec.yaml'));
87
- const hasMultipleSpecs = specFiles.length > 1;
88
-
89
- // Check for tools directory (enhanced setup)
90
- const toolsDir = path.join(cwd, 'apps/tools/caws');
91
- const hasTools = fs.existsSync(toolsDir);
92
-
93
- // Determine setup type
94
- let setupType = 'basic';
95
- let capabilities = [];
96
-
97
- if (hasMultipleSpecs && hasWorkingSpec) {
98
- setupType = 'enhanced';
99
- capabilities.push('multiple-specs', 'working-spec', 'domain-specific');
100
- } else if (hasWorkingSpec) {
101
- setupType = 'standard';
102
- capabilities.push('working-spec');
103
- }
104
-
105
- if (hasValidateScript) {
106
- capabilities.push('validation');
107
- }
108
- if (hasPolicy) {
109
- capabilities.push('policies');
110
- }
111
- if (hasSchemas) {
112
- capabilities.push('schemas');
113
- }
114
- if (hasTemplates) {
115
- capabilities.push('templates');
116
- }
117
- if (hasTools) {
118
- capabilities.push('tools');
119
- }
120
-
121
- if (!isQuietCommand) {
122
- console.log(chalk.green(`✅ Detected ${setupType} CAWS setup`));
123
- console.log(chalk.gray(` Capabilities: ${capabilities.join(', ')}`));
124
- }
125
-
126
- // Check for template directory - try multiple possible locations
127
- let templateDir = null;
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)' },
132
- // Try relative to current working directory (for monorepo setups)
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' },
138
- // Try relative to CLI location (for installed CLI)
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' },
142
- // Try absolute paths for CI environments
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
- },
153
- // Try from workspace root
154
- { path: path.resolve(process.cwd(), 'caws-template'), source: 'workspace caws-template/' },
155
- // Try various other common locations
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' },
162
- ];
163
-
164
- for (const { path: testPath, source } of possibleTemplatePaths) {
165
- if (fs.existsSync(testPath)) {
166
- templateDir = testPath;
167
- if (!isQuietCommand) {
168
- console.log(`✅ Found CAWS templates in ${source}:`);
169
- console.log(` ${chalk.gray(testPath)}`);
170
- }
171
- break;
172
- }
173
- }
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
-
183
- const hasTemplateDir = templateDir !== null;
184
-
185
- return {
186
- type: setupType,
187
- hasCAWSDir: true,
188
- cawsDir,
189
- hasWorkingSpec,
190
- hasMultipleSpecs,
191
- hasValidateScript,
192
- hasPolicy,
193
- hasSchemas,
194
- hasTemplates,
195
- hasTools,
196
- hasTemplateDir,
197
- templateDir,
198
- capabilities,
199
- isEnhanced: setupType === 'enhanced',
200
- isAdvanced: hasTools || hasValidateScript,
201
- };
202
- }
203
-
204
- let cawsSetup = null;
205
-
206
- // Initialize global setup detection
207
- try {
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
- }
225
- } catch (error) {
226
- console.warn('⚠️ Failed to detect CAWS setup globally:', error.message);
227
- cawsSetup = {
228
- type: 'unknown',
229
- hasCAWSDir: false,
230
- cawsDir: null,
231
- hasWorkingSpec: false,
232
- hasMultipleSpecs: false,
233
- hasValidateScript: false,
234
- hasPolicy: false,
235
- hasSchemas: false,
236
- hasTemplates: false,
237
- hasTools: false,
238
- hasTemplateDir: false,
239
- templateDir: null,
240
- capabilities: [],
241
- isEnhanced: false,
242
- isAdvanced: false,
243
- };
244
- }
245
-
246
- // Dynamic imports based on setup
247
- let provenanceTools = null;
248
-
249
- // Function to load provenance tools dynamically
250
- function loadProvenanceTools() {
251
- if (provenanceTools) return provenanceTools; // Already loaded
252
-
253
- try {
254
- const setup = detectCAWSSetup();
255
- if (setup?.hasTemplateDir && setup?.templateDir) {
256
- const { generateProvenance, saveProvenance } = require(
257
- path.join(setup.templateDir, 'apps/tools/caws/provenance.js')
258
- );
259
- provenanceTools = { generateProvenance, saveProvenance };
260
- console.log('✅ Loaded provenance tools from:', setup.templateDir);
261
- }
262
- } catch (error) {
263
- // Fallback for environments without template
264
- provenanceTools = null;
265
- console.warn('⚠️ Provenance tools not available:', error.message);
266
- }
267
-
268
- return provenanceTools;
269
- }
270
-
271
- const CLI_VERSION = require('../package.json').version;
272
-
273
- // Initialize JSON Schema validator - using simplified validation for CLI stability
274
- const validateWorkingSpec = (spec) => {
275
- try {
276
- // Basic structural validation for essential fields
277
- const requiredFields = [
278
- 'id',
279
- 'title',
280
- 'risk_tier',
281
- 'mode',
282
- 'change_budget',
283
- 'blast_radius',
284
- 'operational_rollback_slo',
285
- 'scope',
286
- 'invariants',
287
- 'acceptance',
288
- 'non_functional',
289
- 'contracts',
290
- ];
291
-
292
- for (const field of requiredFields) {
293
- if (!spec[field]) {
294
- return {
295
- valid: false,
296
- errors: [
297
- {
298
- instancePath: `/${field}`,
299
- message: `Missing required field: ${field}`,
300
- },
301
- ],
302
- };
303
- }
304
- }
305
-
306
- // Validate specific field formats
307
- if (!/^[A-Z]+-\d+$/.test(spec.id)) {
308
- return {
309
- valid: false,
310
- errors: [
311
- {
312
- instancePath: '/id',
313
- message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
314
- },
315
- ],
316
- };
317
- }
318
-
319
- // Validate experimental mode
320
- if (spec.experimental_mode) {
321
- if (typeof spec.experimental_mode !== 'object') {
322
- return {
323
- valid: false,
324
- errors: [
325
- {
326
- instancePath: '/experimental_mode',
327
- message:
328
- 'Experimental mode must be an object with enabled, rationale, and expires_at fields',
329
- },
330
- ],
331
- };
332
- }
333
-
334
- const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
335
- for (const field of requiredExpFields) {
336
- if (!(field in spec.experimental_mode)) {
337
- return {
338
- valid: false,
339
- errors: [
340
- {
341
- instancePath: `/experimental_mode/${field}`,
342
- message: `Missing required experimental mode field: ${field}`,
343
- },
344
- ],
345
- };
346
- }
347
- }
348
-
349
- if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
350
- return {
351
- valid: false,
352
- errors: [
353
- {
354
- instancePath: '/experimental_mode',
355
- message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
356
- },
357
- ],
358
- };
359
- }
360
- }
361
-
362
- if (spec.risk_tier < 1 || spec.risk_tier > 3) {
363
- return {
364
- valid: false,
365
- errors: [
366
- {
367
- instancePath: '/risk_tier',
368
- message: 'Risk tier must be 1, 2, or 3',
369
- },
370
- ],
371
- };
372
- }
373
-
374
- if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
375
- return {
376
- valid: false,
377
- errors: [
378
- {
379
- instancePath: '/scope/in',
380
- message: 'Scope IN must not be empty',
381
- },
382
- ],
383
- };
384
- }
385
-
386
- return { valid: true };
387
- } catch (error) {
388
- return {
389
- valid: false,
390
- errors: [
391
- {
392
- instancePath: '',
393
- message: `Validation error: ${error.message}`,
394
- },
395
- ],
396
- };
397
- }
398
- };
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
-
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'));
911
- }
912
-
913
- /**
914
- * Generate working spec YAML with user input
915
- * @param {Object} answers - User responses
916
- * @returns {string} - Generated YAML content
917
- */
918
- function generateWorkingSpec(answers) {
919
- const template = {
920
- id: answers.projectId,
921
- title: answers.projectTitle,
922
- risk_tier: answers.riskTier,
923
- mode: answers.projectMode,
924
- change_budget: {
925
- max_files: answers.maxFiles,
926
- max_loc: answers.maxLoc,
927
- },
928
- blast_radius: {
929
- modules: answers.blastModules
930
- .split(',')
931
- .map((m) => m.trim())
932
- .filter((m) => m),
933
- data_migration: answers.dataMigration,
934
- },
935
- operational_rollback_slo: answers.rollbackSlo,
936
- threats: (answers.projectThreats || '')
937
- .split('\n')
938
- .map((t) => t.trim())
939
- .filter((t) => t && !t.startsWith('-') === false), // Allow lines starting with -
940
- scope: {
941
- in: (answers.scopeIn || '')
942
- .split(',')
943
- .map((s) => s.trim())
944
- .filter((s) => s),
945
- out: (answers.scopeOut || '')
946
- .split(',')
947
- .map((s) => s.trim())
948
- .filter((s) => s),
949
- },
950
- invariants: (answers.projectInvariants || '')
951
- .split('\n')
952
- .map((i) => i.trim())
953
- .filter((i) => i),
954
- acceptance: answers.acceptanceCriteria
955
- .split('\n')
956
- .filter((a) => a.trim())
957
- .map((criteria, index) => {
958
- const id = `A${index + 1}`;
959
- const upperCriteria = criteria.toUpperCase();
960
-
961
- // Try different variations of the format
962
- let given = '';
963
- let when = '';
964
- let then = '';
965
-
966
- if (
967
- upperCriteria.includes('GIVEN') &&
968
- upperCriteria.includes('WHEN') &&
969
- upperCriteria.includes('THEN')
970
- ) {
971
- given = criteria.split(/WHEN/i)[0]?.replace(/GIVEN/i, '').trim() || '';
972
- const whenThen = criteria.split(/WHEN/i)[1];
973
- when = whenThen?.split(/THEN/i)[0]?.trim() || '';
974
- then = whenThen?.split(/THEN/i)[1]?.trim() || '';
975
- } else {
976
- // Fallback: just split by lines and create simple criteria
977
- given = 'Current system state';
978
- when = criteria.replace(/^(GIVEN|WHEN|THEN)/i, '').trim();
979
- then = 'Expected behavior occurs';
980
- }
981
-
982
- return {
983
- id,
984
- given: given || 'Current system state',
985
- when: when || criteria,
986
- then: then || 'Expected behavior occurs',
987
- };
988
- }),
989
- non_functional: {
990
- a11y: answers.a11yRequirements
991
- .split(',')
992
- .map((a) => a.trim())
993
- .filter((a) => a),
994
- perf: { api_p95_ms: answers.perfBudget },
995
- security: answers.securityRequirements
996
- .split(',')
997
- .map((s) => s.trim())
998
- .filter((s) => s),
999
- },
1000
- contracts: [
1001
- {
1002
- type: answers.contractType,
1003
- path: answers.contractPath,
1004
- },
1005
- ],
1006
- observability: {
1007
- logs: answers.observabilityLogs
1008
- .split(',')
1009
- .map((l) => l.trim())
1010
- .filter((l) => l),
1011
- metrics: answers.observabilityMetrics
1012
- .split(',')
1013
- .map((m) => m.trim())
1014
- .filter((m) => m),
1015
- traces: answers.observabilityTraces
1016
- .split(',')
1017
- .map((t) => t.trim())
1018
- .filter((t) => t),
1019
- },
1020
- migrations: (answers.migrationPlan || '')
1021
- .split('\n')
1022
- .map((m) => m.trim())
1023
- .filter((m) => m),
1024
- rollback: (answers.rollbackPlan || '')
1025
- .split('\n')
1026
- .map((r) => r.trim())
1027
- .filter((r) => r),
1028
- human_override: answers.needsOverride
1029
- ? {
1030
- enabled: true,
1031
- approver: answers.overrideApprover,
1032
- rationale: answers.overrideRationale,
1033
- waived_gates: answers.waivedGates,
1034
- approved_at: new Date().toISOString(),
1035
- expires_at: new Date(
1036
- Date.now() + answers.overrideExpiresDays * 24 * 60 * 60 * 1000
1037
- ).toISOString(),
1038
- }
1039
- : undefined,
1040
- experimental_mode: answers.isExperimental
1041
- ? {
1042
- enabled: true,
1043
- rationale: answers.experimentalRationale,
1044
- expires_at: new Date(
1045
- Date.now() + answers.experimentalExpiresDays * 24 * 60 * 60 * 1000
1046
- ).toISOString(),
1047
- sandbox_location: answers.experimentalSandbox,
1048
- }
1049
- : undefined,
1050
- ai_assessment: {
1051
- confidence_level: answers.aiConfidence,
1052
- uncertainty_areas: answers.uncertaintyAreas
1053
- .split(',')
1054
- .map((a) => a.trim())
1055
- .filter((a) => a),
1056
- complexity_factors: answers.complexityFactors
1057
- .split(',')
1058
- .map((f) => f.trim())
1059
- .filter((f) => f),
1060
- risk_factors: [], // Could be populated by AI analysis
1061
- },
1062
- };
1063
-
1064
- return yaml.dump(template, { indent: 2 });
1065
- }
1066
-
1067
- /**
1068
- * Validate generated working spec against JSON schema
1069
- * @param {string} specContent - YAML spec content
1070
- * @param {Object} answers - User responses for error context
1071
- */
1072
- function validateGeneratedSpec(specContent, _answers) {
1073
- try {
1074
- const spec = yaml.load(specContent);
1075
-
1076
- const isValid = validateWorkingSpec(spec);
1077
-
1078
- if (!isValid) {
1079
- console.error(chalk.red('❌ Generated working spec failed validation:'));
1080
- validateWorkingSpec.errors.forEach((error) => {
1081
- console.error(` - ${error.instancePath || 'root'}: ${error.message}`);
1082
- });
1083
-
1084
- // Provide helpful guidance
1085
- console.log(chalk.blue('\n💡 Validation Tips:'));
1086
- console.log(' - Ensure risk_tier is 1, 2, or 3');
1087
- console.log(' - Check that scope.in is not empty');
1088
- console.log(' - Verify invariants and acceptance criteria are provided');
1089
- console.log(' - For tier 1 and 2, ensure contracts are specified');
1090
-
1091
- process.exit(1);
1092
- }
1093
-
1094
- console.log(chalk.green('✅ Generated working spec passed validation'));
1095
- } catch (error) {
1096
- console.error(chalk.red('❌ Error validating working spec:'), error.message);
1097
- process.exit(1);
1098
- }
1099
- }
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
-
1396
- /**
1397
- * Initialize a new project with CAWS
1398
- */
1399
- async function initProject(projectName, options) {
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
- }
1412
-
1413
- let answers; // Will be set either interactively or with defaults
1414
-
1415
- try {
1416
- // Validate project name
1417
- if (!projectName || projectName.trim() === '') {
1418
- console.error(chalk.red('❌ Project name is required'));
1419
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1420
- process.exit(1);
1421
- }
1422
-
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
- }
1431
- }
1432
-
1433
- // Validate project name length
1434
- if (projectName.length > 50) {
1435
- console.error(chalk.red('❌ Project name is too long (max 50 characters)'));
1436
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1437
- process.exit(1);
1438
- }
1439
-
1440
- // Validate project name format
1441
- if (projectName.length === 0) {
1442
- console.error(chalk.red('❌ Project name cannot be empty'));
1443
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1444
- process.exit(1);
1445
- }
1446
-
1447
- // Check for invalid characters that should cause immediate failure
1448
- if (projectName.includes('/') || projectName.includes('\\') || projectName.includes('..')) {
1449
- console.error(chalk.red('❌ Project name contains invalid characters'));
1450
- console.error(chalk.blue('💡 Usage: caws init <project-name>'));
1451
- console.error(chalk.blue('💡 Project name should not contain: / \\ ..'));
1452
- process.exit(1);
1453
- }
1454
-
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
- }
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
- }
1486
- }
1487
-
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
- }
1503
-
1504
- // Detect and adapt to existing setup
1505
- const currentSetup = detectCAWSSetup(process.cwd());
1506
-
1507
- if (currentSetup.type === 'new') {
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) {
1515
- try {
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
- }
1557
- } catch (templateError) {
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
- );
1562
- }
1563
- }
1564
- } else {
1565
- // Already has CAWS setup
1566
- console.log(chalk.green('✅ CAWS project detected - skipping template copy'));
1567
- }
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
-
1919
- // Set default answers for non-interactive mode
1920
- if (!options.interactive || options.nonInteractive) {
1921
- // Use directory name for current directory init
1922
- const displayName = initInCurrentDir ? path.basename(process.cwd()) : projectName;
1923
-
1924
- answers = {
1925
- projectId: displayName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
1926
- projectTitle: displayName.charAt(0).toUpperCase() + displayName.slice(1).replace(/-/g, ' '),
1927
- riskTier: 2,
1928
- projectMode: 'feature',
1929
- maxFiles: 25,
1930
- maxLoc: 1000,
1931
- blastModules: 'core,ui',
1932
- dataMigration: false,
1933
- rollbackSlo: '5m',
1934
- projectThreats: 'Standard project threats',
1935
- scopeIn: 'project files',
1936
- scopeOut: 'external dependencies',
1937
- projectInvariants: 'System maintains consistency',
1938
- acceptanceCriteria: 'GIVEN current state WHEN action THEN expected result',
1939
- a11yRequirements: 'keyboard navigation, screen reader support',
1940
- perfBudget: 250,
1941
- securityRequirements: 'input validation, authentication',
1942
- contractType: 'openapi',
1943
- contractPath: 'apps/contracts/api.yaml',
1944
- observabilityLogs: 'auth.success,api.request',
1945
- observabilityMetrics: 'requests_total',
1946
- observabilityTraces: 'api_flow',
1947
- migrationPlan: 'Standard deployment process',
1948
- rollbackPlan: 'Feature flag disable and rollback',
1949
- needsOverride: false,
1950
- overrideRationale: '',
1951
- overrideApprover: '',
1952
- waivedGates: [],
1953
- overrideExpiresDays: 7,
1954
- isExperimental: false,
1955
- experimentalRationale: '',
1956
- experimentalSandbox: 'experimental/',
1957
- experimentalExpiresDays: 14,
1958
- aiConfidence: 7,
1959
- uncertaintyAreas: '',
1960
- complexityFactors: '',
1961
- };
1962
-
1963
- // Generate working spec for non-interactive mode
1964
- const workingSpecContent = generateWorkingSpec(answers);
1965
-
1966
- // Validate the generated spec
1967
- validateGeneratedSpec(workingSpecContent, answers);
1968
-
1969
- // Save the working spec
1970
- await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
1971
-
1972
- console.log(chalk.green('✅ Working spec generated and validated'));
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
-
2000
- // Finalize project with provenance and git initialization
2001
- await finalizeProject(projectName, options, answers);
2002
-
2003
- continueToSuccess();
2004
- return;
2005
- }
2006
-
2007
- if (options.interactive && !options.nonInteractive) {
2008
- // Interactive setup with enhanced prompts
2009
- console.log(chalk.cyan('🔧 Starting interactive project configuration...'));
2010
- console.log(chalk.blue('💡 Press Ctrl+C at any time to exit and use defaults'));
2011
-
2012
- const questions = [
2013
- {
2014
- type: 'input',
2015
- name: 'projectId',
2016
- message: '📋 Project ID (e.g., FEAT-1234, AUTH-456):',
2017
- default: projectName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
2018
- validate: (input) => {
2019
- if (!input.trim()) return 'Project ID is required';
2020
- const pattern = /^[A-Z]+-\d+$/;
2021
- if (!pattern.test(input)) {
2022
- return 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)';
2023
- }
2024
- return true;
2025
- },
2026
- },
2027
- {
2028
- type: 'input',
2029
- name: 'projectTitle',
2030
- message: '📝 Project Title (descriptive name):',
2031
- default: projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
2032
- validate: (input) => {
2033
- if (!input.trim()) return 'Project title is required';
2034
- if (input.trim().length < 8) {
2035
- return 'Project title should be at least 8 characters long';
2036
- }
2037
- return true;
2038
- },
2039
- },
2040
- {
2041
- type: 'list',
2042
- name: 'riskTier',
2043
- message: '⚠️ Risk Tier (higher tier = more rigor):',
2044
- choices: [
2045
- {
2046
- name: '🔴 Tier 1 - Critical (auth, billing, migrations) - Max rigor',
2047
- value: 1,
2048
- },
2049
- {
2050
- name: '🟡 Tier 2 - Standard (features, APIs) - Standard rigor',
2051
- value: 2,
2052
- },
2053
- {
2054
- name: '🟢 Tier 3 - Low Risk (UI, tooling) - Basic rigor',
2055
- value: 3,
2056
- },
2057
- ],
2058
- default: 2,
2059
- },
2060
- {
2061
- type: 'list',
2062
- name: 'projectMode',
2063
- message: '🎯 Project Mode:',
2064
- choices: [
2065
- { name: '✨ feature (new functionality)', value: 'feature' },
2066
- { name: '🔄 refactor (code restructuring)', value: 'refactor' },
2067
- { name: '🐛 fix (bug fixes)', value: 'fix' },
2068
- { name: '📚 doc (documentation)', value: 'doc' },
2069
- { name: '🧹 chore (maintenance)', value: 'chore' },
2070
- ],
2071
- default: 'feature',
2072
- },
2073
- {
2074
- type: 'number',
2075
- name: 'maxFiles',
2076
- message: '📊 Max files to change:',
2077
- default: (answers) => {
2078
- // Dynamic defaults based on risk tier
2079
- switch (answers.riskTier) {
2080
- case 1:
2081
- return 40;
2082
- case 2:
2083
- return 25;
2084
- case 3:
2085
- return 15;
2086
- default:
2087
- return 25;
2088
- }
2089
- },
2090
- validate: (input) => {
2091
- if (input < 1) return 'Must change at least 1 file';
2092
- return true;
2093
- },
2094
- },
2095
- {
2096
- type: 'number',
2097
- name: 'maxLoc',
2098
- message: '📏 Max lines of code to change:',
2099
- default: (answers) => {
2100
- // Dynamic defaults based on risk tier
2101
- switch (answers.riskTier) {
2102
- case 1:
2103
- return 1500;
2104
- case 2:
2105
- return 1000;
2106
- case 3:
2107
- return 600;
2108
- default:
2109
- return 1000;
2110
- }
2111
- },
2112
- validate: (input) => {
2113
- if (input < 1) return 'Must change at least 1 line';
2114
- return true;
2115
- },
2116
- },
2117
- {
2118
- type: 'input',
2119
- name: 'blastModules',
2120
- message: '💥 Blast Radius - Affected modules (comma-separated):',
2121
- default: 'core,api',
2122
- validate: (input) => {
2123
- if (!input.trim()) return 'At least one module must be specified';
2124
- return true;
2125
- },
2126
- },
2127
- {
2128
- type: 'confirm',
2129
- name: 'dataMigration',
2130
- message: '🗄️ Requires data migration?',
2131
- default: false,
2132
- },
2133
- {
2134
- type: 'input',
2135
- name: 'rollbackSlo',
2136
- message: '⏱️ Operational rollback SLO (e.g., 5m, 1h, 24h):',
2137
- default: '5m',
2138
- validate: (input) => {
2139
- const pattern = /^([0-9]+m|[0-9]+h|[0-9]+d)$/;
2140
- if (!pattern.test(input)) {
2141
- return 'SLO should be in format: NUMBER + m/h/d (e.g., 5m, 1h, 24h)';
2142
- }
2143
- return true;
2144
- },
2145
- },
2146
- {
2147
- type: 'editor',
2148
- name: 'projectThreats',
2149
- message: '⚠️ Project Threats & Risks (one per line, ESC to finish):',
2150
- default: (answers) => {
2151
- const baseThreats =
2152
- '- Race condition in concurrent operations\n- Performance degradation under load';
2153
- if (answers.dataMigration) {
2154
- return baseThreats + '\n- Data migration failure\n- Inconsistent data state';
2155
- }
2156
- return baseThreats;
2157
- },
2158
- },
2159
- {
2160
- type: 'input',
2161
- name: 'scopeIn',
2162
- message: "🎯 Scope IN - What's included (comma-separated):",
2163
- default: (answers) => {
2164
- if (answers.projectMode === 'feature') return 'user authentication, api endpoints';
2165
- if (answers.projectMode === 'refactor') return 'authentication module, user service';
2166
- if (answers.projectMode === 'fix') return 'error handling, validation';
2167
- return 'project files';
2168
- },
2169
- validate: (input) => {
2170
- if (!input.trim()) return 'At least one scope item must be specified';
2171
- return true;
2172
- },
2173
- },
2174
- {
2175
- type: 'input',
2176
- name: 'scopeOut',
2177
- message: "🚫 Scope OUT - What's excluded (comma-separated):",
2178
- default: (answers) => {
2179
- if (answers.projectMode === 'feature')
2180
- return 'legacy authentication, deprecated endpoints';
2181
- if (answers.projectMode === 'refactor')
2182
- return 'external dependencies, configuration files';
2183
- return 'unrelated features';
2184
- },
2185
- },
2186
- {
2187
- type: 'editor',
2188
- name: 'projectInvariants',
2189
- message: '🛡️ System Invariants (one per line, ESC to finish):',
2190
- default:
2191
- '- System remains available\n- Data consistency maintained\n- User sessions preserved',
2192
- },
2193
- {
2194
- type: 'editor',
2195
- name: 'acceptanceCriteria',
2196
- message: '✅ Acceptance Criteria (GIVEN...WHEN...THEN, one per line, ESC to finish):',
2197
- default: (answers) => {
2198
- if (answers.projectMode === 'feature') {
2199
- return 'GIVEN user is authenticated WHEN accessing protected endpoint THEN access is granted\nGIVEN invalid credentials WHEN attempting login THEN access is denied';
2200
- }
2201
- if (answers.projectMode === 'fix') {
2202
- return 'GIVEN existing functionality WHEN applying fix THEN behavior is preserved\nGIVEN error condition WHEN fix is applied THEN error is resolved';
2203
- }
2204
- return 'GIVEN current system state WHEN change is applied THEN expected behavior occurs';
2205
- },
2206
- validate: (input) => {
2207
- if (!input.trim()) return 'At least one acceptance criterion is required';
2208
- const lines = input
2209
- .trim()
2210
- .split('\n')
2211
- .filter((line) => line.trim());
2212
- if (lines.length === 0) return 'At least one acceptance criterion is required';
2213
- return true;
2214
- },
2215
- },
2216
- {
2217
- type: 'input',
2218
- name: 'a11yRequirements',
2219
- message: '♿ Accessibility Requirements (comma-separated):',
2220
- default: 'keyboard navigation, screen reader support, color contrast',
2221
- },
2222
- {
2223
- type: 'number',
2224
- name: 'perfBudget',
2225
- message: '⚡ Performance Budget (API p95 latency in ms):',
2226
- default: 250,
2227
- validate: (input) => {
2228
- if (input < 1) return 'Performance budget must be at least 1ms';
2229
- if (input > 10000) return 'Performance budget seems too high (max 10s)';
2230
- return true;
2231
- },
2232
- },
2233
- {
2234
- type: 'input',
2235
- name: 'securityRequirements',
2236
- message: '🔒 Security Requirements (comma-separated):',
2237
- default: 'input validation, rate limiting, authentication, authorization',
2238
- },
2239
- {
2240
- type: 'list',
2241
- name: 'contractType',
2242
- message: '📄 Contract Type:',
2243
- choices: [
2244
- { name: 'OpenAPI (REST APIs)', value: 'openapi' },
2245
- { name: 'GraphQL Schema', value: 'graphql' },
2246
- { name: 'Protocol Buffers', value: 'proto' },
2247
- { name: 'Pact (consumer-driven)', value: 'pact' },
2248
- ],
2249
- default: 'openapi',
2250
- },
2251
- {
2252
- type: 'input',
2253
- name: 'contractPath',
2254
- message: '📁 Contract File Path:',
2255
- default: (answers) => {
2256
- if (answers.contractType === 'openapi') return 'apps/contracts/api.yaml';
2257
- if (answers.contractType === 'graphql') return 'apps/contracts/schema.graphql';
2258
- if (answers.contractType === 'proto') return 'apps/contracts/service.proto';
2259
- if (answers.contractType === 'pact') return 'apps/contracts/pacts/';
2260
- return 'apps/contracts/api.yaml';
2261
- },
2262
- },
2263
- {
2264
- type: 'input',
2265
- name: 'observabilityLogs',
2266
- message: '📝 Observability - Log Events (comma-separated):',
2267
- default: 'auth.success, auth.failure, api.request, api.response',
2268
- },
2269
- {
2270
- type: 'input',
2271
- name: 'observabilityMetrics',
2272
- message: '📊 Observability - Metrics (comma-separated):',
2273
- default: 'auth_attempts_total, auth_success_total, api_requests_total, api_errors_total',
2274
- },
2275
- {
2276
- type: 'input',
2277
- name: 'observabilityTraces',
2278
- message: '🔍 Observability - Traces (comma-separated):',
2279
- default: 'auth_flow, api_request',
2280
- },
2281
- {
2282
- type: 'editor',
2283
- name: 'migrationPlan',
2284
- message: '🔄 Migration Plan (one per line, ESC to finish):',
2285
- default: (answers) => {
2286
- if (answers.dataMigration) {
2287
- return '- Create new database schema\n- Add new auth table\n- Migrate existing users\n- Validate data integrity';
2288
- }
2289
- return '- Deploy feature flags\n- Roll out gradually\n- Monitor metrics';
2290
- },
2291
- validate: (input) => {
2292
- if (!input.trim()) return 'Migration plan is required';
2293
- return true;
2294
- },
2295
- },
2296
- {
2297
- type: 'editor',
2298
- name: 'rollbackPlan',
2299
- message: '🔙 Rollback Plan (one per line, ESC to finish):',
2300
- default: (answers) => {
2301
- if (answers.dataMigration) {
2302
- return '- Feature flag kill-switch\n- Database rollback script\n- Restore from backup\n- Verify system state';
2303
- }
2304
- return '- Feature flag disable\n- Deploy previous version\n- Monitor for issues';
2305
- },
2306
- validate: (input) => {
2307
- if (!input.trim()) return 'Rollback plan is required';
2308
- return true;
2309
- },
2310
- },
2311
- {
2312
- type: 'confirm',
2313
- name: 'needsOverride',
2314
- message: '🚨 Need human override for urgent/low-risk change?',
2315
- default: false,
2316
- },
2317
- {
2318
- type: 'input',
2319
- name: 'overrideRationale',
2320
- message: '📝 Override rationale (urgency, low risk, etc.):',
2321
- when: (answers) => answers.needsOverride,
2322
- validate: (input) => {
2323
- if (!input.trim()) return 'Rationale is required for override';
2324
- return true;
2325
- },
2326
- },
2327
- {
2328
- type: 'input',
2329
- name: 'overrideApprover',
2330
- message: '👤 Override approver (GitHub username or email):',
2331
- when: (answers) => answers.needsOverride,
2332
- validate: (input) => {
2333
- if (!input.trim()) return 'Approver is required for override';
2334
- return true;
2335
- },
2336
- },
2337
- {
2338
- type: 'checkbox',
2339
- name: 'waivedGates',
2340
- message: '⚠️ Gates to waive (select with space):',
2341
- choices: [
2342
- { name: 'Coverage testing', value: 'coverage' },
2343
- { name: 'Mutation testing', value: 'mutation' },
2344
- { name: 'Contract testing', value: 'contracts' },
2345
- { name: 'Manual review', value: 'manual_review' },
2346
- { name: 'Trust score check', value: 'trust_score' },
2347
- ],
2348
- when: (answers) => answers.needsOverride,
2349
- validate: (input) => {
2350
- if (input.length === 0) return 'At least one gate must be waived';
2351
- return true;
2352
- },
2353
- },
2354
- {
2355
- type: 'number',
2356
- name: 'overrideExpiresDays',
2357
- message: '⏰ Override expires in how many days?',
2358
- default: 7,
2359
- when: (answers) => answers.needsOverride,
2360
- validate: (input) => {
2361
- if (input < 1) return 'Must expire in at least 1 day';
2362
- if (input > 30) return 'Cannot exceed 30 days';
2363
- return true;
2364
- },
2365
- },
2366
- {
2367
- type: 'confirm',
2368
- name: 'isExperimental',
2369
- message: '🧪 Experimental/Prototype mode? (Reduced requirements for sandbox code)',
2370
- default: false,
2371
- },
2372
- {
2373
- type: 'input',
2374
- name: 'experimentalRationale',
2375
- message: '🔬 Experimental rationale (what are you exploring?):',
2376
- when: (answers) => answers.isExperimental,
2377
- validate: (input) => {
2378
- if (!input.trim()) return 'Rationale is required for experimental mode';
2379
- return true;
2380
- },
2381
- },
2382
- {
2383
- type: 'input',
2384
- name: 'experimentalSandbox',
2385
- message: '📁 Sandbox location (directory or feature flag):',
2386
- default: 'experimental/',
2387
- when: (answers) => answers.isExperimental,
2388
- validate: (input) => {
2389
- if (!input.trim()) return 'Sandbox location is required';
2390
- return true;
2391
- },
2392
- },
2393
- {
2394
- type: 'number',
2395
- name: 'experimentalExpiresDays',
2396
- message: '⏰ Experimental code expires in how many days?',
2397
- default: 14,
2398
- when: (answers) => answers.isExperimental,
2399
- validate: (input) => {
2400
- if (input < 1) return 'Must expire in at least 1 day';
2401
- if (input > 90) return 'Cannot exceed 90 days for experimental code';
2402
- return true;
2403
- },
2404
- },
2405
- {
2406
- type: 'number',
2407
- name: 'aiConfidence',
2408
- message: '🤖 AI confidence level (1-10, 10 = very confident):',
2409
- default: 7,
2410
- validate: (input) => {
2411
- if (input < 1 || input > 10) return 'Must be between 1 and 10';
2412
- return true;
2413
- },
2414
- },
2415
- {
2416
- type: 'input',
2417
- name: 'uncertaintyAreas',
2418
- message: '❓ Areas of uncertainty (comma-separated):',
2419
- default: '',
2420
- },
2421
- {
2422
- type: 'input',
2423
- name: 'complexityFactors',
2424
- message: '🔧 Complexity factors (comma-separated):',
2425
- default: '',
2426
- },
2427
- ];
2428
-
2429
- console.log(chalk.cyan('⏳ Gathering project requirements...'));
2430
-
2431
- let answers;
2432
- try {
2433
- answers = await inquirer.prompt(questions);
2434
- } catch (error) {
2435
- if (error.isTtyError) {
2436
- console.error(chalk.red('❌ Interactive prompts not supported in this environment'));
2437
- console.error(chalk.blue('💡 Run with --non-interactive flag to use defaults'));
2438
- process.exit(1);
2439
- } else {
2440
- console.error(chalk.red('❌ Error during interactive setup:'), error.message);
2441
- process.exit(1);
2442
- }
2443
- }
2444
-
2445
- console.log(chalk.green('✅ Project requirements gathered successfully!'));
2446
-
2447
- // Show summary before generating spec
2448
- console.log(chalk.bold('\n📋 Configuration Summary:'));
2449
- console.log(` ${chalk.cyan('Project')}: ${answers.projectTitle} (${answers.projectId})`);
2450
- console.log(
2451
- ` ${chalk.cyan('Mode')}: ${answers.projectMode} | ${chalk.cyan('Tier')}: ${answers.riskTier}`
2452
- );
2453
- console.log(` ${chalk.cyan('Budget')}: ${answers.maxFiles} files, ${answers.maxLoc} lines`);
2454
- console.log(` ${chalk.cyan('Data Migration')}: ${answers.dataMigration ? 'Yes' : 'No'}`);
2455
- console.log(` ${chalk.cyan('Rollback SLO')}: ${answers.rollbackSlo}`);
2456
-
2457
- // Generate working spec
2458
- const workingSpecContent = generateWorkingSpec(answers);
2459
-
2460
- // Validate the generated spec
2461
- validateGeneratedSpec(workingSpecContent, answers);
2462
-
2463
- // Save the working spec
2464
- await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
2465
-
2466
- console.log(chalk.green('✅ Working spec generated and validated'));
2467
-
2468
- // Finalize project with provenance and git initialization
2469
- await finalizeProject(projectName, options, answers);
2470
-
2471
- continueToSuccess();
2472
- }
2473
- } catch (error) {
2474
- console.error(chalk.red('❌ Error during project initialization:'), error.message);
2475
-
2476
- // Cleanup on error
2477
- if (fs.existsSync(projectName)) {
2478
- console.log(chalk.cyan('🧹 Cleaning up failed initialization...'));
2479
- try {
2480
- await fs.remove(projectName);
2481
- console.log(chalk.green('✅ Cleanup completed'));
2482
- } catch (cleanupError) {
2483
- console.warn(
2484
- chalk.yellow('⚠️ Could not clean up:'),
2485
- cleanupError?.message || cleanupError
2486
- );
2487
- }
2488
- }
2489
-
2490
- process.exit(1);
2491
- }
2492
- }
2493
-
2494
- // Generate provenance manifest and git initialization (for both modes)
2495
- async function finalizeProject(projectName, options, answers) {
2496
- try {
2497
- // Detect and configure language support
2498
- if (languageSupport) {
2499
- console.log(chalk.cyan('🔍 Detecting project language...'));
2500
- const detectedLanguage = languageSupport.detectProjectLanguage();
2501
-
2502
- if (detectedLanguage !== 'unknown') {
2503
- console.log(chalk.green(`✅ Detected language: ${detectedLanguage}`));
2504
-
2505
- // Generate language-specific configuration
2506
- try {
2507
- const langConfig = languageSupport.generateLanguageConfig(
2508
- detectedLanguage,
2509
- '.caws/language-config.json'
2510
- );
2511
-
2512
- console.log(chalk.green('✅ Generated language-specific configuration'));
2513
- console.log(` Language: ${langConfig.name}`);
2514
- console.log(` Tier: ${langConfig.tier}`);
2515
- console.log(
2516
- ` Thresholds: Branch ≥${langConfig.thresholds.min_branch * 100}%, Mutation ≥${langConfig.thresholds.min_mutation * 100}%`
2517
- );
2518
- } catch (langError) {
2519
- console.warn(chalk.yellow('⚠️ Could not generate language config:'), langError.message);
2520
- }
2521
- } else {
2522
- console.log(
2523
- chalk.blue('ℹ️ Could not detect project language - using default configuration')
2524
- );
2525
- }
2526
- }
2527
-
2528
- // Generate provenance manifest
2529
- console.log(chalk.cyan('📦 Generating provenance manifest...'));
2530
-
2531
- const provenanceData = {
2532
- agent: 'caws-cli',
2533
- model: 'cli-interactive',
2534
- modelHash: CLI_VERSION,
2535
- toolAllowlist: [
2536
- 'node',
2537
- 'npm',
2538
- 'git',
2539
- 'fs-extra',
2540
- 'inquirer',
2541
- 'commander',
2542
- 'js-yaml',
2543
- 'ajv',
2544
- 'chalk',
2545
- ],
2546
- prompts: Object.keys(answers),
2547
- commit: null, // Will be set after git init
2548
- artifacts: ['.caws/working-spec.yaml'],
2549
- results: {
2550
- project_id: answers.projectId,
2551
- project_title: answers.projectTitle,
2552
- risk_tier: answers.riskTier,
2553
- mode: answers.projectMode,
2554
- change_budget: {
2555
- max_files: answers.maxFiles,
2556
- max_loc: answers.maxLoc,
2557
- },
2558
- },
2559
- approvals: [],
2560
- };
2561
-
2562
- // Generate provenance if tools are available
2563
- const tools = loadProvenanceTools();
2564
- if (
2565
- tools &&
2566
- typeof tools.generateProvenance === 'function' &&
2567
- typeof tools.saveProvenance === 'function'
2568
- ) {
2569
- const provenance = tools.generateProvenance(provenanceData);
2570
- await tools.saveProvenance(provenance, '.agent/provenance.json');
2571
- console.log(chalk.green('✅ Provenance manifest generated'));
2572
- } else {
2573
- console.log(
2574
- chalk.yellow('⚠️ Provenance tools not available - skipping manifest generation')
2575
- );
2576
- }
2577
-
2578
- // Initialize git repository
2579
- if (options.git) {
2580
- try {
2581
- console.log(chalk.cyan('🔧 Initializing git repository...'));
2582
-
2583
- // Check if git is available
2584
- try {
2585
- require('child_process').execSync('git --version', { stdio: 'ignore' });
2586
- } catch (error) {
2587
- console.warn(chalk.yellow('⚠️ Git not found. Skipping git initialization.'));
2588
- console.warn(chalk.blue('💡 Install git to enable automatic repository setup.'));
2589
- return;
2590
- }
2591
-
2592
- require('child_process').execSync('git init', { stdio: 'inherit' });
2593
- require('child_process').execSync('git add .', { stdio: 'inherit' });
2594
- require('child_process').execSync('git commit -m "Initial CAWS project setup"', {
2595
- stdio: 'inherit',
2596
- });
2597
- console.log(chalk.green('✅ Git repository initialized'));
2598
-
2599
- // Update provenance with commit hash
2600
- const commitHash = require('child_process')
2601
- .execSync('git rev-parse HEAD', { encoding: 'utf8' })
2602
- .trim();
2603
- const currentProvenance = JSON.parse(fs.readFileSync('.agent/provenance.json', 'utf8'));
2604
- currentProvenance.commit = commitHash;
2605
- currentProvenance.hash = require('crypto')
2606
- .createHash('sha256')
2607
- .update(JSON.stringify(currentProvenance, Object.keys(currentProvenance).sort()))
2608
- .digest('hex');
2609
- await fs.writeFile('.agent/provenance.json', JSON.stringify(currentProvenance, null, 2));
2610
-
2611
- console.log(chalk.green('✅ Provenance updated with commit hash'));
2612
- } catch (error) {
2613
- console.warn(
2614
- chalk.yellow('⚠️ Failed to initialize git repository:'),
2615
- error?.message || String(error)
2616
- );
2617
- console.warn(chalk.blue('💡 You can initialize git manually later with:'));
2618
- console.warn(" git init && git add . && git commit -m 'Initial CAWS project setup'");
2619
- }
2620
- }
2621
- } catch (error) {
2622
- console.error(
2623
- chalk.red('❌ Error during project finalization:'),
2624
- error?.message || String(error)
2625
- );
2626
- }
2627
- }
2628
-
2629
- function continueToSuccess() {
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
-
2646
- console.log(chalk.bold('\nNext steps:'));
2647
- console.log('1. Customize .caws/working-spec.yaml');
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');
2654
- console.log(chalk.blue('\nFor help: caws --help'));
2655
- }
2656
-
2657
- /**
2658
- * Scaffold existing project with CAWS components
2659
- */
2660
- async function scaffoldProject(options) {
2661
- const currentDir = process.cwd();
2662
- const projectName = path.basename(currentDir);
2663
-
2664
- try {
2665
- // Detect existing CAWS setup FIRST before any logging
2666
- const setup = detectCAWSSetup(currentDir);
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
-
2681
- // Preserve the original template directory from global cawsSetup
2682
- // (needed because detectCAWSSetup from within a new project won't find the template)
2683
- if (cawsSetup?.templateDir && !setup.templateDir) {
2684
- setup.templateDir = cawsSetup.templateDir;
2685
- setup.hasTemplateDir = true;
2686
- } else if (!setup.templateDir) {
2687
- // Try to find template directory using absolute paths that work in CI
2688
- const possiblePaths = [
2689
- '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
2690
- '/workspace/packages/caws-template',
2691
- '/caws/packages/caws-template',
2692
- path.resolve(process.cwd(), '../../../packages/caws-template'),
2693
- path.resolve(process.cwd(), '../../packages/caws-template'),
2694
- path.resolve(process.cwd(), '../packages/caws-template'),
2695
- ];
2696
-
2697
- for (const testPath of possiblePaths) {
2698
- if (fs.existsSync(testPath)) {
2699
- setup.templateDir = testPath;
2700
- setup.hasTemplateDir = true;
2701
- break;
2702
- }
2703
- }
2704
-
2705
- if (!setup.templateDir) {
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')}`);
2713
- }
2714
- }
2715
-
2716
- // Override global cawsSetup with current context for scaffold operations
2717
- cawsSetup = setup;
2718
-
2719
- if (!setup.hasCAWSDir) {
2720
- console.error(chalk.red('❌ No .caws directory found'));
2721
- console.error(chalk.blue('💡 Run "caws init <project-name>" first to create a CAWS project'));
2722
- process.exit(1);
2723
- }
2724
-
2725
- // Adapt behavior based on setup type
2726
- if (setup.isEnhanced) {
2727
- console.log(chalk.green('🎯 Enhanced CAWS detected - adding automated publishing'));
2728
- } else if (setup.isAdvanced) {
2729
- console.log(chalk.blue('🔧 Advanced CAWS detected - adding missing capabilities'));
2730
- } else {
2731
- console.log(chalk.blue('📋 Basic CAWS detected - enhancing with additional tools'));
2732
- }
2733
-
2734
- // Generate provenance for scaffolding operation
2735
- const scaffoldProvenance = {
2736
- agent: 'caws-cli',
2737
- model: 'cli-scaffold',
2738
- modelHash: CLI_VERSION,
2739
- toolAllowlist: ['node', 'fs-extra'],
2740
- prompts: ['scaffold', options.force ? 'force' : 'normal'],
2741
- commit: null,
2742
- artifacts: [],
2743
- results: {
2744
- operation: 'scaffold',
2745
- force_mode: options.force,
2746
- target_directory: currentDir,
2747
- },
2748
- approvals: [],
2749
- timestamp: new Date().toISOString(),
2750
- version: CLI_VERSION,
2751
- };
2752
-
2753
- // Calculate hash after object is fully defined
2754
- scaffoldProvenance.hash = require('crypto')
2755
- .createHash('sha256')
2756
- .update(JSON.stringify(scaffoldProvenance))
2757
- .digest('hex');
2758
-
2759
- // Determine what enhancements to add based on setup type and options
2760
- const enhancements = [];
2761
-
2762
- // Add CAWS tools directory structure (matches test expectations)
2763
- enhancements.push({
2764
- name: 'apps/tools/caws',
2765
- description: 'CAWS tools directory',
2766
- required: true,
2767
- });
2768
-
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
- }
2777
-
2778
- // Also add automated publishing for enhanced setups
2779
- if (setup.isEnhanced) {
2780
- enhancements.push({
2781
- name: '.github/workflows/release.yml',
2782
- description: 'GitHub Actions workflow for automated publishing',
2783
- required: true,
2784
- });
2785
-
2786
- enhancements.push({
2787
- name: '.releaserc.json',
2788
- description: 'semantic-release configuration',
2789
- required: true,
2790
- });
2791
- }
2792
-
2793
- // Add commit conventions for setups that don't have them
2794
- if (!setup.hasTemplates || !fs.existsSync(path.join(currentDir, 'COMMIT_CONVENTIONS.md'))) {
2795
- enhancements.push({
2796
- name: 'COMMIT_CONVENTIONS.md',
2797
- description: 'Commit message guidelines',
2798
- required: false,
2799
- });
2800
- }
2801
-
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
- ) {
2807
- enhancements.push({
2808
- name: 'OIDC_SETUP.md',
2809
- description: 'OIDC trusted publisher setup guide',
2810
- required: false,
2811
- });
2812
- }
2813
-
2814
- // For enhanced setups, preserve existing tools
2815
- if (setup.isEnhanced) {
2816
- console.log(chalk.blue('ℹ️ Preserving existing sophisticated CAWS tools'));
2817
- }
2818
-
2819
- let addedCount = 0;
2820
- let skippedCount = 0;
2821
- const addedFiles = [];
2822
-
2823
- for (const enhancement of enhancements) {
2824
- if (!setup?.templateDir) {
2825
- console.warn(
2826
- chalk.yellow(`⚠️ Template directory not available for enhancement: ${enhancement.name}`)
2827
- );
2828
- continue;
2829
- }
2830
- const sourcePath = path.join(setup.templateDir, enhancement.name);
2831
- const destPath = path.join(currentDir, enhancement.name);
2832
-
2833
- if (!fs.existsSync(destPath)) {
2834
- if (fs.existsSync(sourcePath)) {
2835
- try {
2836
- await fs.copy(sourcePath, destPath);
2837
- console.log(chalk.green(`✅ Added ${enhancement.description}`));
2838
- addedCount++;
2839
- addedFiles.push(enhancement.name);
2840
- } catch (copyError) {
2841
- console.warn(chalk.yellow(`⚠️ Failed to add ${enhancement.name}:`), copyError.message);
2842
- }
2843
- } else {
2844
- // If source doesn't exist in template, create the directory structure
2845
- try {
2846
- await fs.ensureDir(destPath);
2847
- console.log(chalk.green(`✅ Created ${enhancement.description}`));
2848
- addedCount++;
2849
- addedFiles.push(enhancement.name);
2850
- } catch (createError) {
2851
- console.warn(
2852
- chalk.yellow(`⚠️ Failed to create ${enhancement.name}:`),
2853
- createError.message
2854
- );
2855
- }
2856
- }
2857
- } else {
2858
- if (options.force) {
2859
- try {
2860
- await fs.remove(destPath);
2861
- if (fs.existsSync(sourcePath)) {
2862
- await fs.copy(sourcePath, destPath);
2863
- } else {
2864
- await fs.ensureDir(destPath);
2865
- }
2866
- console.log(chalk.blue(`🔄 Updated ${enhancement.description}`));
2867
- addedCount++;
2868
- addedFiles.push(enhancement.name);
2869
- } catch (overwriteError) {
2870
- console.warn(
2871
- chalk.yellow(`⚠️ Failed to update ${enhancement.name}:`),
2872
- overwriteError.message
2873
- );
2874
- }
2875
- } else {
2876
- console.log(`⏭️ Skipped ${enhancement.name} (already exists)`);
2877
- skippedCount++;
2878
- }
2879
- }
2880
- }
2881
-
2882
- // Update provenance with results
2883
- scaffoldProvenance.artifacts = addedFiles;
2884
- scaffoldProvenance.results.files_added = addedCount;
2885
- scaffoldProvenance.results.files_skipped = skippedCount;
2886
-
2887
- // Show summary
2888
- console.log(chalk.green(`\n🎉 Enhancement completed!`));
2889
- console.log(chalk.bold(`📊 Summary: ${addedCount} added, ${skippedCount} skipped`));
2890
-
2891
- if (addedCount > 0) {
2892
- console.log(chalk.bold('\n📝 Next steps:'));
2893
- console.log('1. Review the added files');
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
- }
2906
- }
2907
-
2908
- if (setup.isEnhanced) {
2909
- console.log(
2910
- chalk.blue('\n🎯 Your enhanced CAWS setup has been improved with automated publishing!')
2911
- );
2912
- }
18
+ // Import configuration and utilities
19
+ const {
20
+ CLI_VERSION,
21
+ initializeGlobalSetup,
22
+ loadProvenanceTools,
23
+ initializeLanguageSupport,
24
+ } = require('./config');
25
+
26
+ // Import command handlers
27
+ const { initProject } = require('./commands/init');
28
+ const { validateCommand } = require('./commands/validate');
29
+ const { burnupCommand } = require('./commands/burnup');
30
+ const { testAnalysisCommand } = require('./test-analysis');
31
+ const { provenanceCommand } = require('./commands/provenance');
32
+ const { executeTool } = require('./commands/tool');
33
+
34
+ // Import scaffold functionality
35
+ const { scaffoldProject, setScaffoldDependencies } = require('./scaffold');
36
+
37
+ // Import git hooks functionality
38
+ const { scaffoldGitHooks, removeGitHooks, checkGitHooksStatus } = require('./scaffold/git-hooks');
39
+
40
+ // Import validation functionality
41
+ // eslint-disable-next-line no-unused-vars
42
+ const { validateWorkingSpecWithSuggestions } = require('./validation/spec-validation');
43
+
44
+ // Import finalization utilities
45
+ const {
46
+ // eslint-disable-next-line no-unused-vars
47
+ finalizeProject,
48
+ // eslint-disable-next-line no-unused-vars
49
+ continueToSuccess,
50
+ setFinalizationDependencies,
51
+ } = require('./utils/finalization');
52
+
53
+ // Import generators
54
+ const { generateWorkingSpec, validateGeneratedSpec } = require('./generators/working-spec');
55
+
56
+ // Initialize global configuration
57
+ const program = new Command();
2913
58
 
2914
- if (options.force) {
2915
- console.log(chalk.yellow('\n⚠️ Force mode was used - review changes carefully'));
2916
- }
59
+ // Initialize global state
60
+ const cawsSetup = initializeGlobalSetup();
61
+ const languageSupport = initializeLanguageSupport();
2917
62
 
2918
- // Save provenance manifest if tools are available
2919
- const tools = loadProvenanceTools();
2920
- if (tools && typeof tools.saveProvenance === 'function') {
2921
- await tools.saveProvenance(scaffoldProvenance, '.agent/scaffold-provenance.json');
2922
- console.log(chalk.green('✅ Scaffolding provenance saved'));
2923
- } else {
2924
- console.log(chalk.yellow('⚠️ Provenance tools not available - skipping manifest save'));
2925
- }
2926
- } catch (error) {
2927
- // Handle circular reference errors from Commander.js
2928
- if (error.message && error.message.includes('Converting circular structure to JSON')) {
2929
- console.log(
2930
- chalk.yellow('⚠️ Scaffolding completed with minor issues (circular reference handled)')
2931
- );
2932
- console.log(chalk.green('✅ CAWS components scaffolded successfully'));
2933
- } else {
2934
- console.error(chalk.red('❌ Error during scaffolding:'), error.message);
2935
- process.exit(1);
2936
- }
2937
- }
2938
- }
63
+ // Set up dependencies for modules that need them
64
+ setScaffoldDependencies({
65
+ cawsSetup,
66
+ loadProvenanceTools,
67
+ });
2939
68
 
2940
- /**
2941
- * Show version information
2942
- */
2943
- // function showVersion() {
2944
- // console.log(chalk.bold(`CAWS CLI v${CLI_VERSION}`));
2945
- // console.log(chalk.cyan('Coding Agent Workflow System - Scaffolding Tool'));
2946
- // console.log(chalk.gray('Author: @darianrosebrook'));
2947
- // console.log(chalk.gray('License: MIT'));
2948
- // }
69
+ setFinalizationDependencies({
70
+ languageSupport,
71
+ loadProvenanceTools,
72
+ });
2949
73
 
2950
- // CLI Commands
2951
- program
2952
- .name('caws')
2953
- .description('CAWS - Coding Agent Workflow System CLI')
2954
- .version(CLI_VERSION, '-v, --version', 'Show version information');
74
+ // Setup CLI program
75
+ program.name('caws').description('CAWS - Coding Agent Workflow System CLI').version(CLI_VERSION);
2955
76
 
77
+ // Init command
2956
78
  program
2957
79
  .command('init')
2958
- .alias('i')
2959
80
  .description('Initialize a new project with CAWS')
2960
- .argument('<project-name>', 'Name of the new project')
2961
- .option('-i, --interactive', 'Run interactive setup wizard')
2962
- .option('-g, --git', 'Initialize git repository', true)
2963
- .option('-n, --non-interactive', 'Skip interactive prompts')
2964
- .option('--no-git', "Don't initialize git repository")
2965
- .option('-t, --template <type>', 'Use project template (extension|library|api|cli|monorepo)')
81
+ .argument('[project-name]', 'Name of the project to create (use "." for current directory)')
82
+ .option('-i, --interactive', 'Run interactive setup wizard', true)
83
+ .option('--non-interactive', 'Skip interactive prompts (use defaults)', false)
84
+ .option('--template <template>', 'Use specific project template')
2966
85
  .action(initProject);
2967
86
 
87
+ // Scaffold command
2968
88
  program
2969
89
  .command('scaffold')
2970
- .alias('s')
2971
90
  .description('Add CAWS components to existing project')
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)')
91
+ .option('-f, --force', 'Overwrite existing files', false)
92
+ .option('--minimal', 'Only essential components', false)
93
+ .option('--with-codemods', 'Include codemod scripts', false)
94
+ .option('--with-oidc', 'Include OIDC trusted publisher setup', false)
2976
95
  .action(scaffoldProject);
2977
96
 
97
+ // Validate command
2978
98
  program
2979
99
  .command('validate')
2980
- .alias('v')
2981
100
  .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
- }
101
+ .argument('[spec-file]', 'Path to working spec file (default: .caws/working-spec.yaml)')
102
+ .option('-q, --quiet', 'Suppress suggestions and warnings', false)
103
+ .option('--auto-fix', 'Automatically fix safe validation issues', false)
104
+ .action(validateCommand);
105
+
106
+ // Tool command
107
+ program
108
+ .command('tool')
109
+ .description('Execute CAWS tools programmatically')
110
+ .argument('<tool-id>', 'ID of the tool to execute')
111
+ .option('-p, --params <json>', 'Parameters as JSON string', '{}')
112
+ .option('-t, --timeout <ms>', 'Execution timeout in milliseconds', parseInt, 30000)
113
+ .action(executeTool);
114
+
115
+ // Test Analysis command
116
+ program
117
+ .command('test-analysis <subcommand> [options...]')
118
+ .description('Statistical analysis for budget prediction and test optimization')
119
+ .action(testAnalysisCommand);
120
+
121
+ // Provenance command group
122
+ const provenanceCmd = program
123
+ .command('provenance')
124
+ .description('Manage CAWS provenance tracking and audit trails');
125
+
126
+ // Subcommands
127
+ provenanceCmd
128
+ .command('update')
129
+ .description('Add new commit to provenance chain')
130
+ .requiredOption('-c, --commit <hash>', 'Git commit hash')
131
+ .option('-m, --message <msg>', 'Commit message')
132
+ .option('-a, --author <info>', 'Author information')
133
+ .option('-q, --quiet', 'Suppress output')
134
+ .option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
135
+ .action(async (options) => {
136
+ await provenanceCommand('update', options);
137
+ });
2995
138
 
2996
- // Load and parse spec
2997
- const specContent = fs.readFileSync(specFile, 'utf8');
2998
- const spec = yaml.load(specContent);
139
+ provenanceCmd
140
+ .command('show')
141
+ .description('Display current provenance history')
142
+ .option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
143
+ .option('--format <type>', 'Output format: text, json, dashboard', 'text')
144
+ .action(async (options) => {
145
+ await provenanceCommand('show', options);
146
+ });
2999
147
 
3000
- if (!spec) {
3001
- console.error(chalk.red('❌ Failed to parse working spec YAML'));
3002
- process.exit(1);
3003
- }
148
+ provenanceCmd
149
+ .command('verify')
150
+ .description('Validate provenance chain integrity')
151
+ .option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
152
+ .action(async (options) => {
153
+ await provenanceCommand('verify', options);
154
+ });
155
+
156
+ provenanceCmd
157
+ .command('analyze-ai')
158
+ .description('Analyze AI-assisted development patterns')
159
+ .option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
160
+ .action(async (options) => {
161
+ await provenanceCommand('analyze-ai', options);
162
+ });
163
+
164
+ provenanceCmd
165
+ .command('init')
166
+ .description('Initialize provenance tracking for the project')
167
+ .option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
168
+ .option('--cursor-api <url>', 'Cursor tracking API endpoint')
169
+ .option('--cursor-key <key>', 'Cursor API key')
170
+ .action(async (options) => {
171
+ await provenanceCommand('init', options);
172
+ });
3004
173
 
3005
- // Validate with suggestions
3006
- const result = validateWorkingSpecWithSuggestions(spec, {
3007
- autoFix: options.autoFix,
3008
- suggestions: !options.quiet,
3009
- });
174
+ // Git hooks command
175
+ const hooksCmd = program
176
+ .command('hooks')
177
+ .description('Manage CAWS git hooks for provenance tracking');
178
+
179
+ hooksCmd
180
+ .command('install')
181
+ .description('Install CAWS git hooks')
182
+ .option('--no-provenance', 'Skip provenance tracking hooks')
183
+ .option('--no-validation', 'Skip validation hooks')
184
+ .option('--no-quality-gates', 'Skip quality gate hooks')
185
+ .option('--force', 'Overwrite existing hooks')
186
+ .option('--backup', 'Backup existing hooks before replacing')
187
+ .action(async (options) => {
188
+ const hookOptions = {
189
+ provenance: options.provenance !== false,
190
+ validation: options.validation !== false,
191
+ qualityGates: options.qualityGates !== false,
192
+ force: options.force,
193
+ backup: options.backup,
194
+ };
3010
195
 
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}`));
196
+ try {
197
+ const result = await scaffoldGitHooks(process.cwd(), hookOptions);
198
+ if (result.added > 0) {
199
+ console.log(`✅ Successfully installed ${result.added} git hooks`);
200
+ if (result.skipped > 0) {
201
+ console.log(`⏭️ Skipped ${result.skipped} existing hooks`);
202
+ }
203
+ } else {
204
+ console.log('ℹ️ All hooks already configured');
3016
205
  }
206
+ } catch (error) {
207
+ console.error(`❌ Failed to install git hooks: ${error.message}`);
208
+ process.exit(1);
209
+ }
210
+ });
211
+
212
+ hooksCmd
213
+ .command('remove')
214
+ .description('Remove CAWS git hooks')
215
+ .action(async () => {
216
+ try {
217
+ await removeGitHooks(process.cwd());
218
+ } catch (error) {
219
+ console.error(`❌ Failed to remove git hooks: ${error.message}`);
220
+ process.exit(1);
221
+ }
222
+ });
3017
223
 
3018
- // Exit with appropriate code
3019
- process.exit(result.valid ? 0 : 1);
224
+ hooksCmd
225
+ .command('status')
226
+ .description('Check git hooks status')
227
+ .action(async () => {
228
+ try {
229
+ await checkGitHooksStatus(process.cwd());
3020
230
  } catch (error) {
3021
- console.error(chalk.red('❌ Error during validation:'), error.message);
231
+ console.error(`❌ Failed to check git hooks status: ${error.message}`);
3022
232
  process.exit(1);
3023
233
  }
3024
234
  });
@@ -3036,7 +246,7 @@ program.exitOverride((err) => {
3036
246
  process.exit(1);
3037
247
  });
3038
248
 
3039
- // Parse and run (only when run directly, not when required as module)
249
+ // Parse and run
3040
250
  if (require.main === module) {
3041
251
  try {
3042
252
  program.parse();