@paths.design/caws-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,946 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview CAWS CLI - Scaffolding tool for Coding Agent Workflow System
4
+ * Provides commands to initialize new projects and scaffold existing ones with CAWS
5
+ * @author @darianrosebrook
6
+ */
7
+ const { Command } = require('commander');
8
+ const fs = require('fs-extra');
9
+ const path = require('path');
10
+ const inquirer = require('inquirer').default || require('inquirer');
11
+ const yaml = require('js-yaml');
12
+ const chalk = require('chalk');
13
+ const program = new Command();
14
+ // Configuration
15
+ const TEMPLATE_DIR = path.join(__dirname, '../../caws-template');
16
+ const { generateProvenance, saveProvenance } = require(path.join(TEMPLATE_DIR, 'apps/tools/caws/provenance.js'));
17
+ const CLI_VERSION = require('../package.json').version;
18
+ // Initialize JSON Schema validator - using simplified validation for CLI stability
19
+ const validateWorkingSpec = (spec) => {
20
+ try {
21
+ // Basic structural validation for essential fields
22
+ const requiredFields = [
23
+ 'id',
24
+ 'title',
25
+ 'risk_tier',
26
+ 'mode',
27
+ 'change_budget',
28
+ 'blast_radius',
29
+ 'operational_rollback_slo',
30
+ 'scope',
31
+ 'invariants',
32
+ 'acceptance',
33
+ 'non_functional',
34
+ 'contracts',
35
+ ];
36
+ for (const field of requiredFields) {
37
+ if (!spec[field]) {
38
+ return {
39
+ valid: false,
40
+ errors: [
41
+ {
42
+ instancePath: `/${field}`,
43
+ message: `Missing required field: ${field}`,
44
+ },
45
+ ],
46
+ };
47
+ }
48
+ }
49
+ // Validate specific field formats
50
+ if (!/^[A-Z]+-\d+$/.test(spec.id)) {
51
+ return {
52
+ valid: false,
53
+ errors: [
54
+ {
55
+ instancePath: '/id',
56
+ message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ if (spec.risk_tier < 1 || spec.risk_tier > 3) {
62
+ return {
63
+ valid: false,
64
+ errors: [
65
+ {
66
+ instancePath: '/risk_tier',
67
+ message: 'Risk tier must be 1, 2, or 3',
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
73
+ return {
74
+ valid: false,
75
+ errors: [
76
+ {
77
+ instancePath: '/scope/in',
78
+ message: 'Scope IN must not be empty',
79
+ },
80
+ ],
81
+ };
82
+ }
83
+ return { valid: true };
84
+ }
85
+ catch (error) {
86
+ return {
87
+ valid: false,
88
+ errors: [
89
+ {
90
+ instancePath: '',
91
+ message: `Validation error: ${error.message}`,
92
+ },
93
+ ],
94
+ };
95
+ }
96
+ };
97
+ console.log(chalk.green('āœ… Schema validation initialized successfully'));
98
+ /**
99
+ * Copy template files to destination
100
+ * @param {string} templatePath - Source template path
101
+ * @param {string} destPath - Destination path
102
+ * @param {Object} replacements - Template variable replacements
103
+ */
104
+ async function copyTemplate(templatePath, destPath, replacements = {}) {
105
+ try {
106
+ // Ensure destination directory exists
107
+ await fs.ensureDir(destPath);
108
+ // Check if template directory exists
109
+ if (!fs.existsSync(templatePath)) {
110
+ console.error(chalk.red('āŒ Template directory not found:'), templatePath);
111
+ console.error(chalk.blue("šŸ’” Make sure you're running the CLI from the correct directory"));
112
+ process.exit(1);
113
+ }
114
+ // Copy all files and directories
115
+ await fs.copy(templatePath, destPath);
116
+ // Replace template variables in text files
117
+ const files = await fs.readdir(destPath, { recursive: true });
118
+ for (const file of files) {
119
+ const filePath = path.join(destPath, file);
120
+ const stat = await fs.stat(filePath);
121
+ if (stat.isFile() &&
122
+ (file.endsWith('.md') || file.endsWith('.yml') || file.endsWith('.yaml'))) {
123
+ try {
124
+ let content = await fs.readFile(filePath, 'utf8');
125
+ Object.entries(replacements).forEach(([key, value]) => {
126
+ content = content.replace(new RegExp(`{{${key}}}`, 'g'), value);
127
+ });
128
+ await fs.writeFile(filePath, content);
129
+ }
130
+ catch (fileError) {
131
+ console.warn(chalk.yellow(`āš ļø Warning: Could not process template file ${file}:`), fileError.message);
132
+ }
133
+ }
134
+ }
135
+ console.log(chalk.green('āœ… Template files copied successfully'));
136
+ }
137
+ catch (error) {
138
+ console.error(chalk.red('āŒ Error copying template:'), error.message);
139
+ if (error.code === 'EACCES') {
140
+ console.error(chalk.blue('šŸ’” This might be a permissions issue. Try running with elevated privileges.'));
141
+ }
142
+ else if (error.code === 'ENOENT') {
143
+ console.error(chalk.blue('šŸ’” Template directory not found. Make sure the caws-template directory exists.'));
144
+ }
145
+ else if (error.code === 'ENOSPC') {
146
+ console.error(chalk.blue('šŸ’” Not enough disk space to copy template files.'));
147
+ }
148
+ process.exit(1);
149
+ }
150
+ }
151
+ /**
152
+ * Generate working spec YAML with user input
153
+ * @param {Object} answers - User responses
154
+ * @returns {string} - Generated YAML content
155
+ */
156
+ function generateWorkingSpec(answers) {
157
+ const template = {
158
+ id: answers.projectId,
159
+ title: answers.projectTitle,
160
+ risk_tier: answers.riskTier,
161
+ mode: answers.projectMode,
162
+ change_budget: {
163
+ max_files: answers.maxFiles,
164
+ max_loc: answers.maxLoc,
165
+ },
166
+ blast_radius: {
167
+ modules: answers.blastModules
168
+ .split(',')
169
+ .map((m) => m.trim())
170
+ .filter((m) => m),
171
+ data_migration: answers.dataMigration,
172
+ },
173
+ operational_rollback_slo: answers.rollbackSlo,
174
+ threats: (answers.projectThreats || '')
175
+ .split('\n')
176
+ .map((t) => t.trim())
177
+ .filter((t) => t && !t.startsWith('-') === false), // Allow lines starting with -
178
+ scope: {
179
+ in: (answers.scopeIn || '')
180
+ .split(',')
181
+ .map((s) => s.trim())
182
+ .filter((s) => s),
183
+ out: (answers.scopeOut || '')
184
+ .split(',')
185
+ .map((s) => s.trim())
186
+ .filter((s) => s),
187
+ },
188
+ invariants: (answers.projectInvariants || '')
189
+ .split('\n')
190
+ .map((i) => i.trim())
191
+ .filter((i) => i),
192
+ acceptance: answers.acceptanceCriteria
193
+ .split('\n')
194
+ .filter((a) => a.trim())
195
+ .map((criteria, index) => {
196
+ const id = `A${index + 1}`;
197
+ const upperCriteria = criteria.toUpperCase();
198
+ // Try different variations of the format
199
+ let given = '';
200
+ let when = '';
201
+ let then = '';
202
+ if (upperCriteria.includes('GIVEN') &&
203
+ upperCriteria.includes('WHEN') &&
204
+ upperCriteria.includes('THEN')) {
205
+ given = criteria.split(/WHEN/i)[0]?.replace(/GIVEN/i, '').trim() || '';
206
+ const whenThen = criteria.split(/WHEN/i)[1];
207
+ when = whenThen?.split(/THEN/i)[0]?.trim() || '';
208
+ then = whenThen?.split(/THEN/i)[1]?.trim() || '';
209
+ }
210
+ else {
211
+ // Fallback: just split by lines and create simple criteria
212
+ given = 'Current system state';
213
+ when = criteria.replace(/^(GIVEN|WHEN|THEN)/i, '').trim();
214
+ then = 'Expected behavior occurs';
215
+ }
216
+ return {
217
+ id,
218
+ given: given || 'Current system state',
219
+ when: when || criteria,
220
+ then: then || 'Expected behavior occurs',
221
+ };
222
+ }),
223
+ non_functional: {
224
+ a11y: answers.a11yRequirements
225
+ .split(',')
226
+ .map((a) => a.trim())
227
+ .filter((a) => a),
228
+ perf: { api_p95_ms: answers.perfBudget },
229
+ security: answers.securityRequirements
230
+ .split(',')
231
+ .map((s) => s.trim())
232
+ .filter((s) => s),
233
+ },
234
+ contracts: [
235
+ {
236
+ type: answers.contractType,
237
+ path: answers.contractPath,
238
+ },
239
+ ],
240
+ observability: {
241
+ logs: answers.observabilityLogs
242
+ .split(',')
243
+ .map((l) => l.trim())
244
+ .filter((l) => l),
245
+ metrics: answers.observabilityMetrics
246
+ .split(',')
247
+ .map((m) => m.trim())
248
+ .filter((m) => m),
249
+ traces: answers.observabilityTraces
250
+ .split(',')
251
+ .map((t) => t.trim())
252
+ .filter((t) => t),
253
+ },
254
+ migrations: (answers.migrationPlan || '')
255
+ .split('\n')
256
+ .map((m) => m.trim())
257
+ .filter((m) => m),
258
+ rollback: (answers.rollbackPlan || '')
259
+ .split('\n')
260
+ .map((r) => r.trim())
261
+ .filter((r) => r),
262
+ };
263
+ return yaml.dump(template, { indent: 2 });
264
+ }
265
+ /**
266
+ * Validate generated working spec against JSON schema
267
+ * @param {string} specContent - YAML spec content
268
+ * @param {Object} answers - User responses for error context
269
+ */
270
+ function validateGeneratedSpec(specContent, _answers) {
271
+ try {
272
+ const spec = yaml.load(specContent);
273
+ const isValid = validateWorkingSpec(spec);
274
+ if (!isValid) {
275
+ console.error(chalk.red('āŒ Generated working spec failed validation:'));
276
+ validateWorkingSpec.errors.forEach((error) => {
277
+ console.error(` - ${error.instancePath || 'root'}: ${error.message}`);
278
+ });
279
+ // Provide helpful guidance
280
+ console.log(chalk.blue('\nšŸ’” Validation Tips:'));
281
+ console.log(' - Ensure risk_tier is 1, 2, or 3');
282
+ console.log(' - Check that scope.in is not empty');
283
+ console.log(' - Verify invariants and acceptance criteria are provided');
284
+ console.log(' - For tier 1 and 2, ensure contracts are specified');
285
+ process.exit(1);
286
+ }
287
+ console.log(chalk.green('āœ… Generated working spec passed validation'));
288
+ }
289
+ catch (error) {
290
+ console.error(chalk.red('āŒ Error validating working spec:'), error.message);
291
+ process.exit(1);
292
+ }
293
+ }
294
+ /**
295
+ * Initialize a new project with CAWS
296
+ */
297
+ async function initProject(projectName, options) {
298
+ console.log(chalk.cyan(`šŸš€ Initializing new CAWS project: ${projectName}`));
299
+ try {
300
+ // Validate project name
301
+ if (!projectName || projectName.trim() === '') {
302
+ console.error(chalk.red('āŒ Project name is required'));
303
+ console.error(chalk.blue('šŸ’” Usage: caws init <project-name>'));
304
+ process.exit(1);
305
+ }
306
+ // Sanitize project name
307
+ const sanitizedName = projectName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
308
+ if (sanitizedName !== projectName) {
309
+ console.warn(chalk.yellow(`āš ļø Project name sanitized to: ${sanitizedName}`));
310
+ projectName = sanitizedName;
311
+ }
312
+ // Check if directory already exists
313
+ if (fs.existsSync(projectName)) {
314
+ console.error(chalk.red(`āŒ Directory ${projectName} already exists`));
315
+ console.error(chalk.blue('šŸ’” Choose a different name or remove the existing directory'));
316
+ process.exit(1);
317
+ }
318
+ // Create project directory
319
+ await fs.ensureDir(projectName);
320
+ process.chdir(projectName);
321
+ console.log(chalk.green(`šŸ“ Created project directory: ${projectName}`));
322
+ // Copy template files
323
+ await copyTemplate(TEMPLATE_DIR, '.');
324
+ if (options.interactive && !options.nonInteractive) {
325
+ // Interactive setup with enhanced prompts
326
+ console.log(chalk.cyan('šŸ”§ Starting interactive project configuration...'));
327
+ console.log(chalk.blue('šŸ’” Press Ctrl+C at any time to exit and use defaults'));
328
+ const questions = [
329
+ {
330
+ type: 'input',
331
+ name: 'projectId',
332
+ message: 'šŸ“‹ Project ID (e.g., FEAT-1234, AUTH-456):',
333
+ default: projectName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
334
+ validate: (input) => {
335
+ if (!input.trim())
336
+ return 'Project ID is required';
337
+ const pattern = /^[A-Z]+-\d+$/;
338
+ if (!pattern.test(input)) {
339
+ return 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)';
340
+ }
341
+ return true;
342
+ },
343
+ },
344
+ {
345
+ type: 'input',
346
+ name: 'projectTitle',
347
+ message: 'šŸ“ Project Title (descriptive name):',
348
+ default: projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
349
+ validate: (input) => {
350
+ if (!input.trim())
351
+ return 'Project title is required';
352
+ if (input.trim().length < 8) {
353
+ return 'Project title should be at least 8 characters long';
354
+ }
355
+ return true;
356
+ },
357
+ },
358
+ {
359
+ type: 'list',
360
+ name: 'riskTier',
361
+ message: 'āš ļø Risk Tier (higher tier = more rigor):',
362
+ choices: [
363
+ {
364
+ name: 'šŸ”“ Tier 1 - Critical (auth, billing, migrations) - Max rigor',
365
+ value: 1,
366
+ },
367
+ {
368
+ name: '🟔 Tier 2 - Standard (features, APIs) - Standard rigor',
369
+ value: 2,
370
+ },
371
+ {
372
+ name: '🟢 Tier 3 - Low Risk (UI, tooling) - Basic rigor',
373
+ value: 3,
374
+ },
375
+ ],
376
+ default: 2,
377
+ },
378
+ {
379
+ type: 'list',
380
+ name: 'projectMode',
381
+ message: 'šŸŽÆ Project Mode:',
382
+ choices: [
383
+ { name: '✨ feature (new functionality)', value: 'feature' },
384
+ { name: 'šŸ”„ refactor (code restructuring)', value: 'refactor' },
385
+ { name: 'šŸ› fix (bug fixes)', value: 'fix' },
386
+ { name: 'šŸ“š doc (documentation)', value: 'doc' },
387
+ { name: '🧹 chore (maintenance)', value: 'chore' },
388
+ ],
389
+ default: 'feature',
390
+ },
391
+ {
392
+ type: 'number',
393
+ name: 'maxFiles',
394
+ message: 'šŸ“Š Max files to change:',
395
+ default: (answers) => {
396
+ // Dynamic defaults based on risk tier
397
+ switch (answers.riskTier) {
398
+ case 1:
399
+ return 40;
400
+ case 2:
401
+ return 25;
402
+ case 3:
403
+ return 15;
404
+ default:
405
+ return 25;
406
+ }
407
+ },
408
+ validate: (input) => {
409
+ if (input < 1)
410
+ return 'Must change at least 1 file';
411
+ return true;
412
+ },
413
+ },
414
+ {
415
+ type: 'number',
416
+ name: 'maxLoc',
417
+ message: 'šŸ“ Max lines of code to change:',
418
+ default: (answers) => {
419
+ // Dynamic defaults based on risk tier
420
+ switch (answers.riskTier) {
421
+ case 1:
422
+ return 1500;
423
+ case 2:
424
+ return 1000;
425
+ case 3:
426
+ return 600;
427
+ default:
428
+ return 1000;
429
+ }
430
+ },
431
+ validate: (input) => {
432
+ if (input < 1)
433
+ return 'Must change at least 1 line';
434
+ return true;
435
+ },
436
+ },
437
+ {
438
+ type: 'input',
439
+ name: 'blastModules',
440
+ message: 'šŸ’„ Blast Radius - Affected modules (comma-separated):',
441
+ default: 'core,api',
442
+ validate: (input) => {
443
+ if (!input.trim())
444
+ return 'At least one module must be specified';
445
+ return true;
446
+ },
447
+ },
448
+ {
449
+ type: 'confirm',
450
+ name: 'dataMigration',
451
+ message: 'šŸ—„ļø Requires data migration?',
452
+ default: false,
453
+ },
454
+ {
455
+ type: 'input',
456
+ name: 'rollbackSlo',
457
+ message: 'ā±ļø Operational rollback SLO (e.g., 5m, 1h, 24h):',
458
+ default: '5m',
459
+ validate: (input) => {
460
+ const pattern = /^([0-9]+m|[0-9]+h|[0-9]+d)$/;
461
+ if (!pattern.test(input)) {
462
+ return 'SLO should be in format: NUMBER + m/h/d (e.g., 5m, 1h, 24h)';
463
+ }
464
+ return true;
465
+ },
466
+ },
467
+ {
468
+ type: 'editor',
469
+ name: 'projectThreats',
470
+ message: 'āš ļø Project Threats & Risks (one per line, ESC to finish):',
471
+ default: (answers) => {
472
+ const baseThreats = '- Race condition in concurrent operations\n- Performance degradation under load';
473
+ if (answers.dataMigration) {
474
+ return baseThreats + '\n- Data migration failure\n- Inconsistent data state';
475
+ }
476
+ return baseThreats;
477
+ },
478
+ },
479
+ {
480
+ type: 'input',
481
+ name: 'scopeIn',
482
+ message: "šŸŽÆ Scope IN - What's included (comma-separated):",
483
+ default: (answers) => {
484
+ if (answers.projectMode === 'feature')
485
+ return 'user authentication, api endpoints';
486
+ if (answers.projectMode === 'refactor')
487
+ return 'authentication module, user service';
488
+ if (answers.projectMode === 'fix')
489
+ return 'error handling, validation';
490
+ return 'project files';
491
+ },
492
+ validate: (input) => {
493
+ if (!input.trim())
494
+ return 'At least one scope item must be specified';
495
+ return true;
496
+ },
497
+ },
498
+ {
499
+ type: 'input',
500
+ name: 'scopeOut',
501
+ message: "🚫 Scope OUT - What's excluded (comma-separated):",
502
+ default: (answers) => {
503
+ if (answers.projectMode === 'feature')
504
+ return 'legacy authentication, deprecated endpoints';
505
+ if (answers.projectMode === 'refactor')
506
+ return 'external dependencies, configuration files';
507
+ return 'unrelated features';
508
+ },
509
+ },
510
+ {
511
+ type: 'editor',
512
+ name: 'projectInvariants',
513
+ message: 'šŸ›”ļø System Invariants (one per line, ESC to finish):',
514
+ default: '- System remains available\n- Data consistency maintained\n- User sessions preserved',
515
+ },
516
+ {
517
+ type: 'editor',
518
+ name: 'acceptanceCriteria',
519
+ message: 'āœ… Acceptance Criteria (GIVEN...WHEN...THEN, one per line, ESC to finish):',
520
+ default: (answers) => {
521
+ if (answers.projectMode === 'feature') {
522
+ return 'GIVEN user is authenticated WHEN accessing protected endpoint THEN access is granted\nGIVEN invalid credentials WHEN attempting login THEN access is denied';
523
+ }
524
+ if (answers.projectMode === 'fix') {
525
+ return 'GIVEN existing functionality WHEN applying fix THEN behavior is preserved\nGIVEN error condition WHEN fix is applied THEN error is resolved';
526
+ }
527
+ return 'GIVEN current system state WHEN change is applied THEN expected behavior occurs';
528
+ },
529
+ validate: (input) => {
530
+ if (!input.trim())
531
+ return 'At least one acceptance criterion is required';
532
+ const lines = input
533
+ .trim()
534
+ .split('\n')
535
+ .filter((line) => line.trim());
536
+ if (lines.length === 0)
537
+ return 'At least one acceptance criterion is required';
538
+ return true;
539
+ },
540
+ },
541
+ {
542
+ type: 'input',
543
+ name: 'a11yRequirements',
544
+ message: '♿ Accessibility Requirements (comma-separated):',
545
+ default: 'keyboard navigation, screen reader support, color contrast',
546
+ },
547
+ {
548
+ type: 'number',
549
+ name: 'perfBudget',
550
+ message: '⚔ Performance Budget (API p95 latency in ms):',
551
+ default: 250,
552
+ validate: (input) => {
553
+ if (input < 1)
554
+ return 'Performance budget must be at least 1ms';
555
+ if (input > 10000)
556
+ return 'Performance budget seems too high (max 10s)';
557
+ return true;
558
+ },
559
+ },
560
+ {
561
+ type: 'input',
562
+ name: 'securityRequirements',
563
+ message: 'šŸ”’ Security Requirements (comma-separated):',
564
+ default: 'input validation, rate limiting, authentication, authorization',
565
+ },
566
+ {
567
+ type: 'list',
568
+ name: 'contractType',
569
+ message: 'šŸ“„ Contract Type:',
570
+ choices: [
571
+ { name: 'OpenAPI (REST APIs)', value: 'openapi' },
572
+ { name: 'GraphQL Schema', value: 'graphql' },
573
+ { name: 'Protocol Buffers', value: 'proto' },
574
+ { name: 'Pact (consumer-driven)', value: 'pact' },
575
+ ],
576
+ default: 'openapi',
577
+ },
578
+ {
579
+ type: 'input',
580
+ name: 'contractPath',
581
+ message: 'šŸ“ Contract File Path:',
582
+ default: (answers) => {
583
+ if (answers.contractType === 'openapi')
584
+ return 'apps/contracts/api.yaml';
585
+ if (answers.contractType === 'graphql')
586
+ return 'apps/contracts/schema.graphql';
587
+ if (answers.contractType === 'proto')
588
+ return 'apps/contracts/service.proto';
589
+ if (answers.contractType === 'pact')
590
+ return 'apps/contracts/pacts/';
591
+ return 'apps/contracts/api.yaml';
592
+ },
593
+ },
594
+ {
595
+ type: 'input',
596
+ name: 'observabilityLogs',
597
+ message: 'šŸ“ Observability - Log Events (comma-separated):',
598
+ default: 'auth.success, auth.failure, api.request, api.response',
599
+ },
600
+ {
601
+ type: 'input',
602
+ name: 'observabilityMetrics',
603
+ message: 'šŸ“Š Observability - Metrics (comma-separated):',
604
+ default: 'auth_attempts_total, auth_success_total, api_requests_total, api_errors_total',
605
+ },
606
+ {
607
+ type: 'input',
608
+ name: 'observabilityTraces',
609
+ message: 'šŸ” Observability - Traces (comma-separated):',
610
+ default: 'auth_flow, api_request',
611
+ },
612
+ {
613
+ type: 'editor',
614
+ name: 'migrationPlan',
615
+ message: 'šŸ”„ Migration Plan (one per line, ESC to finish):',
616
+ default: (answers) => {
617
+ if (answers.dataMigration) {
618
+ return '- Create new database schema\n- Add new auth table\n- Migrate existing users\n- Validate data integrity';
619
+ }
620
+ return '- Deploy feature flags\n- Roll out gradually\n- Monitor metrics';
621
+ },
622
+ validate: (input) => {
623
+ if (!input.trim())
624
+ return 'Migration plan is required';
625
+ return true;
626
+ },
627
+ },
628
+ {
629
+ type: 'editor',
630
+ name: 'rollbackPlan',
631
+ message: 'šŸ”™ Rollback Plan (one per line, ESC to finish):',
632
+ default: (answers) => {
633
+ if (answers.dataMigration) {
634
+ return '- Feature flag kill-switch\n- Database rollback script\n- Restore from backup\n- Verify system state';
635
+ }
636
+ return '- Feature flag disable\n- Deploy previous version\n- Monitor for issues';
637
+ },
638
+ validate: (input) => {
639
+ if (!input.trim())
640
+ return 'Rollback plan is required';
641
+ return true;
642
+ },
643
+ },
644
+ ];
645
+ console.log(chalk.cyan('ā³ Gathering project requirements...'));
646
+ let answers;
647
+ try {
648
+ answers = await inquirer.prompt(questions);
649
+ }
650
+ catch (error) {
651
+ if (error.isTtyError) {
652
+ console.error(chalk.red('āŒ Interactive prompts not supported in this environment'));
653
+ console.error(chalk.blue('šŸ’” Run with --non-interactive flag to use defaults'));
654
+ process.exit(1);
655
+ }
656
+ else {
657
+ console.error(chalk.red('āŒ Error during interactive setup:'), error.message);
658
+ process.exit(1);
659
+ }
660
+ }
661
+ console.log(chalk.green('āœ… Project requirements gathered successfully!'));
662
+ // Show summary before generating spec
663
+ console.log(chalk.bold('\nšŸ“‹ Configuration Summary:'));
664
+ console.log(` ${chalk.cyan('Project')}: ${answers.projectTitle} (${answers.projectId})`);
665
+ console.log(` ${chalk.cyan('Mode')}: ${answers.projectMode} | ${chalk.cyan('Tier')}: ${answers.riskTier}`);
666
+ console.log(` ${chalk.cyan('Budget')}: ${answers.maxFiles} files, ${answers.maxLoc} lines`);
667
+ console.log(` ${chalk.cyan('Data Migration')}: ${answers.dataMigration ? 'Yes' : 'No'}`);
668
+ console.log(` ${chalk.cyan('Rollback SLO')}: ${answers.rollbackSlo}`);
669
+ // Generate working spec
670
+ const workingSpecContent = generateWorkingSpec(answers);
671
+ // Validate the generated spec
672
+ validateGeneratedSpec(workingSpecContent, answers);
673
+ // Save the working spec
674
+ await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
675
+ console.log(chalk.green('āœ… Working spec generated and validated'));
676
+ }
677
+ // Finalize project with provenance and git initialization
678
+ await finalizeProject(projectName, options, answers);
679
+ continueToSuccess();
680
+ }
681
+ catch (error) {
682
+ console.error(chalk.red('āŒ Error during project initialization:'), error.message);
683
+ // Cleanup on error
684
+ if (fs.existsSync(projectName)) {
685
+ console.log(chalk.cyan('🧹 Cleaning up failed initialization...'));
686
+ try {
687
+ await fs.remove(projectName);
688
+ console.log(chalk.green('āœ… Cleanup completed'));
689
+ }
690
+ catch (cleanupError) {
691
+ console.warn(chalk.yellow('āš ļø Could not clean up:'), cleanupError?.message || cleanupError);
692
+ }
693
+ }
694
+ process.exit(1);
695
+ }
696
+ }
697
+ // Generate provenance manifest and git initialization (for both modes)
698
+ async function finalizeProject(projectName, options, answers) {
699
+ try {
700
+ // Generate provenance manifest
701
+ console.log(chalk.cyan('šŸ“¦ Generating provenance manifest...'));
702
+ const provenanceData = {
703
+ agent: 'caws-cli',
704
+ model: 'cli-interactive',
705
+ modelHash: CLI_VERSION,
706
+ toolAllowlist: [
707
+ 'node',
708
+ 'npm',
709
+ 'git',
710
+ 'fs-extra',
711
+ 'inquirer',
712
+ 'commander',
713
+ 'js-yaml',
714
+ 'ajv',
715
+ 'chalk',
716
+ ],
717
+ prompts: Object.keys(answers),
718
+ commit: null, // Will be set after git init
719
+ artifacts: ['.caws/working-spec.yaml'],
720
+ results: {
721
+ project_id: answers.projectId,
722
+ project_title: answers.projectTitle,
723
+ risk_tier: answers.riskTier,
724
+ mode: answers.projectMode,
725
+ change_budget: {
726
+ max_files: answers.maxFiles,
727
+ max_loc: answers.maxLoc,
728
+ },
729
+ },
730
+ approvals: [],
731
+ };
732
+ const provenance = generateProvenance(provenanceData);
733
+ await saveProvenance(provenance, '.agent/provenance.json');
734
+ console.log(chalk.green('āœ… Provenance manifest generated'));
735
+ // Initialize git repository
736
+ if (options.git) {
737
+ try {
738
+ console.log(chalk.cyan('šŸ”§ Initializing git repository...'));
739
+ // Check if git is available
740
+ try {
741
+ require('child_process').execSync('git --version', { stdio: 'ignore' });
742
+ }
743
+ catch (error) {
744
+ console.warn(chalk.yellow('āš ļø Git not found. Skipping git initialization.'));
745
+ console.warn(chalk.blue('šŸ’” Install git to enable automatic repository setup.'));
746
+ return;
747
+ }
748
+ require('child_process').execSync('git init', { stdio: 'inherit' });
749
+ require('child_process').execSync('git add .', { stdio: 'inherit' });
750
+ require('child_process').execSync('git commit -m "Initial CAWS project setup"', {
751
+ stdio: 'inherit',
752
+ });
753
+ console.log(chalk.green('āœ… Git repository initialized'));
754
+ // Update provenance with commit hash
755
+ const commitHash = require('child_process')
756
+ .execSync('git rev-parse HEAD', { encoding: 'utf8' })
757
+ .trim();
758
+ const currentProvenance = JSON.parse(fs.readFileSync('.agent/provenance.json', 'utf8'));
759
+ currentProvenance.commit = commitHash;
760
+ currentProvenance.hash = require('crypto')
761
+ .createHash('sha256')
762
+ .update(JSON.stringify(currentProvenance, Object.keys(currentProvenance).sort()))
763
+ .digest('hex');
764
+ await fs.writeFile('.agent/provenance.json', JSON.stringify(currentProvenance, null, 2));
765
+ console.log(chalk.green('āœ… Provenance updated with commit hash'));
766
+ }
767
+ catch (error) {
768
+ console.warn(chalk.yellow('āš ļø Failed to initialize git repository:'), error.message);
769
+ console.warn(chalk.blue('šŸ’” You can initialize git manually later with:'));
770
+ console.warn(" git init && git add . && git commit -m 'Initial CAWS project setup'");
771
+ }
772
+ }
773
+ }
774
+ catch (error) {
775
+ console.error(chalk.red('āŒ Error during project finalization:'), error.message);
776
+ }
777
+ }
778
+ function continueToSuccess() {
779
+ console.log(chalk.green('\nšŸŽ‰ Project initialized successfully!'));
780
+ console.log(`šŸ“ ${chalk.cyan('Project location')}: ${path.resolve(process.cwd())}`);
781
+ console.log(chalk.bold('\nNext steps:'));
782
+ console.log('1. Customize .caws/working-spec.yaml');
783
+ console.log('2. npm install (if using Node.js)');
784
+ console.log('3. Set up your CI/CD pipeline');
785
+ console.log(chalk.blue('\nFor help: caws --help'));
786
+ }
787
+ /**
788
+ * Scaffold existing project with CAWS components
789
+ */
790
+ async function scaffoldProject(options) {
791
+ const currentDir = process.cwd();
792
+ const projectName = path.basename(currentDir);
793
+ console.log(chalk.cyan(`šŸ”§ Scaffolding existing project: ${projectName}`));
794
+ try {
795
+ // Check if template directory exists
796
+ if (!fs.existsSync(TEMPLATE_DIR)) {
797
+ console.error(chalk.red('āŒ Template directory not found:'), TEMPLATE_DIR);
798
+ console.error(chalk.blue("šŸ’” Make sure you're running the CLI from the correct directory"));
799
+ process.exit(1);
800
+ }
801
+ // Generate provenance for scaffolding operation
802
+ const scaffoldProvenance = generateProvenance({
803
+ agent: 'caws-cli',
804
+ model: 'cli-scaffold',
805
+ modelHash: CLI_VERSION,
806
+ toolAllowlist: ['node', 'fs-extra'],
807
+ prompts: ['scaffold', options.force ? 'force' : 'normal'],
808
+ commit: null,
809
+ artifacts: [],
810
+ results: {
811
+ operation: 'scaffold',
812
+ force_mode: options.force,
813
+ target_directory: currentDir,
814
+ },
815
+ approvals: [],
816
+ });
817
+ // Copy missing CAWS components
818
+ const cawsFiles = ['.caws', 'apps/tools/caws', 'codemod', '.github/workflows/caws.yml'];
819
+ let addedCount = 0;
820
+ let skippedCount = 0;
821
+ const addedFiles = [];
822
+ for (const file of cawsFiles) {
823
+ const templatePath = path.join(TEMPLATE_DIR, file);
824
+ const destPath = path.join(currentDir, file);
825
+ if (!fs.existsSync(destPath)) {
826
+ if (fs.existsSync(templatePath)) {
827
+ try {
828
+ await fs.copy(templatePath, destPath);
829
+ console.log(chalk.green(`āœ… Added ${file}`));
830
+ addedCount++;
831
+ addedFiles.push(file);
832
+ }
833
+ catch (copyError) {
834
+ console.warn(chalk.yellow(`āš ļø Failed to copy ${file}:`), copyError.message);
835
+ }
836
+ }
837
+ else {
838
+ console.warn(chalk.yellow(`āš ļø Template not found for ${file}, skipping`));
839
+ }
840
+ }
841
+ else {
842
+ if (options.force) {
843
+ try {
844
+ await fs.remove(destPath);
845
+ await fs.copy(templatePath, destPath);
846
+ console.log(chalk.blue(`šŸ”„ Overwritten ${file}`));
847
+ addedCount++;
848
+ addedFiles.push(file);
849
+ }
850
+ catch (overwriteError) {
851
+ console.warn(chalk.yellow(`āš ļø Failed to overwrite ${file}:`), overwriteError.message);
852
+ }
853
+ }
854
+ else {
855
+ console.log(`ā­ļø Skipped ${file} (already exists)`);
856
+ skippedCount++;
857
+ }
858
+ }
859
+ }
860
+ // Update provenance with results
861
+ scaffoldProvenance.artifacts = addedFiles;
862
+ scaffoldProvenance.results.files_added = addedCount;
863
+ scaffoldProvenance.results.files_skipped = skippedCount;
864
+ console.log(chalk.green(`\nšŸŽ‰ Scaffolding completed!`));
865
+ console.log(chalk.bold(`šŸ“Š Summary: ${addedCount} added, ${skippedCount} skipped`));
866
+ if (addedCount > 0) {
867
+ console.log(chalk.bold('\nšŸ“ Next steps:'));
868
+ console.log('1. Review and customize the added files');
869
+ console.log('2. Update .caws/working-spec.yaml if needed');
870
+ console.log('3. Run tests to ensure everything works');
871
+ console.log('4. Set up your CI/CD pipeline');
872
+ }
873
+ if (options.force) {
874
+ console.log(chalk.yellow('\nāš ļø Force mode was used - review changes carefully'));
875
+ }
876
+ // Save provenance manifest
877
+ await saveProvenance(scaffoldProvenance, '.agent/scaffold-provenance.json');
878
+ console.log(chalk.green('āœ… Scaffolding provenance saved'));
879
+ }
880
+ catch (error) {
881
+ console.error(chalk.red('āŒ Error during scaffolding:'), error.message);
882
+ process.exit(1);
883
+ }
884
+ }
885
+ /**
886
+ * Show version information
887
+ */
888
+ function showVersion() {
889
+ console.log(chalk.bold(`CAWS CLI v${CLI_VERSION}`));
890
+ console.log(chalk.cyan('Coding Agent Workflow System - Scaffolding Tool'));
891
+ console.log(chalk.gray('Author: @darianrosebrook'));
892
+ console.log(chalk.gray('License: MIT'));
893
+ }
894
+ // CLI Commands
895
+ program
896
+ .name('caws')
897
+ .description('CAWS - Coding Agent Workflow System CLI')
898
+ .version(CLI_VERSION, '-v, --version', 'Show version information')
899
+ .action(() => showVersion());
900
+ program
901
+ .command('init')
902
+ .alias('i')
903
+ .description('Initialize a new project with CAWS')
904
+ .argument('<project-name>', 'Name of the new project')
905
+ .option('-i, --interactive', 'Run interactive setup', true)
906
+ .option('-g, --git', 'Initialize git repository', true)
907
+ .option('-n, --non-interactive', 'Skip interactive prompts')
908
+ .option('--no-git', "Don't initialize git repository")
909
+ .action(initProject);
910
+ program
911
+ .command('scaffold')
912
+ .alias('s')
913
+ .description('Add CAWS components to existing project')
914
+ .option('-f, --force', 'Overwrite existing files')
915
+ .action(scaffoldProject);
916
+ // Error handling
917
+ program.exitOverride((err) => {
918
+ if (err.code === 'commander.help' ||
919
+ err.code === 'commander.version' ||
920
+ err.message.includes('outputHelp')) {
921
+ process.exit(0);
922
+ }
923
+ console.error(chalk.red('āŒ Error:'), err.message);
924
+ process.exit(1);
925
+ });
926
+ // Parse and run
927
+ try {
928
+ program.parse();
929
+ }
930
+ catch (error) {
931
+ if (error.code === 'commander.help' ||
932
+ error.code === 'commander.version' ||
933
+ error.message.includes('outputHelp')) {
934
+ process.exit(0);
935
+ }
936
+ else {
937
+ console.error(chalk.red('āŒ Error:'), error.message);
938
+ process.exit(1);
939
+ }
940
+ }
941
+ // Export functions for testing
942
+ module.exports = {
943
+ generateWorkingSpec,
944
+ validateGeneratedSpec,
945
+ };
946
+ //# sourceMappingURL=index.js.map