@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/README.md +222 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +946 -0
- package/dist/index.js.map +1 -0
- package/dist/minimal-cli.d.ts +3 -0
- package/dist/minimal-cli.d.ts.map +1 -0
- package/dist/minimal-cli.js +76 -0
- package/dist/minimal-cli.js.map +1 -0
- package/dist/test-chalk.d.ts +3 -0
- package/dist/test-chalk.d.ts.map +1 -0
- package/dist/test-chalk.js +12 -0
- package/dist/test-chalk.js.map +1 -0
- package/package.json +58 -0
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
|