@paths.design/caws-cli 3.1.1 â 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.
- package/dist/commands/provenance.d.ts +10 -0
- package/dist/commands/provenance.d.ts.map +1 -1
- package/dist/commands/provenance.js +388 -3
- package/dist/index.js +113 -7
- package/dist/scaffold/git-hooks.d.ts +20 -0
- package/dist/scaffold/git-hooks.d.ts.map +1 -0
- package/dist/scaffold/git-hooks.js +417 -0
- package/dist/scaffold/index.d.ts.map +1 -1
- package/dist/scaffold/index.js +19 -19
- package/package.json +1 -1
- package/dist/index-new.d.ts +0 -5
- package/dist/index-new.d.ts.map +0 -1
- package/dist/index-new.js +0 -317
- package/dist/index.js.backup +0 -4711
- package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
- package/templates/apps/tools/caws/provenance.js.backup +0 -73
package/dist/index.js.backup
DELETED
|
@@ -1,4711 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @fileoverview CAWS CLI - Scaffolding tool for Coding Agent Workflow System
|
|
5
|
-
* Provides commands to initialize new projects and scaffold existing ones with CAWS
|
|
6
|
-
* @author @darianrosebrook
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { Command } = require('commander');
|
|
10
|
-
const fs = require('fs-extra');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const yaml = require('js-yaml');
|
|
13
|
-
const chalk = require('chalk');
|
|
14
|
-
|
|
15
|
-
// Import configuration and utilities
|
|
16
|
-
const {
|
|
17
|
-
CLI_VERSION,
|
|
18
|
-
initializeGlobalSetup,
|
|
19
|
-
loadProvenanceTools,
|
|
20
|
-
initializeLanguageSupport,
|
|
21
|
-
getGlobalCAWSSetup,
|
|
22
|
-
} = require('./config');
|
|
23
|
-
|
|
24
|
-
// Import command handlers
|
|
25
|
-
const { initProject } = require('./commands/init');
|
|
26
|
-
|
|
27
|
-
// Import scaffold functionality
|
|
28
|
-
const { scaffoldProject, setScaffoldDependencies } = require('./scaffold');
|
|
29
|
-
|
|
30
|
-
// Import validation functionality
|
|
31
|
-
const { validateWorkingSpecWithSuggestions } = require('./validation/spec-validation');
|
|
32
|
-
|
|
33
|
-
// Import finalization utilities
|
|
34
|
-
const { finalizeProject, continueToSuccess, setFinalizationDependencies } = require('./utils/finalization');
|
|
35
|
-
|
|
36
|
-
// Import generators
|
|
37
|
-
const { generateWorkingSpec, validateGeneratedSpec } = require('./generators/working-spec');
|
|
38
|
-
|
|
39
|
-
// Import tool system
|
|
40
|
-
const ToolLoader = require('./tool-loader');
|
|
41
|
-
const ToolValidator = require('./tool-validator');
|
|
42
|
-
|
|
43
|
-
// Initialize global configuration
|
|
44
|
-
const program = new Command();
|
|
45
|
-
|
|
46
|
-
// Initialize global state
|
|
47
|
-
const cawsSetup = initializeGlobalSetup();
|
|
48
|
-
const languageSupport = initializeLanguageSupport();
|
|
49
|
-
|
|
50
|
-
// Set up dependencies for modules that need them
|
|
51
|
-
setScaffoldDependencies({
|
|
52
|
-
cawsSetup,
|
|
53
|
-
loadProvenanceTools,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
setFinalizationDependencies({
|
|
57
|
-
languageSupport,
|
|
58
|
-
loadProvenanceTools,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Tool system state
|
|
62
|
-
let toolLoader = null;
|
|
63
|
-
let toolValidator = null;
|
|
64
|
-
|
|
65
|
-
// CAWS Detection and Configuration
|
|
66
|
-
function detectCAWSSetup(cwd = process.cwd()) {
|
|
67
|
-
// Skip logging for version/help commands
|
|
68
|
-
const isQuietCommand =
|
|
69
|
-
process.argv.includes('--version') ||
|
|
70
|
-
process.argv.includes('-V') ||
|
|
71
|
-
process.argv.includes('--help');
|
|
72
|
-
|
|
73
|
-
if (!isQuietCommand) {
|
|
74
|
-
console.log(chalk.blue('đ Detecting CAWS setup...'));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Check for existing CAWS setup
|
|
78
|
-
const cawsDir = path.join(cwd, '.caws');
|
|
79
|
-
const hasCAWSDir = fs.existsSync(cawsDir);
|
|
80
|
-
|
|
81
|
-
if (!hasCAWSDir) {
|
|
82
|
-
if (!isQuietCommand) {
|
|
83
|
-
console.log(chalk.gray('âšī¸ No .caws directory found - new project setup'));
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
type: 'new',
|
|
87
|
-
hasCAWSDir: false,
|
|
88
|
-
cawsDir: null,
|
|
89
|
-
capabilities: [],
|
|
90
|
-
hasTemplateDir: false,
|
|
91
|
-
templateDir: null,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Analyze existing setup
|
|
96
|
-
const files = fs.readdirSync(cawsDir);
|
|
97
|
-
const hasWorkingSpec = fs.existsSync(path.join(cawsDir, 'working-spec.yaml'));
|
|
98
|
-
const hasValidateScript = fs.existsSync(path.join(cawsDir, 'validate.js'));
|
|
99
|
-
const hasPolicy = fs.existsSync(path.join(cawsDir, 'policy'));
|
|
100
|
-
const hasSchemas = fs.existsSync(path.join(cawsDir, 'schemas'));
|
|
101
|
-
const hasTemplates = fs.existsSync(path.join(cawsDir, 'templates'));
|
|
102
|
-
|
|
103
|
-
// Check for multiple spec files (enhanced project pattern)
|
|
104
|
-
const specFiles = files.filter((f) => f.endsWith('-spec.yaml'));
|
|
105
|
-
const hasMultipleSpecs = specFiles.length > 1;
|
|
106
|
-
|
|
107
|
-
// Check for tools directory (enhanced setup)
|
|
108
|
-
const toolsDir = path.join(cwd, 'apps/tools/caws');
|
|
109
|
-
const hasTools = fs.existsSync(toolsDir);
|
|
110
|
-
|
|
111
|
-
// Determine setup type
|
|
112
|
-
let setupType = 'basic';
|
|
113
|
-
let capabilities = [];
|
|
114
|
-
|
|
115
|
-
if (hasMultipleSpecs && hasWorkingSpec) {
|
|
116
|
-
setupType = 'enhanced';
|
|
117
|
-
capabilities.push('multiple-specs', 'working-spec', 'domain-specific');
|
|
118
|
-
} else if (hasWorkingSpec) {
|
|
119
|
-
setupType = 'standard';
|
|
120
|
-
capabilities.push('working-spec');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (hasValidateScript) {
|
|
124
|
-
capabilities.push('validation');
|
|
125
|
-
}
|
|
126
|
-
if (hasPolicy) {
|
|
127
|
-
capabilities.push('policies');
|
|
128
|
-
}
|
|
129
|
-
if (hasSchemas) {
|
|
130
|
-
capabilities.push('schemas');
|
|
131
|
-
}
|
|
132
|
-
if (hasTemplates) {
|
|
133
|
-
capabilities.push('templates');
|
|
134
|
-
}
|
|
135
|
-
if (hasTools) {
|
|
136
|
-
capabilities.push('tools');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!isQuietCommand) {
|
|
140
|
-
console.log(chalk.green(`â
Detected ${setupType} CAWS setup`));
|
|
141
|
-
console.log(chalk.gray(` Capabilities: ${capabilities.join(', ')}`));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Check for template directory - try multiple possible locations
|
|
145
|
-
let templateDir = null;
|
|
146
|
-
const possibleTemplatePaths = [
|
|
147
|
-
// FIRST: Try bundled templates (for npm-installed CLI)
|
|
148
|
-
{ path: path.resolve(__dirname, '../templates'), source: 'bundled with CLI' },
|
|
149
|
-
{ path: path.resolve(__dirname, 'templates'), source: 'bundled with CLI (fallback)' },
|
|
150
|
-
// Try relative to current working directory (for monorepo setups)
|
|
151
|
-
{ path: path.resolve(cwd, '../caws-template'), source: 'monorepo parent directory' },
|
|
152
|
-
{ path: path.resolve(cwd, '../../caws-template'), source: 'monorepo grandparent' },
|
|
153
|
-
{ path: path.resolve(cwd, '../../../caws-template'), source: 'workspace root' },
|
|
154
|
-
{ path: path.resolve(cwd, 'packages/caws-template'), source: 'packages/ subdirectory' },
|
|
155
|
-
{ path: path.resolve(cwd, 'caws-template'), source: 'caws-template/ subdirectory' },
|
|
156
|
-
// Try relative to CLI location (for installed CLI)
|
|
157
|
-
{ path: path.resolve(__dirname, '../caws-template'), source: 'CLI installation' },
|
|
158
|
-
{ path: path.resolve(__dirname, '../../caws-template'), source: 'CLI parent directory' },
|
|
159
|
-
{ path: path.resolve(__dirname, '../../../caws-template'), source: 'CLI workspace root' },
|
|
160
|
-
// Try absolute paths for CI environments
|
|
161
|
-
{ path: path.resolve(process.cwd(), 'packages/caws-template'), source: 'current packages/' },
|
|
162
|
-
{ path: path.resolve(process.cwd(), '../packages/caws-template'), source: 'parent packages/' },
|
|
163
|
-
{
|
|
164
|
-
path: path.resolve(process.cwd(), '../../packages/caws-template'),
|
|
165
|
-
source: 'grandparent packages/',
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
path: path.resolve(process.cwd(), '../../../packages/caws-template'),
|
|
169
|
-
source: 'workspace packages/',
|
|
170
|
-
},
|
|
171
|
-
// Try from workspace root
|
|
172
|
-
{ path: path.resolve(process.cwd(), 'caws-template'), source: 'workspace caws-template/' },
|
|
173
|
-
// Try various other common locations
|
|
174
|
-
{
|
|
175
|
-
path: '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
|
|
176
|
-
source: 'GitHub Actions CI',
|
|
177
|
-
},
|
|
178
|
-
{ path: '/workspace/packages/caws-template', source: 'Docker workspace' },
|
|
179
|
-
{ path: '/caws/packages/caws-template', source: 'Container workspace' },
|
|
180
|
-
];
|
|
181
|
-
|
|
182
|
-
for (const { path: testPath, source } of possibleTemplatePaths) {
|
|
183
|
-
if (fs.existsSync(testPath)) {
|
|
184
|
-
templateDir = testPath;
|
|
185
|
-
if (!isQuietCommand) {
|
|
186
|
-
console.log(`â
Found CAWS templates in ${source}:`);
|
|
187
|
-
console.log(` ${chalk.gray(testPath)}`);
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!templateDir && !isQuietCommand) {
|
|
194
|
-
console.warn(chalk.yellow('â ī¸ CAWS templates not found in standard locations'));
|
|
195
|
-
console.warn(chalk.blue('đĄ This may limit available scaffolding features'));
|
|
196
|
-
console.warn(
|
|
197
|
-
chalk.blue('đĄ For full functionality, ensure caws-template package is available')
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const hasTemplateDir = templateDir !== null;
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
type: setupType,
|
|
205
|
-
hasCAWSDir: true,
|
|
206
|
-
cawsDir,
|
|
207
|
-
hasWorkingSpec,
|
|
208
|
-
hasMultipleSpecs,
|
|
209
|
-
hasValidateScript,
|
|
210
|
-
hasPolicy,
|
|
211
|
-
hasSchemas,
|
|
212
|
-
hasTemplates,
|
|
213
|
-
hasTools,
|
|
214
|
-
hasTemplateDir,
|
|
215
|
-
templateDir,
|
|
216
|
-
capabilities,
|
|
217
|
-
isEnhanced: setupType === 'enhanced',
|
|
218
|
-
isAdvanced: hasTools || hasValidateScript,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let cawsSetup = null;
|
|
223
|
-
|
|
224
|
-
// Initialize global setup detection
|
|
225
|
-
try {
|
|
226
|
-
cawsSetup = detectCAWSSetup();
|
|
227
|
-
|
|
228
|
-
// If no template dir found in current directory, check CLI installation location
|
|
229
|
-
if (!cawsSetup.hasTemplateDir) {
|
|
230
|
-
const cliTemplatePaths = [
|
|
231
|
-
path.resolve(__dirname, '../templates'),
|
|
232
|
-
path.resolve(__dirname, 'templates'),
|
|
233
|
-
];
|
|
234
|
-
|
|
235
|
-
for (const testPath of cliTemplatePaths) {
|
|
236
|
-
if (fs.existsSync(testPath)) {
|
|
237
|
-
cawsSetup.templateDir = testPath;
|
|
238
|
-
cawsSetup.hasTemplateDir = true;
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.warn('â ī¸ Failed to detect CAWS setup globally:', error.message);
|
|
245
|
-
cawsSetup = {
|
|
246
|
-
type: 'unknown',
|
|
247
|
-
hasCAWSDir: false,
|
|
248
|
-
cawsDir: null,
|
|
249
|
-
hasWorkingSpec: false,
|
|
250
|
-
hasMultipleSpecs: false,
|
|
251
|
-
hasValidateScript: false,
|
|
252
|
-
hasPolicy: false,
|
|
253
|
-
hasSchemas: false,
|
|
254
|
-
hasTemplates: false,
|
|
255
|
-
hasTools: false,
|
|
256
|
-
hasTemplateDir: false,
|
|
257
|
-
templateDir: null,
|
|
258
|
-
capabilities: [],
|
|
259
|
-
isEnhanced: false,
|
|
260
|
-
isAdvanced: false,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Dynamic imports based on setup
|
|
265
|
-
let provenanceTools = null;
|
|
266
|
-
|
|
267
|
-
// Function to load provenance tools dynamically
|
|
268
|
-
function loadProvenanceTools() {
|
|
269
|
-
if (provenanceTools) return provenanceTools; // Already loaded
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
const setup = detectCAWSSetup();
|
|
273
|
-
if (setup?.hasTemplateDir && setup?.templateDir) {
|
|
274
|
-
const { generateProvenance, saveProvenance } = require(
|
|
275
|
-
path.join(setup.templateDir, 'apps/tools/caws/provenance.js')
|
|
276
|
-
);
|
|
277
|
-
provenanceTools = { generateProvenance, saveProvenance };
|
|
278
|
-
console.log('â
Loaded provenance tools from:', setup.templateDir);
|
|
279
|
-
}
|
|
280
|
-
} catch (error) {
|
|
281
|
-
// Fallback for environments without template
|
|
282
|
-
provenanceTools = null;
|
|
283
|
-
console.warn('â ī¸ Provenance tools not available:', error.message);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return provenanceTools;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const CLI_VERSION = require('../package.json').version;
|
|
290
|
-
|
|
291
|
-
// Initialize JSON Schema validator - using simplified validation for CLI stability
|
|
292
|
-
const validateWorkingSpec = (spec) => {
|
|
293
|
-
try {
|
|
294
|
-
// Basic structural validation for essential fields
|
|
295
|
-
const requiredFields = [
|
|
296
|
-
'id',
|
|
297
|
-
'title',
|
|
298
|
-
'risk_tier',
|
|
299
|
-
'mode',
|
|
300
|
-
'change_budget',
|
|
301
|
-
'blast_radius',
|
|
302
|
-
'operational_rollback_slo',
|
|
303
|
-
'scope',
|
|
304
|
-
'invariants',
|
|
305
|
-
'acceptance',
|
|
306
|
-
'non_functional',
|
|
307
|
-
'contracts',
|
|
308
|
-
];
|
|
309
|
-
|
|
310
|
-
for (const field of requiredFields) {
|
|
311
|
-
if (!spec[field]) {
|
|
312
|
-
return {
|
|
313
|
-
valid: false,
|
|
314
|
-
errors: [
|
|
315
|
-
{
|
|
316
|
-
instancePath: `/${field}`,
|
|
317
|
-
message: `Missing required field: ${field}`,
|
|
318
|
-
},
|
|
319
|
-
],
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Validate specific field formats
|
|
325
|
-
if (!/^[A-Z]+-\d+$/.test(spec.id)) {
|
|
326
|
-
return {
|
|
327
|
-
valid: false,
|
|
328
|
-
errors: [
|
|
329
|
-
{
|
|
330
|
-
instancePath: '/id',
|
|
331
|
-
message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
|
|
332
|
-
},
|
|
333
|
-
],
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Validate experimental mode
|
|
338
|
-
if (spec.experimental_mode) {
|
|
339
|
-
if (typeof spec.experimental_mode !== 'object') {
|
|
340
|
-
return {
|
|
341
|
-
valid: false,
|
|
342
|
-
errors: [
|
|
343
|
-
{
|
|
344
|
-
instancePath: '/experimental_mode',
|
|
345
|
-
message:
|
|
346
|
-
'Experimental mode must be an object with enabled, rationale, and expires_at fields',
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
|
|
353
|
-
for (const field of requiredExpFields) {
|
|
354
|
-
if (!(field in spec.experimental_mode)) {
|
|
355
|
-
return {
|
|
356
|
-
valid: false,
|
|
357
|
-
errors: [
|
|
358
|
-
{
|
|
359
|
-
instancePath: `/experimental_mode/${field}`,
|
|
360
|
-
message: `Missing required experimental mode field: ${field}`,
|
|
361
|
-
},
|
|
362
|
-
],
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
|
|
368
|
-
return {
|
|
369
|
-
valid: false,
|
|
370
|
-
errors: [
|
|
371
|
-
{
|
|
372
|
-
instancePath: '/experimental_mode',
|
|
373
|
-
message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (spec.risk_tier < 1 || spec.risk_tier > 3) {
|
|
381
|
-
return {
|
|
382
|
-
valid: false,
|
|
383
|
-
errors: [
|
|
384
|
-
{
|
|
385
|
-
instancePath: '/risk_tier',
|
|
386
|
-
message: 'Risk tier must be 1, 2, or 3',
|
|
387
|
-
},
|
|
388
|
-
],
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
|
|
393
|
-
return {
|
|
394
|
-
valid: false,
|
|
395
|
-
errors: [
|
|
396
|
-
{
|
|
397
|
-
instancePath: '/scope/in',
|
|
398
|
-
message: 'Scope IN must not be empty',
|
|
399
|
-
},
|
|
400
|
-
],
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return { valid: true };
|
|
405
|
-
} catch (error) {
|
|
406
|
-
return {
|
|
407
|
-
valid: false,
|
|
408
|
-
errors: [
|
|
409
|
-
{
|
|
410
|
-
instancePath: '',
|
|
411
|
-
message: `Validation error: ${error.message}`,
|
|
412
|
-
},
|
|
413
|
-
],
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Enhanced validation with suggestions and auto-fix
|
|
420
|
-
*/
|
|
421
|
-
function validateWorkingSpecWithSuggestions(spec, options = {}) {
|
|
422
|
-
const { autoFix = false, suggestions = true } = options;
|
|
423
|
-
|
|
424
|
-
try {
|
|
425
|
-
// Basic structural validation for essential fields
|
|
426
|
-
const requiredFields = [
|
|
427
|
-
'id',
|
|
428
|
-
'title',
|
|
429
|
-
'risk_tier',
|
|
430
|
-
'mode',
|
|
431
|
-
'change_budget',
|
|
432
|
-
'blast_radius',
|
|
433
|
-
'operational_rollback_slo',
|
|
434
|
-
'scope',
|
|
435
|
-
'invariants',
|
|
436
|
-
'acceptance',
|
|
437
|
-
'non_functional',
|
|
438
|
-
'contracts',
|
|
439
|
-
];
|
|
440
|
-
|
|
441
|
-
let errors = [];
|
|
442
|
-
let warnings = [];
|
|
443
|
-
let fixes = [];
|
|
444
|
-
|
|
445
|
-
for (const field of requiredFields) {
|
|
446
|
-
if (!spec[field]) {
|
|
447
|
-
errors.push({
|
|
448
|
-
instancePath: `/${field}`,
|
|
449
|
-
message: `Missing required field: ${field}`,
|
|
450
|
-
suggestion: getFieldSuggestion(field, spec),
|
|
451
|
-
canAutoFix: canAutoFixField(field, spec),
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Validate specific field formats
|
|
457
|
-
if (spec.id && !/^[A-Z]+-\d+$/.test(spec.id)) {
|
|
458
|
-
errors.push({
|
|
459
|
-
instancePath: '/id',
|
|
460
|
-
message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
|
|
461
|
-
suggestion: 'Use format like: PROJ-001, FEAT-002, FIX-003',
|
|
462
|
-
canAutoFix: false,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Validate risk tier
|
|
467
|
-
if (spec.risk_tier !== undefined && (spec.risk_tier < 1 || spec.risk_tier > 3)) {
|
|
468
|
-
errors.push({
|
|
469
|
-
instancePath: '/risk_tier',
|
|
470
|
-
message: 'Risk tier must be 1, 2, or 3',
|
|
471
|
-
suggestion:
|
|
472
|
-
'Tier 1: Critical (auth, billing), Tier 2: Standard (features), Tier 3: Low risk (UI)',
|
|
473
|
-
canAutoFix: true,
|
|
474
|
-
});
|
|
475
|
-
fixes.push({ field: 'risk_tier', value: Math.max(1, Math.min(3, spec.risk_tier || 2)) });
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Validate scope.in is not empty
|
|
479
|
-
if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
|
|
480
|
-
errors.push({
|
|
481
|
-
instancePath: '/scope/in',
|
|
482
|
-
message: 'Scope IN must not be empty',
|
|
483
|
-
suggestion: 'Specify directories/files that are included in changes',
|
|
484
|
-
canAutoFix: false,
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Check for common issues
|
|
489
|
-
if (!spec.invariants || spec.invariants.length === 0) {
|
|
490
|
-
warnings.push({
|
|
491
|
-
instancePath: '/invariants',
|
|
492
|
-
message: 'No system invariants defined',
|
|
493
|
-
suggestion: 'Add 1-3 statements about what must always remain true',
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
if (!spec.acceptance || spec.acceptance.length === 0) {
|
|
498
|
-
warnings.push({
|
|
499
|
-
instancePath: '/acceptance',
|
|
500
|
-
message: 'No acceptance criteria defined',
|
|
501
|
-
suggestion: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Validate experimental mode
|
|
506
|
-
if (spec.experimental_mode) {
|
|
507
|
-
if (typeof spec.experimental_mode !== 'object') {
|
|
508
|
-
errors.push({
|
|
509
|
-
instancePath: '/experimental_mode',
|
|
510
|
-
message:
|
|
511
|
-
'Experimental mode must be an object with enabled, rationale, and expires_at fields',
|
|
512
|
-
suggestion: 'Fix experimental_mode structure',
|
|
513
|
-
canAutoFix: false,
|
|
514
|
-
});
|
|
515
|
-
} else {
|
|
516
|
-
const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
|
|
517
|
-
for (const field of requiredExpFields) {
|
|
518
|
-
if (!(field in spec.experimental_mode)) {
|
|
519
|
-
errors.push({
|
|
520
|
-
instancePath: `/experimental_mode/${field}`,
|
|
521
|
-
message: `Missing required experimental mode field: ${field}`,
|
|
522
|
-
suggestion: `Add ${field} to experimental_mode`,
|
|
523
|
-
canAutoFix: field === 'enabled' ? true : false,
|
|
524
|
-
});
|
|
525
|
-
if (field === 'enabled') {
|
|
526
|
-
fixes.push({ field: `experimental_mode.${field}`, value: true });
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
|
|
532
|
-
warnings.push({
|
|
533
|
-
instancePath: '/experimental_mode',
|
|
534
|
-
message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
|
|
535
|
-
suggestion: 'Either set risk_tier to 3 or disable experimental mode',
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Apply auto-fixes if requested
|
|
542
|
-
if (autoFix && fixes.length > 0) {
|
|
543
|
-
console.log(chalk.cyan('đ§ Applying auto-fixes...'));
|
|
544
|
-
for (const fix of fixes) {
|
|
545
|
-
if (fix.field.includes('.')) {
|
|
546
|
-
const [parent, child] = fix.field.split('.');
|
|
547
|
-
if (!spec[parent]) spec[parent] = {};
|
|
548
|
-
spec[parent][child] = fix.value;
|
|
549
|
-
} else {
|
|
550
|
-
spec[fix.field] = fix.value;
|
|
551
|
-
}
|
|
552
|
-
console.log(` Fixed: ${fix.field} = ${JSON.stringify(fix.value)}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Display results
|
|
557
|
-
if (errors.length > 0) {
|
|
558
|
-
console.error(chalk.red('â Validation failed with errors:'));
|
|
559
|
-
errors.forEach((error, index) => {
|
|
560
|
-
console.error(`${index + 1}. ${error.instancePath || 'root'}: ${error.message}`);
|
|
561
|
-
if (suggestions && error.suggestion) {
|
|
562
|
-
console.error(` đĄ ${error.suggestion}`);
|
|
563
|
-
}
|
|
564
|
-
if (error.canAutoFix) {
|
|
565
|
-
console.error(` đ§ Can auto-fix: ${autoFix ? 'applied' : 'run with --auto-fix'}`);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
return { valid: false, errors, warnings };
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (warnings.length > 0 && suggestions) {
|
|
572
|
-
console.warn(chalk.yellow('â ī¸ Validation passed with warnings:'));
|
|
573
|
-
warnings.forEach((warning, index) => {
|
|
574
|
-
console.warn(`${index + 1}. ${warning.instancePath || 'root'}: ${warning.message}`);
|
|
575
|
-
if (warning.suggestion) {
|
|
576
|
-
console.warn(` đĄ ${warning.suggestion}`);
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
console.log(chalk.green('â
Working specification is valid'));
|
|
582
|
-
return { valid: true, errors: [], warnings };
|
|
583
|
-
} catch (error) {
|
|
584
|
-
console.error(chalk.red('â Error during validation:'), error.message);
|
|
585
|
-
return {
|
|
586
|
-
valid: false,
|
|
587
|
-
errors: [{ instancePath: '', message: `Validation error: ${error.message}` }],
|
|
588
|
-
warnings: [],
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function getFieldSuggestion(field, _spec) {
|
|
594
|
-
const suggestions = {
|
|
595
|
-
id: 'Use format like: PROJ-001, FEAT-002, FIX-003',
|
|
596
|
-
title: 'Add a descriptive project title',
|
|
597
|
-
risk_tier: 'Choose: 1 (critical), 2 (standard), or 3 (low risk)',
|
|
598
|
-
mode: 'Choose: feature, refactor, fix, doc, or chore',
|
|
599
|
-
change_budget: 'Set max_files and max_loc based on risk tier',
|
|
600
|
-
blast_radius: 'List affected modules and data migration needs',
|
|
601
|
-
operational_rollback_slo: 'Choose: 1m, 5m, 15m, or 1h',
|
|
602
|
-
scope: "Define what's included (in) and excluded (out) from changes",
|
|
603
|
-
invariants: 'Add 1-3 statements about what must always remain true',
|
|
604
|
-
acceptance: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
|
|
605
|
-
non_functional: 'Define accessibility, performance, and security requirements',
|
|
606
|
-
contracts: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
|
|
607
|
-
};
|
|
608
|
-
return suggestions[field] || `Add the ${field} field`;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
function canAutoFixField(field, _spec) {
|
|
612
|
-
const autoFixable = ['risk_tier'];
|
|
613
|
-
return autoFixable.includes(field);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Generate a getting started guide based on project analysis
|
|
618
|
-
*/
|
|
619
|
-
function generateGettingStartedGuide(analysis) {
|
|
620
|
-
const { projectType, packageJson, hasTests, hasLinting } = analysis;
|
|
621
|
-
|
|
622
|
-
const projectName = packageJson.name || 'your-project';
|
|
623
|
-
const capitalizedType = projectType.charAt(0).toUpperCase() + projectType.slice(1);
|
|
624
|
-
|
|
625
|
-
let guide = `# Getting Started with CAWS - ${capitalizedType} Project
|
|
626
|
-
|
|
627
|
-
**Project**: ${projectName}
|
|
628
|
-
**Type**: ${capitalizedType}
|
|
629
|
-
**Generated**: ${new Date().toLocaleDateString()}
|
|
630
|
-
|
|
631
|
-
---
|
|
632
|
-
|
|
633
|
-
## Phase 1: Setup Verification (15 mins)
|
|
634
|
-
|
|
635
|
-
Complete these steps to ensure your CAWS setup is working:
|
|
636
|
-
|
|
637
|
-
### â
Already Done
|
|
638
|
-
- [x] Initialize CAWS project
|
|
639
|
-
- [x] Generate working spec
|
|
640
|
-
- [x] Set up basic structure
|
|
641
|
-
|
|
642
|
-
### Next Steps
|
|
643
|
-
- [ ] Review \`.caws/working-spec.yaml\` - customize for your needs
|
|
644
|
-
- [ ] Run validation: \`caws validate --suggestions\`
|
|
645
|
-
- [ ] Review tier policy in \`.caws/policy/\` (if applicable)
|
|
646
|
-
- [ ] Update \`.caws/templates/\` with project-specific examples
|
|
647
|
-
|
|
648
|
-
---
|
|
649
|
-
|
|
650
|
-
## Phase 2: First Feature (30 mins)
|
|
651
|
-
|
|
652
|
-
Time to create your first CAWS-managed feature:
|
|
653
|
-
|
|
654
|
-
### Steps
|
|
655
|
-
1. **Copy a template**:
|
|
656
|
-
\`\`\`bash
|
|
657
|
-
cp .caws/templates/feature.plan.md docs/plans/FEATURE-001.md
|
|
658
|
-
\`\`\`
|
|
659
|
-
|
|
660
|
-
2. **Customize the plan**:
|
|
661
|
-
- Update title and description
|
|
662
|
-
- Fill in acceptance criteria (GIVEN/WHEN/THEN format)
|
|
663
|
-
- Set appropriate risk tier
|
|
664
|
-
- Define scope and invariants
|
|
665
|
-
|
|
666
|
-
3. **Write tests first** (TDD approach):
|
|
667
|
-
\`\`\`bash
|
|
668
|
-
# For ${projectType} projects, focus on:
|
|
669
|
-
${getTestingGuidance(projectType)}
|
|
670
|
-
\`\`\`
|
|
671
|
-
|
|
672
|
-
4. **Implement the feature**:
|
|
673
|
-
- Stay within change budget limits
|
|
674
|
-
- Follow acceptance criteria
|
|
675
|
-
- Maintain system invariants
|
|
676
|
-
|
|
677
|
-
5. **Run full verification**:
|
|
678
|
-
\`\`\`bash
|
|
679
|
-
caws validate --suggestions
|
|
680
|
-
${hasTests ? 'npm test' : '# Add tests when ready'}
|
|
681
|
-
${hasLinting ? 'npm run lint' : '# Add linting when ready'}
|
|
682
|
-
\`\`\`
|
|
683
|
-
|
|
684
|
-
---
|
|
685
|
-
|
|
686
|
-
## Phase 3: CI/CD Setup (20 mins)
|
|
687
|
-
|
|
688
|
-
Set up automated quality gates:
|
|
689
|
-
|
|
690
|
-
### GitHub Actions (Recommended)
|
|
691
|
-
1. **Create workflow**: \`.github/workflows/caws.yml\`
|
|
692
|
-
\`\`\`yaml
|
|
693
|
-
name: CAWS Quality Gates
|
|
694
|
-
on: [pull_request]
|
|
695
|
-
|
|
696
|
-
jobs:
|
|
697
|
-
validate:
|
|
698
|
-
runs-on: ubuntu-latest
|
|
699
|
-
steps:
|
|
700
|
-
- uses: actions/checkout@v4
|
|
701
|
-
- uses: actions/setup-node@v4
|
|
702
|
-
with:
|
|
703
|
-
node-version: '18'
|
|
704
|
-
- run: npm ci
|
|
705
|
-
- run: npx caws validate --quiet
|
|
706
|
-
- run: npm test # Add when ready
|
|
707
|
-
\`\`\`
|
|
708
|
-
|
|
709
|
-
2. **Configure branch protection**:
|
|
710
|
-
- Require PR validation
|
|
711
|
-
- Require tests to pass
|
|
712
|
-
- Require CAWS spec validation
|
|
713
|
-
|
|
714
|
-
### Other CI Systems
|
|
715
|
-
- **GitLab CI**: Use \`caws validate\` in \`.gitlab-ci.yml\`
|
|
716
|
-
- **Jenkins**: Add validation step to pipeline
|
|
717
|
-
- **CircleCI**: Include in \`.circleci/config.yml\`
|
|
718
|
-
|
|
719
|
-
---
|
|
720
|
-
|
|
721
|
-
## Phase 4: Team Onboarding (ongoing)
|
|
722
|
-
|
|
723
|
-
### For Team Members
|
|
724
|
-
1. **Read the basics**: Start with this guide
|
|
725
|
-
2. **Learn by example**: Review completed features
|
|
726
|
-
3. **Practice**: Create small features following the process
|
|
727
|
-
4. **Contribute**: Help improve templates and processes
|
|
728
|
-
|
|
729
|
-
### For Project Leads
|
|
730
|
-
1. **Customize templates**: Adapt to team preferences
|
|
731
|
-
2. **Set standards**: Define project-specific conventions
|
|
732
|
-
3. **Monitor quality**: Review metrics and adjust gates
|
|
733
|
-
4. **Scale practices**: Apply CAWS to more complex work
|
|
734
|
-
|
|
735
|
-
---
|
|
736
|
-
|
|
737
|
-
## Key Concepts Quick Reference
|
|
738
|
-
|
|
739
|
-
### Risk Tiers
|
|
740
|
-
- **Tier 1**: Critical (auth, billing, migrations) - Max rigor
|
|
741
|
-
- **Tier 2**: Standard (features, APIs) - Standard rigor
|
|
742
|
-
- **Tier 3**: Low risk (UI, tooling) - Basic rigor
|
|
743
|
-
|
|
744
|
-
### Change Budget
|
|
745
|
-
- Limits help maintain quality and reviewability
|
|
746
|
-
- Adjust based on risk tier and team experience
|
|
747
|
-
- Track actual vs. budgeted changes
|
|
748
|
-
|
|
749
|
-
### System Invariants
|
|
750
|
-
- Core guarantees that must always hold true
|
|
751
|
-
- Examples: "Data integrity maintained", "API contracts honored"
|
|
752
|
-
- Define 2-4 key invariants for your system
|
|
753
|
-
|
|
754
|
-
### Acceptance Criteria
|
|
755
|
-
- Use GIVEN/WHEN/THEN format
|
|
756
|
-
- Focus on observable behavior
|
|
757
|
-
- Include edge cases and error conditions
|
|
758
|
-
|
|
759
|
-
---
|
|
760
|
-
|
|
761
|
-
## Common Pitfalls to Avoid
|
|
762
|
-
|
|
763
|
-
### For ${capitalizedType} Projects
|
|
764
|
-
${getProjectSpecificPitfalls(projectType)}
|
|
765
|
-
|
|
766
|
-
### General Issues
|
|
767
|
-
1. **Over-customization**: Start with defaults, customize gradually
|
|
768
|
-
2. **Missing invariants**: Define what must never break
|
|
769
|
-
3. **Vague acceptance**: Make criteria measurable and testable
|
|
770
|
-
4. **Large changes**: Break big features into smaller, reviewable pieces
|
|
771
|
-
|
|
772
|
-
---
|
|
773
|
-
|
|
774
|
-
## Resources
|
|
775
|
-
|
|
776
|
-
### Documentation
|
|
777
|
-
- **Quick Reference**: This guide
|
|
778
|
-
- **Templates**: \`.caws/templates/\`
|
|
779
|
-
- **Examples**: \`.caws/examples/\` (when available)
|
|
780
|
-
|
|
781
|
-
### Commands
|
|
782
|
-
- \`caws validate --suggestions\` - Get help with issues
|
|
783
|
-
- \`caws validate --auto-fix\` - Fix safe problems automatically
|
|
784
|
-
- \`caws init --interactive\` - Customize existing setup
|
|
785
|
-
|
|
786
|
-
### Community
|
|
787
|
-
- **GitHub Issues**: Report problems and request features
|
|
788
|
-
- **Discussions**: Share experiences and best practices
|
|
789
|
-
- **Wiki**: Growing collection of examples and guides
|
|
790
|
-
|
|
791
|
-
---
|
|
792
|
-
|
|
793
|
-
## Next Steps
|
|
794
|
-
|
|
795
|
-
1. **Right now**: Review your working spec and customize it
|
|
796
|
-
2. **Today**: Create your first feature plan
|
|
797
|
-
3. **This week**: Set up CI/CD and branch protection
|
|
798
|
-
4. **Ongoing**: Refine processes based on team feedback
|
|
799
|
-
|
|
800
|
-
Remember: CAWS is a framework, not a straightjacket. Adapt it to your team's needs while maintaining the core principles of determinism and quality.
|
|
801
|
-
|
|
802
|
-
**Happy coding! đ¯**
|
|
803
|
-
`;
|
|
804
|
-
|
|
805
|
-
return guide;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function getTestingGuidance(projectType) {
|
|
809
|
-
const guidance = {
|
|
810
|
-
extension: `- Webview rendering tests\n- Command registration tests\n- Extension activation tests`,
|
|
811
|
-
library: `- Component rendering tests\n- API function tests\n- Type export tests`,
|
|
812
|
-
api: `- Endpoint response tests\n- Error handling tests\n- Authentication tests`,
|
|
813
|
-
cli: `- Command parsing tests\n- Output formatting tests\n- Error code tests`,
|
|
814
|
-
monorepo: `- Cross-package integration tests\n- Shared module tests\n- Build pipeline tests`,
|
|
815
|
-
application: `- User interaction tests\n- State management tests\n- Integration tests`,
|
|
816
|
-
};
|
|
817
|
-
return (
|
|
818
|
-
guidance[projectType] || `- Unit tests for core functions\n- Integration tests for workflows`
|
|
819
|
-
);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
function getProjectSpecificPitfalls(projectType) {
|
|
823
|
-
const pitfalls = {
|
|
824
|
-
extension: `1. **Webview security**: Never use \`vscode.executeCommand\` from untrusted content
|
|
825
|
-
2. **Activation timing**: Test cold start performance
|
|
826
|
-
3. **API compatibility**: Check VS Code API version compatibility`,
|
|
827
|
-
library: `1. **Bundle size**: Monitor and limit package size
|
|
828
|
-
2. **Type exports**: Ensure all public APIs are typed
|
|
829
|
-
3. **Peer dependencies**: Handle React/Angular versions carefully`,
|
|
830
|
-
api: `1. **Backward compatibility**: Version APIs carefully
|
|
831
|
-
2. **Rate limiting**: Test and document limits
|
|
832
|
-
3. **Data validation**: Validate all inputs thoroughly`,
|
|
833
|
-
cli: `1. **Exit codes**: Use standard codes (0=success, 1=error)
|
|
834
|
-
2. **Help text**: Keep it concise and helpful
|
|
835
|
-
3. **Error messages**: Make them actionable`,
|
|
836
|
-
monorepo: `1. **Dependency cycles**: Avoid circular imports
|
|
837
|
-
2. **Version consistency**: Keep package versions aligned
|
|
838
|
-
3. **Build order**: Ensure correct build dependencies`,
|
|
839
|
-
application: `1. **State consistency**: Prevent invalid state transitions
|
|
840
|
-
2. **Performance**: Monitor and optimize critical paths
|
|
841
|
-
3. **Accessibility**: Test with screen readers and keyboard navigation`,
|
|
842
|
-
};
|
|
843
|
-
return (
|
|
844
|
-
pitfalls[projectType] ||
|
|
845
|
-
`1. **Test coverage**: Maintain adequate test coverage
|
|
846
|
-
2. **Documentation**: Keep code and APIs documented
|
|
847
|
-
3. **Dependencies**: Review and update regularly`
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
/**
|
|
852
|
-
* Generate smart .gitignore patterns for CAWS projects
|
|
853
|
-
*/
|
|
854
|
-
function generateGitignorePatterns(existingGitignore = '') {
|
|
855
|
-
const cawsPatterns = `
|
|
856
|
-
# CAWS Configuration (tracked - these should be versioned)
|
|
857
|
-
# Note: .caws/ and .agent/ are tracked for provenance
|
|
858
|
-
# But we exclude temporary/generated files:
|
|
859
|
-
|
|
860
|
-
# CAWS temporary files (ignored)
|
|
861
|
-
.agent/temp/
|
|
862
|
-
.agent/cache/
|
|
863
|
-
.caws/.cache/
|
|
864
|
-
.caws/tmp/
|
|
865
|
-
|
|
866
|
-
# Build outputs (common patterns)
|
|
867
|
-
dist/
|
|
868
|
-
build/
|
|
869
|
-
*.tsbuildinfo
|
|
870
|
-
.next/
|
|
871
|
-
.nuxt/
|
|
872
|
-
.vite/
|
|
873
|
-
|
|
874
|
-
# Dependencies
|
|
875
|
-
node_modules/
|
|
876
|
-
.pnpm-store/
|
|
877
|
-
|
|
878
|
-
# Environment files
|
|
879
|
-
.env
|
|
880
|
-
.env.local
|
|
881
|
-
.env.development.local
|
|
882
|
-
.env.test.local
|
|
883
|
-
.env.production.local
|
|
884
|
-
|
|
885
|
-
# IDE files
|
|
886
|
-
.vscode/settings.json
|
|
887
|
-
.idea/
|
|
888
|
-
*.swp
|
|
889
|
-
*.swo
|
|
890
|
-
|
|
891
|
-
# OS files
|
|
892
|
-
.DS_Store
|
|
893
|
-
Thumbs.db
|
|
894
|
-
|
|
895
|
-
# Logs
|
|
896
|
-
logs/
|
|
897
|
-
*.log
|
|
898
|
-
npm-debug.log*
|
|
899
|
-
yarn-debug.log*
|
|
900
|
-
yarn-error.log*
|
|
901
|
-
|
|
902
|
-
# Coverage reports
|
|
903
|
-
coverage/
|
|
904
|
-
.nyc_output/
|
|
905
|
-
|
|
906
|
-
# Test results
|
|
907
|
-
test-results/
|
|
908
|
-
playwright-report/
|
|
909
|
-
`;
|
|
910
|
-
|
|
911
|
-
// If there's an existing .gitignore, merge intelligently
|
|
912
|
-
if (existingGitignore.trim()) {
|
|
913
|
-
// Check if CAWS patterns are already present
|
|
914
|
-
if (existingGitignore.includes('# CAWS Configuration')) {
|
|
915
|
-
console.log(chalk.blue('âšī¸ .gitignore already contains CAWS patterns - skipping'));
|
|
916
|
-
return existingGitignore;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Append CAWS patterns to existing .gitignore
|
|
920
|
-
return existingGitignore.trim() + '\n\n' + cawsPatterns.trim() + '\n';
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
return cawsPatterns.trim();
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
// Only log schema validation if not running quiet commands
|
|
927
|
-
if (!process.argv.includes('--version') && !process.argv.includes('-V')) {
|
|
928
|
-
console.log(chalk.green('â
Schema validation initialized successfully'));
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
/**
|
|
932
|
-
* Generate working spec YAML with user input
|
|
933
|
-
* @param {Object} answers - User responses
|
|
934
|
-
* @returns {string} - Generated YAML content
|
|
935
|
-
*/
|
|
936
|
-
function generateWorkingSpec(answers) {
|
|
937
|
-
const template = {
|
|
938
|
-
id: answers.projectId,
|
|
939
|
-
title: answers.projectTitle,
|
|
940
|
-
risk_tier: answers.riskTier,
|
|
941
|
-
mode: answers.projectMode,
|
|
942
|
-
change_budget: {
|
|
943
|
-
max_files: answers.maxFiles,
|
|
944
|
-
max_loc: answers.maxLoc,
|
|
945
|
-
},
|
|
946
|
-
blast_radius: {
|
|
947
|
-
modules: answers.blastModules
|
|
948
|
-
.split(',')
|
|
949
|
-
.map((m) => m.trim())
|
|
950
|
-
.filter((m) => m),
|
|
951
|
-
data_migration: answers.dataMigration,
|
|
952
|
-
},
|
|
953
|
-
operational_rollback_slo: answers.rollbackSlo,
|
|
954
|
-
threats: (answers.projectThreats || '')
|
|
955
|
-
.split('\n')
|
|
956
|
-
.map((t) => t.trim())
|
|
957
|
-
.filter((t) => t && !t.startsWith('-') === false), // Allow lines starting with -
|
|
958
|
-
scope: {
|
|
959
|
-
in: (answers.scopeIn || '')
|
|
960
|
-
.split(',')
|
|
961
|
-
.map((s) => s.trim())
|
|
962
|
-
.filter((s) => s),
|
|
963
|
-
out: (answers.scopeOut || '')
|
|
964
|
-
.split(',')
|
|
965
|
-
.map((s) => s.trim())
|
|
966
|
-
.filter((s) => s),
|
|
967
|
-
},
|
|
968
|
-
invariants: (answers.projectInvariants || '')
|
|
969
|
-
.split('\n')
|
|
970
|
-
.map((i) => i.trim())
|
|
971
|
-
.filter((i) => i),
|
|
972
|
-
acceptance: answers.acceptanceCriteria
|
|
973
|
-
.split('\n')
|
|
974
|
-
.filter((a) => a.trim())
|
|
975
|
-
.map((criteria, index) => {
|
|
976
|
-
const id = `A${index + 1}`;
|
|
977
|
-
const upperCriteria = criteria.toUpperCase();
|
|
978
|
-
|
|
979
|
-
// Try different variations of the format
|
|
980
|
-
let given = '';
|
|
981
|
-
let when = '';
|
|
982
|
-
let then = '';
|
|
983
|
-
|
|
984
|
-
if (
|
|
985
|
-
upperCriteria.includes('GIVEN') &&
|
|
986
|
-
upperCriteria.includes('WHEN') &&
|
|
987
|
-
upperCriteria.includes('THEN')
|
|
988
|
-
) {
|
|
989
|
-
given = criteria.split(/WHEN/i)[0]?.replace(/GIVEN/i, '').trim() || '';
|
|
990
|
-
const whenThen = criteria.split(/WHEN/i)[1];
|
|
991
|
-
when = whenThen?.split(/THEN/i)[0]?.trim() || '';
|
|
992
|
-
then = whenThen?.split(/THEN/i)[1]?.trim() || '';
|
|
993
|
-
} else {
|
|
994
|
-
// Fallback: just split by lines and create simple criteria
|
|
995
|
-
given = 'Current system state';
|
|
996
|
-
when = criteria.replace(/^(GIVEN|WHEN|THEN)/i, '').trim();
|
|
997
|
-
then = 'Expected behavior occurs';
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
return {
|
|
1001
|
-
id,
|
|
1002
|
-
given: given || 'Current system state',
|
|
1003
|
-
when: when || criteria,
|
|
1004
|
-
then: then || 'Expected behavior occurs',
|
|
1005
|
-
};
|
|
1006
|
-
}),
|
|
1007
|
-
non_functional: {
|
|
1008
|
-
a11y: answers.a11yRequirements
|
|
1009
|
-
.split(',')
|
|
1010
|
-
.map((a) => a.trim())
|
|
1011
|
-
.filter((a) => a),
|
|
1012
|
-
perf: { api_p95_ms: answers.perfBudget },
|
|
1013
|
-
security: answers.securityRequirements
|
|
1014
|
-
.split(',')
|
|
1015
|
-
.map((s) => s.trim())
|
|
1016
|
-
.filter((s) => s),
|
|
1017
|
-
},
|
|
1018
|
-
contracts: [
|
|
1019
|
-
{
|
|
1020
|
-
type: answers.contractType,
|
|
1021
|
-
path: answers.contractPath,
|
|
1022
|
-
},
|
|
1023
|
-
],
|
|
1024
|
-
observability: {
|
|
1025
|
-
logs: answers.observabilityLogs
|
|
1026
|
-
.split(',')
|
|
1027
|
-
.map((l) => l.trim())
|
|
1028
|
-
.filter((l) => l),
|
|
1029
|
-
metrics: answers.observabilityMetrics
|
|
1030
|
-
.split(',')
|
|
1031
|
-
.map((m) => m.trim())
|
|
1032
|
-
.filter((m) => m),
|
|
1033
|
-
traces: answers.observabilityTraces
|
|
1034
|
-
.split(',')
|
|
1035
|
-
.map((t) => t.trim())
|
|
1036
|
-
.filter((t) => t),
|
|
1037
|
-
},
|
|
1038
|
-
migrations: (answers.migrationPlan || '')
|
|
1039
|
-
.split('\n')
|
|
1040
|
-
.map((m) => m.trim())
|
|
1041
|
-
.filter((m) => m),
|
|
1042
|
-
rollback: (answers.rollbackPlan || '')
|
|
1043
|
-
.split('\n')
|
|
1044
|
-
.map((r) => r.trim())
|
|
1045
|
-
.filter((r) => r),
|
|
1046
|
-
human_override: answers.needsOverride
|
|
1047
|
-
? {
|
|
1048
|
-
enabled: true,
|
|
1049
|
-
approver: answers.overrideApprover,
|
|
1050
|
-
rationale: answers.overrideRationale,
|
|
1051
|
-
waived_gates: answers.waivedGates,
|
|
1052
|
-
approved_at: new Date().toISOString(),
|
|
1053
|
-
expires_at: new Date(
|
|
1054
|
-
Date.now() + answers.overrideExpiresDays * 24 * 60 * 60 * 1000
|
|
1055
|
-
).toISOString(),
|
|
1056
|
-
}
|
|
1057
|
-
: undefined,
|
|
1058
|
-
experimental_mode: answers.isExperimental
|
|
1059
|
-
? {
|
|
1060
|
-
enabled: true,
|
|
1061
|
-
rationale: answers.experimentalRationale,
|
|
1062
|
-
expires_at: new Date(
|
|
1063
|
-
Date.now() + answers.experimentalExpiresDays * 24 * 60 * 60 * 1000
|
|
1064
|
-
).toISOString(),
|
|
1065
|
-
sandbox_location: answers.experimentalSandbox,
|
|
1066
|
-
}
|
|
1067
|
-
: undefined,
|
|
1068
|
-
ai_assessment: {
|
|
1069
|
-
confidence_level: answers.aiConfidence,
|
|
1070
|
-
uncertainty_areas: answers.uncertaintyAreas
|
|
1071
|
-
.split(',')
|
|
1072
|
-
.map((a) => a.trim())
|
|
1073
|
-
.filter((a) => a),
|
|
1074
|
-
complexity_factors: answers.complexityFactors
|
|
1075
|
-
.split(',')
|
|
1076
|
-
.map((f) => f.trim())
|
|
1077
|
-
.filter((f) => f),
|
|
1078
|
-
risk_factors: [], // Could be populated by AI analysis
|
|
1079
|
-
},
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
return yaml.dump(template, { indent: 2 });
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
/**
|
|
1086
|
-
* Validate generated working spec against JSON schema
|
|
1087
|
-
* @param {string} specContent - YAML spec content
|
|
1088
|
-
* @param {Object} answers - User responses for error context
|
|
1089
|
-
*/
|
|
1090
|
-
function validateGeneratedSpec(specContent, _answers) {
|
|
1091
|
-
try {
|
|
1092
|
-
const spec = yaml.load(specContent);
|
|
1093
|
-
|
|
1094
|
-
const isValid = validateWorkingSpec(spec);
|
|
1095
|
-
|
|
1096
|
-
if (!isValid) {
|
|
1097
|
-
console.error(chalk.red('â Generated working spec failed validation:'));
|
|
1098
|
-
validateWorkingSpec.errors.forEach((error) => {
|
|
1099
|
-
console.error(` - ${error.instancePath || 'root'}: ${error.message}`);
|
|
1100
|
-
});
|
|
1101
|
-
|
|
1102
|
-
// Provide helpful guidance
|
|
1103
|
-
console.log(chalk.blue('\nđĄ Validation Tips:'));
|
|
1104
|
-
console.log(' - Ensure risk_tier is 1, 2, or 3');
|
|
1105
|
-
console.log(' - Check that scope.in is not empty');
|
|
1106
|
-
console.log(' - Verify invariants and acceptance criteria are provided');
|
|
1107
|
-
console.log(' - For tier 1 and 2, ensure contracts are specified');
|
|
1108
|
-
|
|
1109
|
-
process.exit(1);
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
console.log(chalk.green('â
Generated working spec passed validation'));
|
|
1113
|
-
} catch (error) {
|
|
1114
|
-
console.error(chalk.red('â Error validating working spec:'), error.message);
|
|
1115
|
-
process.exit(1);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
/**
|
|
1120
|
-
* Detect project type from existing files and structure
|
|
1121
|
-
*/
|
|
1122
|
-
function detectProjectType(cwd = process.cwd()) {
|
|
1123
|
-
const files = fs.readdirSync(cwd);
|
|
1124
|
-
|
|
1125
|
-
// Check for various project indicators
|
|
1126
|
-
const hasPackageJson = files.includes('package.json');
|
|
1127
|
-
const hasPnpm = files.includes('pnpm-workspace.yaml');
|
|
1128
|
-
const hasYarn = files.includes('yarn.lock');
|
|
1129
|
-
|
|
1130
|
-
let packageJson = {};
|
|
1131
|
-
if (hasPackageJson) {
|
|
1132
|
-
try {
|
|
1133
|
-
packageJson = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
1134
|
-
} catch (e) {
|
|
1135
|
-
// Ignore parse errors
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// VS Code Extension detection
|
|
1140
|
-
const isVscodeExtension =
|
|
1141
|
-
packageJson.engines?.vscode ||
|
|
1142
|
-
packageJson.contributes ||
|
|
1143
|
-
packageJson.activationEvents ||
|
|
1144
|
-
packageJson.main?.includes('extension.js');
|
|
1145
|
-
|
|
1146
|
-
// Monorepo detection
|
|
1147
|
-
const isMonorepo = hasPnpm || hasYarn || files.includes('packages') || files.includes('apps');
|
|
1148
|
-
|
|
1149
|
-
// Library detection
|
|
1150
|
-
const isLibrary = packageJson.main || packageJson.module || packageJson.exports;
|
|
1151
|
-
|
|
1152
|
-
// CLI detection
|
|
1153
|
-
const isCli = packageJson.bin || packageJson.name?.startsWith('@') === false;
|
|
1154
|
-
|
|
1155
|
-
// API detection
|
|
1156
|
-
const isApi =
|
|
1157
|
-
packageJson.scripts?.start ||
|
|
1158
|
-
packageJson.dependencies?.express ||
|
|
1159
|
-
packageJson.dependencies?.fastify ||
|
|
1160
|
-
packageJson.dependencies?.['@types/express'];
|
|
1161
|
-
|
|
1162
|
-
// Determine primary type
|
|
1163
|
-
if (isVscodeExtension) return 'extension';
|
|
1164
|
-
if (isMonorepo) return 'monorepo';
|
|
1165
|
-
if (isApi) return 'api';
|
|
1166
|
-
if (isLibrary) return 'library';
|
|
1167
|
-
if (isCli) return 'cli';
|
|
1168
|
-
|
|
1169
|
-
// Default fallback
|
|
1170
|
-
return 'application';
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
/**
|
|
1174
|
-
* Generate working spec from project analysis
|
|
1175
|
-
*/
|
|
1176
|
-
function generateWorkingSpecFromAnalysis(analysis) {
|
|
1177
|
-
const { projectType, packageJson } = analysis;
|
|
1178
|
-
|
|
1179
|
-
const templates = {
|
|
1180
|
-
extension: {
|
|
1181
|
-
risk_tier: 2,
|
|
1182
|
-
mode: 'feature',
|
|
1183
|
-
change_budget: { max_files: 25, max_loc: 1000 },
|
|
1184
|
-
invariants: [
|
|
1185
|
-
'Webview only accesses workspace files via VS Code API',
|
|
1186
|
-
'Extension activates in <1s on typical machine',
|
|
1187
|
-
'All commands have keyboard shortcuts',
|
|
1188
|
-
],
|
|
1189
|
-
scope: {
|
|
1190
|
-
in: ['src/', 'package.json', 'tsconfig.json'],
|
|
1191
|
-
out: ['node_modules/', '*.vsix'],
|
|
1192
|
-
},
|
|
1193
|
-
acceptance: [
|
|
1194
|
-
{
|
|
1195
|
-
id: 'A1',
|
|
1196
|
-
given: 'User has workspace open',
|
|
1197
|
-
when: 'Extension activates',
|
|
1198
|
-
then: 'Webview loads within 1 second',
|
|
1199
|
-
},
|
|
1200
|
-
],
|
|
1201
|
-
non_functional: {
|
|
1202
|
-
a11y: ['keyboard navigation', 'screen reader support', 'high contrast theme'],
|
|
1203
|
-
perf: { api_p95_ms: 100 },
|
|
1204
|
-
security: ['CSP enforcement for webviews', 'No arbitrary filesystem access'],
|
|
1205
|
-
},
|
|
1206
|
-
},
|
|
1207
|
-
library: {
|
|
1208
|
-
risk_tier: 2,
|
|
1209
|
-
mode: 'feature',
|
|
1210
|
-
change_budget: { max_files: 20, max_loc: 800 },
|
|
1211
|
-
invariants: [
|
|
1212
|
-
'No runtime dependencies except React',
|
|
1213
|
-
'Tree-shakeable exports',
|
|
1214
|
-
'TypeScript types exported',
|
|
1215
|
-
],
|
|
1216
|
-
scope: {
|
|
1217
|
-
in: ['src/', 'lib/', 'package.json'],
|
|
1218
|
-
out: ['examples/', 'docs/', 'node_modules/'],
|
|
1219
|
-
},
|
|
1220
|
-
acceptance: [
|
|
1221
|
-
{
|
|
1222
|
-
id: 'A1',
|
|
1223
|
-
given: 'Library is imported',
|
|
1224
|
-
when: 'Component is rendered',
|
|
1225
|
-
then: 'No runtime errors occur',
|
|
1226
|
-
},
|
|
1227
|
-
],
|
|
1228
|
-
non_functional: {
|
|
1229
|
-
a11y: ['WCAG 2.1 AA compliance', 'Semantic HTML'],
|
|
1230
|
-
perf: { bundle_size_kb: 50 },
|
|
1231
|
-
security: ['Input validation', 'XSS prevention'],
|
|
1232
|
-
},
|
|
1233
|
-
},
|
|
1234
|
-
api: {
|
|
1235
|
-
risk_tier: 1,
|
|
1236
|
-
mode: 'feature',
|
|
1237
|
-
change_budget: { max_files: 40, max_loc: 1500 },
|
|
1238
|
-
invariants: [
|
|
1239
|
-
'API maintains backward compatibility',
|
|
1240
|
-
'All endpoints respond within 100ms',
|
|
1241
|
-
'Data consistency maintained across requests',
|
|
1242
|
-
],
|
|
1243
|
-
scope: {
|
|
1244
|
-
in: ['src/', 'routes/', 'models/', 'tests/'],
|
|
1245
|
-
out: ['node_modules/', 'logs/', 'temp/'],
|
|
1246
|
-
},
|
|
1247
|
-
acceptance: [
|
|
1248
|
-
{
|
|
1249
|
-
id: 'A1',
|
|
1250
|
-
given: 'Valid request is made',
|
|
1251
|
-
when: 'Endpoint is called',
|
|
1252
|
-
then: 'Correct response returned within SLO',
|
|
1253
|
-
},
|
|
1254
|
-
],
|
|
1255
|
-
non_functional: {
|
|
1256
|
-
a11y: ['API documentation accessible'],
|
|
1257
|
-
perf: { api_p95_ms: 100 },
|
|
1258
|
-
security: ['Input validation', 'Rate limiting', 'Authentication'],
|
|
1259
|
-
},
|
|
1260
|
-
},
|
|
1261
|
-
cli: {
|
|
1262
|
-
risk_tier: 3,
|
|
1263
|
-
mode: 'feature',
|
|
1264
|
-
change_budget: { max_files: 15, max_loc: 600 },
|
|
1265
|
-
invariants: [
|
|
1266
|
-
'CLI exits with appropriate codes',
|
|
1267
|
-
'Help text is informative',
|
|
1268
|
-
'Error messages are clear',
|
|
1269
|
-
],
|
|
1270
|
-
scope: {
|
|
1271
|
-
in: ['src/', 'bin/', 'lib/', 'tests/'],
|
|
1272
|
-
out: ['node_modules/', 'dist/'],
|
|
1273
|
-
},
|
|
1274
|
-
acceptance: [
|
|
1275
|
-
{
|
|
1276
|
-
id: 'A1',
|
|
1277
|
-
given: 'User runs command with --help',
|
|
1278
|
-
when: 'Help flag is provided',
|
|
1279
|
-
then: 'Help text displays clearly',
|
|
1280
|
-
},
|
|
1281
|
-
],
|
|
1282
|
-
non_functional: {
|
|
1283
|
-
a11y: ['Color contrast in terminal output'],
|
|
1284
|
-
perf: { api_p95_ms: 50 },
|
|
1285
|
-
security: ['Input validation', 'No arbitrary execution'],
|
|
1286
|
-
},
|
|
1287
|
-
},
|
|
1288
|
-
monorepo: {
|
|
1289
|
-
risk_tier: 1,
|
|
1290
|
-
mode: 'feature',
|
|
1291
|
-
change_budget: { max_files: 50, max_loc: 2000 },
|
|
1292
|
-
invariants: [
|
|
1293
|
-
'All packages remain compatible',
|
|
1294
|
-
'Cross-package dependencies work',
|
|
1295
|
-
'Build system remains stable',
|
|
1296
|
-
],
|
|
1297
|
-
scope: {
|
|
1298
|
-
in: ['packages/', 'apps/', 'tools/', 'scripts/'],
|
|
1299
|
-
out: ['node_modules/', 'dist/', 'build/'],
|
|
1300
|
-
},
|
|
1301
|
-
acceptance: [
|
|
1302
|
-
{
|
|
1303
|
-
id: 'A1',
|
|
1304
|
-
given: 'Change is made to shared package',
|
|
1305
|
-
when: 'All dependent packages build',
|
|
1306
|
-
then: 'No breaking changes introduced',
|
|
1307
|
-
},
|
|
1308
|
-
],
|
|
1309
|
-
non_functional: {
|
|
1310
|
-
a11y: ['Documentation accessible across packages'],
|
|
1311
|
-
perf: { api_p95_ms: 200 },
|
|
1312
|
-
security: ['Dependency audit passes', 'No vulnerable packages'],
|
|
1313
|
-
},
|
|
1314
|
-
},
|
|
1315
|
-
application: {
|
|
1316
|
-
risk_tier: 2,
|
|
1317
|
-
mode: 'feature',
|
|
1318
|
-
change_budget: { max_files: 30, max_loc: 1200 },
|
|
1319
|
-
invariants: [
|
|
1320
|
-
'Application remains functional',
|
|
1321
|
-
'User data is preserved',
|
|
1322
|
-
'Performance does not degrade',
|
|
1323
|
-
],
|
|
1324
|
-
scope: {
|
|
1325
|
-
in: ['src/', 'components/', 'pages/', 'lib/'],
|
|
1326
|
-
out: ['node_modules/', 'build/', 'dist/'],
|
|
1327
|
-
},
|
|
1328
|
-
acceptance: [
|
|
1329
|
-
{
|
|
1330
|
-
id: 'A1',
|
|
1331
|
-
given: 'User interacts with application',
|
|
1332
|
-
when: 'Feature is used',
|
|
1333
|
-
then: 'Expected behavior occurs',
|
|
1334
|
-
},
|
|
1335
|
-
],
|
|
1336
|
-
non_functional: {
|
|
1337
|
-
a11y: ['WCAG 2.1 AA compliance', 'Keyboard navigation'],
|
|
1338
|
-
perf: { api_p95_ms: 250 },
|
|
1339
|
-
security: ['Input validation', 'Authentication', 'Authorization'],
|
|
1340
|
-
},
|
|
1341
|
-
},
|
|
1342
|
-
};
|
|
1343
|
-
|
|
1344
|
-
const baseSpec = templates[projectType] || templates.application;
|
|
1345
|
-
|
|
1346
|
-
return {
|
|
1347
|
-
id: `${packageJson.name?.toUpperCase().replace(/[^A-Z0-9]/g, '-') || 'PROJECT'}-001`,
|
|
1348
|
-
title: packageJson.name || 'Project',
|
|
1349
|
-
risk_tier: baseSpec.risk_tier,
|
|
1350
|
-
mode: baseSpec.mode,
|
|
1351
|
-
change_budget: baseSpec.change_budget,
|
|
1352
|
-
blast_radius: {
|
|
1353
|
-
modules: ['core', 'api', 'ui'],
|
|
1354
|
-
data_migration: false,
|
|
1355
|
-
},
|
|
1356
|
-
operational_rollback_slo: '5m',
|
|
1357
|
-
scope: baseSpec.scope,
|
|
1358
|
-
invariants: baseSpec.invariants,
|
|
1359
|
-
acceptance: baseSpec.acceptance,
|
|
1360
|
-
non_functional: baseSpec.non_functional,
|
|
1361
|
-
contracts: [
|
|
1362
|
-
{
|
|
1363
|
-
type: projectType === 'api' ? 'openapi' : 'none',
|
|
1364
|
-
path: projectType === 'api' ? 'docs/api.yaml' : '',
|
|
1365
|
-
},
|
|
1366
|
-
],
|
|
1367
|
-
observability: {
|
|
1368
|
-
logs: ['error', 'warn', 'info'],
|
|
1369
|
-
metrics: ['requests_total', 'errors_total'],
|
|
1370
|
-
traces: ['request_flow'],
|
|
1371
|
-
},
|
|
1372
|
-
ai_assessment: {
|
|
1373
|
-
confidence_level: 8,
|
|
1374
|
-
uncertainty_areas: [],
|
|
1375
|
-
complexity_factors: [],
|
|
1376
|
-
risk_factors: [],
|
|
1377
|
-
},
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
/**
|
|
1382
|
-
* Detect if current directory appears to be a project that should be initialized directly
|
|
1383
|
-
*/
|
|
1384
|
-
function shouldInitInCurrentDirectory(projectName, currentDir) {
|
|
1385
|
-
// If explicitly '.', always init in current directory
|
|
1386
|
-
if (projectName === '.') return true;
|
|
1387
|
-
|
|
1388
|
-
// Check for common project indicators
|
|
1389
|
-
const projectIndicators = [
|
|
1390
|
-
'package.json',
|
|
1391
|
-
'tsconfig.json',
|
|
1392
|
-
'jest.config.js',
|
|
1393
|
-
'eslint.config.js',
|
|
1394
|
-
'README.md',
|
|
1395
|
-
'src/',
|
|
1396
|
-
'lib/',
|
|
1397
|
-
'app/',
|
|
1398
|
-
'packages/',
|
|
1399
|
-
'.git/',
|
|
1400
|
-
'node_modules/', // Even if empty, suggests intent to be a project
|
|
1401
|
-
];
|
|
1402
|
-
|
|
1403
|
-
const files = fs.readdirSync(currentDir);
|
|
1404
|
-
const hasProjectIndicators = projectIndicators.some((indicator) => {
|
|
1405
|
-
if (indicator.endsWith('/')) {
|
|
1406
|
-
return files.includes(indicator.slice(0, -1));
|
|
1407
|
-
}
|
|
1408
|
-
return files.includes(indicator);
|
|
1409
|
-
});
|
|
1410
|
-
|
|
1411
|
-
return hasProjectIndicators;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
/**
|
|
1415
|
-
* Initialize a new project with CAWS
|
|
1416
|
-
*/
|
|
1417
|
-
async function initProject(projectName, options) {
|
|
1418
|
-
const currentDir = process.cwd();
|
|
1419
|
-
const isCurrentDirInit = shouldInitInCurrentDirectory(projectName, currentDir);
|
|
1420
|
-
|
|
1421
|
-
if (!isCurrentDirInit && projectName !== '.') {
|
|
1422
|
-
console.log(chalk.cyan(`đ Initializing new CAWS project: ${projectName}`));
|
|
1423
|
-
console.log(chalk.gray(` (Creating subdirectory: ${projectName}/)`));
|
|
1424
|
-
} else {
|
|
1425
|
-
console.log(
|
|
1426
|
-
chalk.cyan(`đ Initializing CAWS in current project: ${path.basename(currentDir)}`)
|
|
1427
|
-
);
|
|
1428
|
-
console.log(chalk.gray(` (Adding CAWS files to existing project)`));
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
let answers; // Will be set either interactively or with defaults
|
|
1432
|
-
|
|
1433
|
-
try {
|
|
1434
|
-
// Validate project name
|
|
1435
|
-
if (!projectName || projectName.trim() === '') {
|
|
1436
|
-
console.error(chalk.red('â Project name is required'));
|
|
1437
|
-
console.error(chalk.blue('đĄ Usage: caws init <project-name>'));
|
|
1438
|
-
process.exit(1);
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
// Special case: '.' means current directory, don't sanitize
|
|
1442
|
-
if (projectName !== '.') {
|
|
1443
|
-
// Sanitize project name
|
|
1444
|
-
const sanitizedName = projectName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
|
|
1445
|
-
if (sanitizedName !== projectName) {
|
|
1446
|
-
console.warn(chalk.yellow(`â ī¸ Project name sanitized to: ${sanitizedName}`));
|
|
1447
|
-
projectName = sanitizedName;
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
// Validate project name length
|
|
1452
|
-
if (projectName.length > 50) {
|
|
1453
|
-
console.error(chalk.red('â Project name is too long (max 50 characters)'));
|
|
1454
|
-
console.error(chalk.blue('đĄ Usage: caws init <project-name>'));
|
|
1455
|
-
process.exit(1);
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
// Validate project name format
|
|
1459
|
-
if (projectName.length === 0) {
|
|
1460
|
-
console.error(chalk.red('â Project name cannot be empty'));
|
|
1461
|
-
console.error(chalk.blue('đĄ Usage: caws init <project-name>'));
|
|
1462
|
-
process.exit(1);
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
// Check for invalid characters that should cause immediate failure
|
|
1466
|
-
if (projectName.includes('/') || projectName.includes('\\') || projectName.includes('..')) {
|
|
1467
|
-
console.error(chalk.red('â Project name contains invalid characters'));
|
|
1468
|
-
console.error(chalk.blue('đĄ Usage: caws init <project-name>'));
|
|
1469
|
-
console.error(chalk.blue('đĄ Project name should not contain: / \\ ..'));
|
|
1470
|
-
process.exit(1);
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
// Determine if initializing in current directory
|
|
1474
|
-
const initInCurrentDir = projectName === '.';
|
|
1475
|
-
const targetDir = initInCurrentDir ? process.cwd() : path.resolve(process.cwd(), projectName);
|
|
1476
|
-
|
|
1477
|
-
// Check if target directory already exists and has content (skip check for current directory)
|
|
1478
|
-
if (!initInCurrentDir && fs.existsSync(projectName)) {
|
|
1479
|
-
const existingFiles = fs.readdirSync(projectName);
|
|
1480
|
-
if (existingFiles.length > 0) {
|
|
1481
|
-
console.error(chalk.red(`â Directory '${projectName}' already exists and contains files`));
|
|
1482
|
-
console.error(chalk.blue('đĄ To initialize CAWS in current directory instead:'));
|
|
1483
|
-
console.error(` ${chalk.cyan('caws init .')}`);
|
|
1484
|
-
console.error(chalk.blue('đĄ Or choose a different name/remove existing directory'));
|
|
1485
|
-
process.exit(1);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
// Check if current directory has project files when trying to init in subdirectory
|
|
1490
|
-
if (!initInCurrentDir) {
|
|
1491
|
-
const currentDirFiles = fs.readdirSync(process.cwd());
|
|
1492
|
-
const hasProjectFiles = currentDirFiles.some(
|
|
1493
|
-
(file) => !file.startsWith('.') && file !== 'node_modules' && file !== '.git'
|
|
1494
|
-
);
|
|
1495
|
-
|
|
1496
|
-
if (hasProjectFiles) {
|
|
1497
|
-
console.warn(chalk.yellow('â ī¸ Current directory contains project files'));
|
|
1498
|
-
console.warn(
|
|
1499
|
-
chalk.blue('đĄ You might want to initialize CAWS in current directory instead:')
|
|
1500
|
-
);
|
|
1501
|
-
console.warn(` ${chalk.cyan('caws init .')}`);
|
|
1502
|
-
console.warn(chalk.blue(' Or continue to create subdirectory (Ctrl+C to cancel)'));
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// Save the original template directory before changing directories
|
|
1507
|
-
const originalTemplateDir = cawsSetup?.hasTemplateDir ? cawsSetup.templateDir : null;
|
|
1508
|
-
|
|
1509
|
-
// Check for existing agents.md/caws.md in target directory
|
|
1510
|
-
const existingAgentsMd = fs.existsSync(path.join(targetDir, 'agents.md'));
|
|
1511
|
-
const existingCawsMd = fs.existsSync(path.join(targetDir, 'caws.md'));
|
|
1512
|
-
|
|
1513
|
-
// Create project directory and change to it (unless already in current directory)
|
|
1514
|
-
if (!initInCurrentDir) {
|
|
1515
|
-
await fs.ensureDir(projectName);
|
|
1516
|
-
process.chdir(projectName);
|
|
1517
|
-
console.log(chalk.green(`đ Created project directory: ${projectName}`));
|
|
1518
|
-
} else {
|
|
1519
|
-
console.log(chalk.green(`đ Initializing in current directory`));
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// Detect and adapt to existing setup
|
|
1523
|
-
const currentSetup = detectCAWSSetup(process.cwd());
|
|
1524
|
-
|
|
1525
|
-
if (currentSetup.type === 'new') {
|
|
1526
|
-
// Create minimal CAWS structure
|
|
1527
|
-
await fs.ensureDir('.caws');
|
|
1528
|
-
await fs.ensureDir('.agent');
|
|
1529
|
-
console.log(chalk.blue('âšī¸ Created basic CAWS structure'));
|
|
1530
|
-
|
|
1531
|
-
// Copy agents.md guide if templates are available
|
|
1532
|
-
if (originalTemplateDir) {
|
|
1533
|
-
try {
|
|
1534
|
-
const agentsMdSource = path.join(originalTemplateDir, 'agents.md');
|
|
1535
|
-
let targetFile = 'agents.md';
|
|
1536
|
-
|
|
1537
|
-
if (fs.existsSync(agentsMdSource)) {
|
|
1538
|
-
// Use the pre-checked values for conflicts
|
|
1539
|
-
if (existingAgentsMd) {
|
|
1540
|
-
// Conflict: user already has agents.md
|
|
1541
|
-
if (options.interactive && !options.nonInteractive) {
|
|
1542
|
-
// Interactive mode: ask user
|
|
1543
|
-
const overwriteAnswer = await inquirer.prompt([
|
|
1544
|
-
{
|
|
1545
|
-
type: 'confirm',
|
|
1546
|
-
name: 'overwrite',
|
|
1547
|
-
message: 'â ī¸ agents.md already exists. Overwrite with CAWS guide?',
|
|
1548
|
-
default: false,
|
|
1549
|
-
},
|
|
1550
|
-
]);
|
|
1551
|
-
|
|
1552
|
-
if (overwriteAnswer.overwrite) {
|
|
1553
|
-
targetFile = 'agents.md';
|
|
1554
|
-
} else {
|
|
1555
|
-
targetFile = 'caws.md';
|
|
1556
|
-
}
|
|
1557
|
-
} else {
|
|
1558
|
-
// Non-interactive mode: use caws.md instead
|
|
1559
|
-
targetFile = 'caws.md';
|
|
1560
|
-
console.log(chalk.blue('âšī¸ agents.md exists, using caws.md for CAWS guide'));
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
// If caws.md also exists and that's our target, skip
|
|
1565
|
-
if (targetFile === 'caws.md' && existingCawsMd) {
|
|
1566
|
-
console.log(
|
|
1567
|
-
chalk.yellow('â ī¸ Both agents.md and caws.md exist, skipping guide copy')
|
|
1568
|
-
);
|
|
1569
|
-
} else {
|
|
1570
|
-
const agentsMdDest = path.join(process.cwd(), targetFile);
|
|
1571
|
-
await fs.copyFile(agentsMdSource, agentsMdDest);
|
|
1572
|
-
console.log(chalk.green(`â
Added ${targetFile} guide`));
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
} catch (templateError) {
|
|
1576
|
-
console.warn(chalk.yellow('â ī¸ Could not copy agents guide:'), templateError.message);
|
|
1577
|
-
console.warn(
|
|
1578
|
-
chalk.blue('đĄ You can manually copy the guide from the caws-template package')
|
|
1579
|
-
);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
} else {
|
|
1583
|
-
// Already has CAWS setup
|
|
1584
|
-
console.log(chalk.green('â
CAWS project detected - skipping template copy'));
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
// Handle interactive wizard or template-based setup
|
|
1588
|
-
if (options.interactive && !options.nonInteractive) {
|
|
1589
|
-
console.log(chalk.cyan('đ¯ CAWS Interactive Setup Wizard'));
|
|
1590
|
-
console.log(chalk.blue('ââââââââââââââââââââââââââââââââââââââââ'));
|
|
1591
|
-
console.log(chalk.gray('This wizard will guide you through creating a CAWS working spec\n'));
|
|
1592
|
-
|
|
1593
|
-
// Detect project type
|
|
1594
|
-
const detectedType = detectProjectType(process.cwd());
|
|
1595
|
-
console.log(chalk.blue(`đĻ Detected project type: ${chalk.cyan(detectedType)}`));
|
|
1596
|
-
|
|
1597
|
-
// Get package.json info if available
|
|
1598
|
-
let packageJson = {};
|
|
1599
|
-
try {
|
|
1600
|
-
packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
|
|
1601
|
-
} catch (e) {
|
|
1602
|
-
// No package.json, that's fine
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
const wizardQuestions = [
|
|
1606
|
-
{
|
|
1607
|
-
type: 'list',
|
|
1608
|
-
name: 'projectType',
|
|
1609
|
-
message: 'â What type of project is this?',
|
|
1610
|
-
choices: [
|
|
1611
|
-
{
|
|
1612
|
-
name: 'đ VS Code Extension (webview, commands, integrations)',
|
|
1613
|
-
value: 'extension',
|
|
1614
|
-
short: 'VS Code Extension',
|
|
1615
|
-
},
|
|
1616
|
-
{
|
|
1617
|
-
name: 'đ Library/Package (reusable components, utilities)',
|
|
1618
|
-
value: 'library',
|
|
1619
|
-
short: 'Library',
|
|
1620
|
-
},
|
|
1621
|
-
{
|
|
1622
|
-
name: 'đ API Service (REST, GraphQL, microservices)',
|
|
1623
|
-
value: 'api',
|
|
1624
|
-
short: 'API Service',
|
|
1625
|
-
},
|
|
1626
|
-
{
|
|
1627
|
-
name: 'đģ CLI Tool (command-line interface)',
|
|
1628
|
-
value: 'cli',
|
|
1629
|
-
short: 'CLI Tool',
|
|
1630
|
-
},
|
|
1631
|
-
{
|
|
1632
|
-
name: 'đī¸ Monorepo (multiple packages/apps)',
|
|
1633
|
-
value: 'monorepo',
|
|
1634
|
-
short: 'Monorepo',
|
|
1635
|
-
},
|
|
1636
|
-
{
|
|
1637
|
-
name: 'đą Application (standalone app)',
|
|
1638
|
-
value: 'application',
|
|
1639
|
-
short: 'Application',
|
|
1640
|
-
},
|
|
1641
|
-
],
|
|
1642
|
-
default: detectedType,
|
|
1643
|
-
},
|
|
1644
|
-
{
|
|
1645
|
-
type: 'input',
|
|
1646
|
-
name: 'projectTitle',
|
|
1647
|
-
message: 'đ Project Title (descriptive name):',
|
|
1648
|
-
default:
|
|
1649
|
-
packageJson.name ||
|
|
1650
|
-
projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
|
|
1651
|
-
},
|
|
1652
|
-
{
|
|
1653
|
-
type: 'list',
|
|
1654
|
-
name: 'riskTier',
|
|
1655
|
-
message: 'â ī¸ Risk Tier (higher tier = more rigor):',
|
|
1656
|
-
choices: [
|
|
1657
|
-
{
|
|
1658
|
-
name: 'đ´ Tier 1 - Critical (auth, billing, migrations) - Max rigor',
|
|
1659
|
-
value: 1,
|
|
1660
|
-
short: 'Critical',
|
|
1661
|
-
},
|
|
1662
|
-
{
|
|
1663
|
-
name: 'đĄ Tier 2 - Standard (features, APIs) - Standard rigor',
|
|
1664
|
-
value: 2,
|
|
1665
|
-
short: 'Standard',
|
|
1666
|
-
},
|
|
1667
|
-
{
|
|
1668
|
-
name: 'đĸ Tier 3 - Low Risk (UI, tooling) - Basic rigor',
|
|
1669
|
-
value: 3,
|
|
1670
|
-
short: 'Low Risk',
|
|
1671
|
-
},
|
|
1672
|
-
],
|
|
1673
|
-
default: (answers) => {
|
|
1674
|
-
const typeDefaults = {
|
|
1675
|
-
extension: 2,
|
|
1676
|
-
library: 2,
|
|
1677
|
-
api: 1,
|
|
1678
|
-
cli: 3,
|
|
1679
|
-
monorepo: 1,
|
|
1680
|
-
application: 2,
|
|
1681
|
-
};
|
|
1682
|
-
return typeDefaults[answers.projectType] || 2;
|
|
1683
|
-
},
|
|
1684
|
-
},
|
|
1685
|
-
{
|
|
1686
|
-
type: 'list',
|
|
1687
|
-
name: 'projectMode',
|
|
1688
|
-
message: 'đ¯ Primary development mode:',
|
|
1689
|
-
choices: [
|
|
1690
|
-
{ name: '⨠feature (new functionality)', value: 'feature' },
|
|
1691
|
-
{ name: 'đ refactor (code restructuring)', value: 'refactor' },
|
|
1692
|
-
{ name: 'đ fix (bug fixes)', value: 'fix' },
|
|
1693
|
-
{ name: 'đ doc (documentation)', value: 'doc' },
|
|
1694
|
-
{ name: 'đ§š chore (maintenance)', value: 'chore' },
|
|
1695
|
-
],
|
|
1696
|
-
default: 'feature',
|
|
1697
|
-
},
|
|
1698
|
-
{
|
|
1699
|
-
type: 'number',
|
|
1700
|
-
name: 'maxFiles',
|
|
1701
|
-
message: 'đ Max files to change per feature:',
|
|
1702
|
-
default: (answers) => {
|
|
1703
|
-
const tierDefaults = { 1: 40, 2: 25, 3: 15 };
|
|
1704
|
-
const typeAdjustments = {
|
|
1705
|
-
extension: -5,
|
|
1706
|
-
library: -10,
|
|
1707
|
-
api: 10,
|
|
1708
|
-
cli: -10,
|
|
1709
|
-
monorepo: 25,
|
|
1710
|
-
application: 0,
|
|
1711
|
-
};
|
|
1712
|
-
return Math.max(
|
|
1713
|
-
5,
|
|
1714
|
-
tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
|
|
1715
|
-
);
|
|
1716
|
-
},
|
|
1717
|
-
},
|
|
1718
|
-
{
|
|
1719
|
-
type: 'number',
|
|
1720
|
-
name: 'maxLoc',
|
|
1721
|
-
message: 'đ Max lines of code to change per feature:',
|
|
1722
|
-
default: (answers) => {
|
|
1723
|
-
const tierDefaults = { 1: 1500, 2: 1000, 3: 600 };
|
|
1724
|
-
const typeAdjustments = {
|
|
1725
|
-
extension: -200,
|
|
1726
|
-
library: -300,
|
|
1727
|
-
api: 500,
|
|
1728
|
-
cli: -400,
|
|
1729
|
-
monorepo: 1000,
|
|
1730
|
-
application: 0,
|
|
1731
|
-
};
|
|
1732
|
-
return Math.max(
|
|
1733
|
-
50,
|
|
1734
|
-
tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
|
|
1735
|
-
);
|
|
1736
|
-
},
|
|
1737
|
-
},
|
|
1738
|
-
{
|
|
1739
|
-
type: 'input',
|
|
1740
|
-
name: 'blastModules',
|
|
1741
|
-
message: 'đĨ Affected modules (comma-separated):',
|
|
1742
|
-
default: (answers) => {
|
|
1743
|
-
const typeDefaults = {
|
|
1744
|
-
extension: 'core,webview',
|
|
1745
|
-
library: 'components,utils',
|
|
1746
|
-
api: 'routes,models,controllers',
|
|
1747
|
-
cli: 'commands,utils',
|
|
1748
|
-
monorepo: 'shared,packages',
|
|
1749
|
-
application: 'ui,logic,data',
|
|
1750
|
-
};
|
|
1751
|
-
return typeDefaults[answers.projectType] || 'core,ui';
|
|
1752
|
-
},
|
|
1753
|
-
},
|
|
1754
|
-
{
|
|
1755
|
-
type: 'confirm',
|
|
1756
|
-
name: 'dataMigration',
|
|
1757
|
-
message: 'đī¸ Requires data migration?',
|
|
1758
|
-
default: false,
|
|
1759
|
-
},
|
|
1760
|
-
{
|
|
1761
|
-
type: 'list',
|
|
1762
|
-
name: 'rollbackSlo',
|
|
1763
|
-
message: 'âąī¸ Operational rollback SLO:',
|
|
1764
|
-
choices: [
|
|
1765
|
-
{ name: '⥠1 minute (critical systems)', value: '1m' },
|
|
1766
|
-
{ name: 'đĄ 5 minutes (standard)', value: '5m' },
|
|
1767
|
-
{ name: 'đ 15 minutes (complex)', value: '15m' },
|
|
1768
|
-
{ name: 'đ´ 1 hour (data migration)', value: '1h' },
|
|
1769
|
-
],
|
|
1770
|
-
default: '5m',
|
|
1771
|
-
},
|
|
1772
|
-
{
|
|
1773
|
-
type: 'confirm',
|
|
1774
|
-
name: 'enableCursorHooks',
|
|
1775
|
-
message: 'đ Enable Cursor hooks for real-time quality gates?',
|
|
1776
|
-
default: true,
|
|
1777
|
-
},
|
|
1778
|
-
{
|
|
1779
|
-
type: 'checkbox',
|
|
1780
|
-
name: 'cursorHookLevels',
|
|
1781
|
-
message: 'đ Which Cursor hooks should be enabled?',
|
|
1782
|
-
when: (answers) => answers.enableCursorHooks,
|
|
1783
|
-
choices: [
|
|
1784
|
-
{ name: 'Safety (secrets, PII, dangerous commands)', value: 'safety', checked: true },
|
|
1785
|
-
{ name: 'Quality (formatting, linting, validation)', value: 'quality', checked: true },
|
|
1786
|
-
{
|
|
1787
|
-
name: 'Scope guards (file scope, naming conventions)',
|
|
1788
|
-
value: 'scope',
|
|
1789
|
-
checked: true,
|
|
1790
|
-
},
|
|
1791
|
-
{ name: 'Audit trail (provenance tracking)', value: 'audit', checked: true },
|
|
1792
|
-
],
|
|
1793
|
-
default: ['safety', 'quality', 'scope', 'audit'],
|
|
1794
|
-
},
|
|
1795
|
-
{
|
|
1796
|
-
type: 'confirm',
|
|
1797
|
-
name: 'configureGit',
|
|
1798
|
-
message: 'đ§ Configure git author information for commits?',
|
|
1799
|
-
default: true,
|
|
1800
|
-
},
|
|
1801
|
-
{
|
|
1802
|
-
type: 'input',
|
|
1803
|
-
name: 'gitAuthorName',
|
|
1804
|
-
message: 'đ¤ Git author name:',
|
|
1805
|
-
when: (answers) => answers.configureGit,
|
|
1806
|
-
default: () => {
|
|
1807
|
-
try {
|
|
1808
|
-
return require('child_process')
|
|
1809
|
-
.execSync('git config user.name', { encoding: 'utf8' })
|
|
1810
|
-
.trim();
|
|
1811
|
-
} catch {
|
|
1812
|
-
return '';
|
|
1813
|
-
}
|
|
1814
|
-
},
|
|
1815
|
-
validate: (input) => input.length > 0 || 'Git author name is required',
|
|
1816
|
-
},
|
|
1817
|
-
{
|
|
1818
|
-
type: 'input',
|
|
1819
|
-
name: 'gitAuthorEmail',
|
|
1820
|
-
message: 'đ§ Git author email:',
|
|
1821
|
-
when: (answers) => answers.configureGit,
|
|
1822
|
-
default: () => {
|
|
1823
|
-
try {
|
|
1824
|
-
return require('child_process')
|
|
1825
|
-
.execSync('git config user.email', { encoding: 'utf8' })
|
|
1826
|
-
.trim();
|
|
1827
|
-
} catch {
|
|
1828
|
-
return '';
|
|
1829
|
-
}
|
|
1830
|
-
},
|
|
1831
|
-
validate: (input) => {
|
|
1832
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1833
|
-
return emailRegex.test(input) || 'Please enter a valid email address';
|
|
1834
|
-
},
|
|
1835
|
-
},
|
|
1836
|
-
];
|
|
1837
|
-
|
|
1838
|
-
console.log(chalk.cyan('âŗ Gathering project requirements...'));
|
|
1839
|
-
|
|
1840
|
-
let wizardAnswers;
|
|
1841
|
-
try {
|
|
1842
|
-
wizardAnswers = await inquirer.prompt(wizardQuestions);
|
|
1843
|
-
} catch (error) {
|
|
1844
|
-
if (error.isTtyError) {
|
|
1845
|
-
console.error(chalk.red('â Interactive prompts not supported in this environment'));
|
|
1846
|
-
console.error(chalk.blue('đĄ Run with --non-interactive flag to use defaults'));
|
|
1847
|
-
process.exit(1);
|
|
1848
|
-
} else {
|
|
1849
|
-
console.error(chalk.red('â Error during interactive setup:'), error.message);
|
|
1850
|
-
process.exit(1);
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
console.log(chalk.green('â
Project requirements gathered successfully!'));
|
|
1855
|
-
|
|
1856
|
-
// Show summary before generating spec
|
|
1857
|
-
console.log(chalk.bold('\nđ Configuration Summary:'));
|
|
1858
|
-
console.log(` ${chalk.cyan('Type')}: ${wizardAnswers.projectType}`);
|
|
1859
|
-
console.log(` ${chalk.cyan('Project')}: ${wizardAnswers.projectTitle}`);
|
|
1860
|
-
console.log(
|
|
1861
|
-
` ${chalk.cyan('Mode')}: ${wizardAnswers.projectMode} | ${chalk.cyan('Tier')}: ${wizardAnswers.riskTier}`
|
|
1862
|
-
);
|
|
1863
|
-
console.log(
|
|
1864
|
-
` ${chalk.cyan('Budget')}: ${wizardAnswers.maxFiles} files, ${wizardAnswers.maxLoc} lines`
|
|
1865
|
-
);
|
|
1866
|
-
console.log(
|
|
1867
|
-
` ${chalk.cyan('Data Migration')}: ${wizardAnswers.dataMigration ? 'Yes' : 'No'}`
|
|
1868
|
-
);
|
|
1869
|
-
console.log(` ${chalk.cyan('Rollback SLO')}: ${wizardAnswers.rollbackSlo}`);
|
|
1870
|
-
|
|
1871
|
-
// Generate working spec using the template system
|
|
1872
|
-
const analysis = {
|
|
1873
|
-
projectType: wizardAnswers.projectType,
|
|
1874
|
-
packageJson: { name: wizardAnswers.projectTitle },
|
|
1875
|
-
hasTests: false,
|
|
1876
|
-
hasLinting: false,
|
|
1877
|
-
hasCi: false,
|
|
1878
|
-
};
|
|
1879
|
-
|
|
1880
|
-
const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
|
|
1881
|
-
|
|
1882
|
-
// Override template-generated values with wizard answers
|
|
1883
|
-
const spec = yaml.load(workingSpecContent);
|
|
1884
|
-
spec.title = wizardAnswers.projectTitle;
|
|
1885
|
-
spec.risk_tier = wizardAnswers.riskTier;
|
|
1886
|
-
spec.mode = wizardAnswers.projectMode;
|
|
1887
|
-
spec.change_budget = {
|
|
1888
|
-
max_files: wizardAnswers.maxFiles,
|
|
1889
|
-
max_loc: wizardAnswers.maxLoc,
|
|
1890
|
-
};
|
|
1891
|
-
spec.blast_radius = {
|
|
1892
|
-
modules: wizardAnswers.blastModules
|
|
1893
|
-
.split(',')
|
|
1894
|
-
.map((m) => m.trim())
|
|
1895
|
-
.filter((m) => m),
|
|
1896
|
-
data_migration: wizardAnswers.dataMigration,
|
|
1897
|
-
};
|
|
1898
|
-
spec.operational_rollback_slo = wizardAnswers.rollbackSlo;
|
|
1899
|
-
|
|
1900
|
-
// Add git configuration if provided
|
|
1901
|
-
if (
|
|
1902
|
-
wizardAnswers.configureGit &&
|
|
1903
|
-
wizardAnswers.gitAuthorName &&
|
|
1904
|
-
wizardAnswers.gitAuthorEmail
|
|
1905
|
-
) {
|
|
1906
|
-
spec.git_config = {
|
|
1907
|
-
author_name: wizardAnswers.gitAuthorName,
|
|
1908
|
-
author_email: wizardAnswers.gitAuthorEmail,
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// Validate the generated spec
|
|
1913
|
-
validateGeneratedSpec(yaml.dump(spec), wizardAnswers);
|
|
1914
|
-
|
|
1915
|
-
// Save the working spec
|
|
1916
|
-
await fs.writeFile('.caws/working-spec.yaml', yaml.dump(spec, { indent: 2 }));
|
|
1917
|
-
|
|
1918
|
-
console.log(chalk.green('â
Working spec generated and validated'));
|
|
1919
|
-
|
|
1920
|
-
// Generate getting started guide
|
|
1921
|
-
const wizardAnalysis = {
|
|
1922
|
-
projectType: wizardAnswers.projectType,
|
|
1923
|
-
packageJson: { name: wizardAnswers.projectTitle },
|
|
1924
|
-
hasTests: false,
|
|
1925
|
-
hasLinting: false,
|
|
1926
|
-
hasCi: false,
|
|
1927
|
-
};
|
|
1928
|
-
|
|
1929
|
-
const guideContent = generateGettingStartedGuide(wizardAnalysis);
|
|
1930
|
-
await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
|
|
1931
|
-
console.log(chalk.green('â
Getting started guide created'));
|
|
1932
|
-
|
|
1933
|
-
// Generate or update .gitignore with CAWS patterns
|
|
1934
|
-
const existingGitignore = fs.existsSync('.gitignore')
|
|
1935
|
-
? fs.readFileSync('.gitignore', 'utf8')
|
|
1936
|
-
: '';
|
|
1937
|
-
|
|
1938
|
-
const updatedGitignore = generateGitignorePatterns(existingGitignore);
|
|
1939
|
-
if (updatedGitignore !== existingGitignore) {
|
|
1940
|
-
await fs.writeFile('.gitignore', updatedGitignore);
|
|
1941
|
-
const action = existingGitignore.trim() ? 'updated' : 'created';
|
|
1942
|
-
console.log(chalk.green(`â
.gitignore ${action} with CAWS patterns`));
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
// Finalize project with provenance and git initialization
|
|
1946
|
-
await finalizeProject(projectName, options, wizardAnswers);
|
|
1947
|
-
|
|
1948
|
-
continueToSuccess();
|
|
1949
|
-
return;
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// Handle template-based setup
|
|
1953
|
-
if (options.template) {
|
|
1954
|
-
console.log(chalk.cyan(`đ¯ Using ${options.template} template`));
|
|
1955
|
-
|
|
1956
|
-
const validTemplates = ['extension', 'library', 'api', 'cli', 'monorepo'];
|
|
1957
|
-
if (!validTemplates.includes(options.template)) {
|
|
1958
|
-
console.error(chalk.red(`â Invalid template: ${options.template}`));
|
|
1959
|
-
console.error(chalk.blue(`đĄ Valid templates: ${validTemplates.join(', ')}`));
|
|
1960
|
-
process.exit(1);
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
const analysis = {
|
|
1964
|
-
projectType: options.template,
|
|
1965
|
-
packageJson: { name: projectName },
|
|
1966
|
-
hasTests: false,
|
|
1967
|
-
hasLinting: false,
|
|
1968
|
-
hasCi: false,
|
|
1969
|
-
};
|
|
1970
|
-
|
|
1971
|
-
const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
|
|
1972
|
-
|
|
1973
|
-
// Validate the generated spec
|
|
1974
|
-
validateGeneratedSpec(workingSpecContent, { projectType: options.template });
|
|
1975
|
-
|
|
1976
|
-
// Save the working spec
|
|
1977
|
-
await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
|
|
1978
|
-
|
|
1979
|
-
console.log(chalk.green('â
Working spec generated from template'));
|
|
1980
|
-
|
|
1981
|
-
// Generate getting started guide
|
|
1982
|
-
const templateAnalysis = {
|
|
1983
|
-
projectType: options.template,
|
|
1984
|
-
packageJson: { name: projectName },
|
|
1985
|
-
hasTests: false,
|
|
1986
|
-
hasLinting: false,
|
|
1987
|
-
hasCi: false,
|
|
1988
|
-
};
|
|
1989
|
-
|
|
1990
|
-
const guideContent = generateGettingStartedGuide(templateAnalysis);
|
|
1991
|
-
await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
|
|
1992
|
-
console.log(chalk.green('â
Getting started guide created'));
|
|
1993
|
-
|
|
1994
|
-
// Generate or update .gitignore with CAWS patterns
|
|
1995
|
-
const existingGitignore = fs.existsSync('.gitignore')
|
|
1996
|
-
? fs.readFileSync('.gitignore', 'utf8')
|
|
1997
|
-
: '';
|
|
1998
|
-
|
|
1999
|
-
const updatedGitignore = generateGitignorePatterns(existingGitignore);
|
|
2000
|
-
if (updatedGitignore !== existingGitignore) {
|
|
2001
|
-
await fs.writeFile('.gitignore', updatedGitignore);
|
|
2002
|
-
const action = existingGitignore.trim() ? 'updated' : 'created';
|
|
2003
|
-
console.log(chalk.green(`â
.gitignore ${action} with CAWS patterns`));
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
// Finalize project
|
|
2007
|
-
await finalizeProject(projectName, options, { projectType: options.template });
|
|
2008
|
-
|
|
2009
|
-
continueToSuccess();
|
|
2010
|
-
return;
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
// Set default answers for non-interactive mode
|
|
2014
|
-
if (!options.interactive || options.nonInteractive) {
|
|
2015
|
-
// Use directory name for current directory init
|
|
2016
|
-
const displayName = initInCurrentDir ? path.basename(process.cwd()) : projectName;
|
|
2017
|
-
|
|
2018
|
-
answers = {
|
|
2019
|
-
projectId: displayName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
|
|
2020
|
-
projectTitle: displayName.charAt(0).toUpperCase() + displayName.slice(1).replace(/-/g, ' '),
|
|
2021
|
-
riskTier: 2,
|
|
2022
|
-
projectMode: 'feature',
|
|
2023
|
-
maxFiles: 25,
|
|
2024
|
-
maxLoc: 1000,
|
|
2025
|
-
blastModules: 'core,ui',
|
|
2026
|
-
dataMigration: false,
|
|
2027
|
-
rollbackSlo: '5m',
|
|
2028
|
-
projectThreats: 'Standard project threats',
|
|
2029
|
-
scopeIn: 'project files',
|
|
2030
|
-
scopeOut: 'external dependencies',
|
|
2031
|
-
projectInvariants: 'System maintains consistency',
|
|
2032
|
-
acceptanceCriteria: 'GIVEN current state WHEN action THEN expected result',
|
|
2033
|
-
a11yRequirements: 'keyboard navigation, screen reader support',
|
|
2034
|
-
perfBudget: 250,
|
|
2035
|
-
securityRequirements: 'input validation, authentication',
|
|
2036
|
-
contractType: 'openapi',
|
|
2037
|
-
contractPath: 'apps/contracts/api.yaml',
|
|
2038
|
-
observabilityLogs: 'auth.success,api.request',
|
|
2039
|
-
observabilityMetrics: 'requests_total',
|
|
2040
|
-
observabilityTraces: 'api_flow',
|
|
2041
|
-
migrationPlan: 'Standard deployment process',
|
|
2042
|
-
rollbackPlan: 'Feature flag disable and rollback',
|
|
2043
|
-
needsOverride: false,
|
|
2044
|
-
overrideRationale: '',
|
|
2045
|
-
overrideApprover: '',
|
|
2046
|
-
waivedGates: [],
|
|
2047
|
-
overrideExpiresDays: 7,
|
|
2048
|
-
isExperimental: false,
|
|
2049
|
-
experimentalRationale: '',
|
|
2050
|
-
experimentalSandbox: 'experimental/',
|
|
2051
|
-
experimentalExpiresDays: 14,
|
|
2052
|
-
aiConfidence: 7,
|
|
2053
|
-
uncertaintyAreas: '',
|
|
2054
|
-
complexityFactors: '',
|
|
2055
|
-
};
|
|
2056
|
-
|
|
2057
|
-
// Generate working spec for non-interactive mode
|
|
2058
|
-
const workingSpecContent = generateWorkingSpec(answers);
|
|
2059
|
-
|
|
2060
|
-
// Validate the generated spec
|
|
2061
|
-
validateGeneratedSpec(workingSpecContent, answers);
|
|
2062
|
-
|
|
2063
|
-
// Save the working spec
|
|
2064
|
-
await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
|
|
2065
|
-
|
|
2066
|
-
console.log(chalk.green('â
Working spec generated and validated'));
|
|
2067
|
-
|
|
2068
|
-
// Generate getting started guide (detect project type)
|
|
2069
|
-
const detectedType = detectProjectType(process.cwd());
|
|
2070
|
-
const defaultAnalysis = {
|
|
2071
|
-
projectType: detectedType,
|
|
2072
|
-
packageJson: { name: displayName },
|
|
2073
|
-
hasTests: false,
|
|
2074
|
-
hasLinting: false,
|
|
2075
|
-
hasCi: false,
|
|
2076
|
-
};
|
|
2077
|
-
|
|
2078
|
-
const guideContent = generateGettingStartedGuide(defaultAnalysis);
|
|
2079
|
-
await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
|
|
2080
|
-
console.log(chalk.green('â
Getting started guide created'));
|
|
2081
|
-
|
|
2082
|
-
// Generate or update .gitignore with CAWS patterns
|
|
2083
|
-
const existingGitignore = fs.existsSync('.gitignore')
|
|
2084
|
-
? fs.readFileSync('.gitignore', 'utf8')
|
|
2085
|
-
: '';
|
|
2086
|
-
|
|
2087
|
-
const updatedGitignore = generateGitignorePatterns(existingGitignore);
|
|
2088
|
-
if (updatedGitignore !== existingGitignore) {
|
|
2089
|
-
await fs.writeFile('.gitignore', updatedGitignore);
|
|
2090
|
-
const action = existingGitignore.trim() ? 'updated' : 'created';
|
|
2091
|
-
console.log(chalk.green(`â
.gitignore ${action} with CAWS patterns`));
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
// Finalize project with provenance and git initialization
|
|
2095
|
-
await finalizeProject(projectName, options, answers);
|
|
2096
|
-
|
|
2097
|
-
continueToSuccess();
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
if (options.interactive && !options.nonInteractive) {
|
|
2102
|
-
// Interactive setup with enhanced prompts
|
|
2103
|
-
console.log(chalk.cyan('đ§ Starting interactive project configuration...'));
|
|
2104
|
-
console.log(chalk.blue('đĄ Press Ctrl+C at any time to exit and use defaults'));
|
|
2105
|
-
|
|
2106
|
-
const questions = [
|
|
2107
|
-
{
|
|
2108
|
-
type: 'input',
|
|
2109
|
-
name: 'projectId',
|
|
2110
|
-
message: 'đ Project ID (e.g., FEAT-1234, AUTH-456):',
|
|
2111
|
-
default: projectName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
|
|
2112
|
-
validate: (input) => {
|
|
2113
|
-
if (!input.trim()) return 'Project ID is required';
|
|
2114
|
-
const pattern = /^[A-Z]+-\d+$/;
|
|
2115
|
-
if (!pattern.test(input)) {
|
|
2116
|
-
return 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)';
|
|
2117
|
-
}
|
|
2118
|
-
return true;
|
|
2119
|
-
},
|
|
2120
|
-
},
|
|
2121
|
-
{
|
|
2122
|
-
type: 'input',
|
|
2123
|
-
name: 'projectTitle',
|
|
2124
|
-
message: 'đ Project Title (descriptive name):',
|
|
2125
|
-
default: projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
|
|
2126
|
-
validate: (input) => {
|
|
2127
|
-
if (!input.trim()) return 'Project title is required';
|
|
2128
|
-
if (input.trim().length < 8) {
|
|
2129
|
-
return 'Project title should be at least 8 characters long';
|
|
2130
|
-
}
|
|
2131
|
-
return true;
|
|
2132
|
-
},
|
|
2133
|
-
},
|
|
2134
|
-
{
|
|
2135
|
-
type: 'list',
|
|
2136
|
-
name: 'riskTier',
|
|
2137
|
-
message: 'â ī¸ Risk Tier (higher tier = more rigor):',
|
|
2138
|
-
choices: [
|
|
2139
|
-
{
|
|
2140
|
-
name: 'đ´ Tier 1 - Critical (auth, billing, migrations) - Max rigor',
|
|
2141
|
-
value: 1,
|
|
2142
|
-
},
|
|
2143
|
-
{
|
|
2144
|
-
name: 'đĄ Tier 2 - Standard (features, APIs) - Standard rigor',
|
|
2145
|
-
value: 2,
|
|
2146
|
-
},
|
|
2147
|
-
{
|
|
2148
|
-
name: 'đĸ Tier 3 - Low Risk (UI, tooling) - Basic rigor',
|
|
2149
|
-
value: 3,
|
|
2150
|
-
},
|
|
2151
|
-
],
|
|
2152
|
-
default: 2,
|
|
2153
|
-
},
|
|
2154
|
-
{
|
|
2155
|
-
type: 'list',
|
|
2156
|
-
name: 'projectMode',
|
|
2157
|
-
message: 'đ¯ Project Mode:',
|
|
2158
|
-
choices: [
|
|
2159
|
-
{ name: '⨠feature (new functionality)', value: 'feature' },
|
|
2160
|
-
{ name: 'đ refactor (code restructuring)', value: 'refactor' },
|
|
2161
|
-
{ name: 'đ fix (bug fixes)', value: 'fix' },
|
|
2162
|
-
{ name: 'đ doc (documentation)', value: 'doc' },
|
|
2163
|
-
{ name: 'đ§š chore (maintenance)', value: 'chore' },
|
|
2164
|
-
],
|
|
2165
|
-
default: 'feature',
|
|
2166
|
-
},
|
|
2167
|
-
{
|
|
2168
|
-
type: 'number',
|
|
2169
|
-
name: 'maxFiles',
|
|
2170
|
-
message: 'đ Max files to change:',
|
|
2171
|
-
default: (answers) => {
|
|
2172
|
-
// Dynamic defaults based on risk tier
|
|
2173
|
-
switch (answers.riskTier) {
|
|
2174
|
-
case 1:
|
|
2175
|
-
return 40;
|
|
2176
|
-
case 2:
|
|
2177
|
-
return 25;
|
|
2178
|
-
case 3:
|
|
2179
|
-
return 15;
|
|
2180
|
-
default:
|
|
2181
|
-
return 25;
|
|
2182
|
-
}
|
|
2183
|
-
},
|
|
2184
|
-
validate: (input) => {
|
|
2185
|
-
if (input < 1) return 'Must change at least 1 file';
|
|
2186
|
-
return true;
|
|
2187
|
-
},
|
|
2188
|
-
},
|
|
2189
|
-
{
|
|
2190
|
-
type: 'number',
|
|
2191
|
-
name: 'maxLoc',
|
|
2192
|
-
message: 'đ Max lines of code to change:',
|
|
2193
|
-
default: (answers) => {
|
|
2194
|
-
// Dynamic defaults based on risk tier
|
|
2195
|
-
switch (answers.riskTier) {
|
|
2196
|
-
case 1:
|
|
2197
|
-
return 1500;
|
|
2198
|
-
case 2:
|
|
2199
|
-
return 1000;
|
|
2200
|
-
case 3:
|
|
2201
|
-
return 600;
|
|
2202
|
-
default:
|
|
2203
|
-
return 1000;
|
|
2204
|
-
}
|
|
2205
|
-
},
|
|
2206
|
-
validate: (input) => {
|
|
2207
|
-
if (input < 1) return 'Must change at least 1 line';
|
|
2208
|
-
return true;
|
|
2209
|
-
},
|
|
2210
|
-
},
|
|
2211
|
-
{
|
|
2212
|
-
type: 'input',
|
|
2213
|
-
name: 'blastModules',
|
|
2214
|
-
message: 'đĨ Blast Radius - Affected modules (comma-separated):',
|
|
2215
|
-
default: 'core,api',
|
|
2216
|
-
validate: (input) => {
|
|
2217
|
-
if (!input.trim()) return 'At least one module must be specified';
|
|
2218
|
-
return true;
|
|
2219
|
-
},
|
|
2220
|
-
},
|
|
2221
|
-
{
|
|
2222
|
-
type: 'confirm',
|
|
2223
|
-
name: 'dataMigration',
|
|
2224
|
-
message: 'đī¸ Requires data migration?',
|
|
2225
|
-
default: false,
|
|
2226
|
-
},
|
|
2227
|
-
{
|
|
2228
|
-
type: 'input',
|
|
2229
|
-
name: 'rollbackSlo',
|
|
2230
|
-
message: 'âąī¸ Operational rollback SLO (e.g., 5m, 1h, 24h):',
|
|
2231
|
-
default: '5m',
|
|
2232
|
-
validate: (input) => {
|
|
2233
|
-
const pattern = /^([0-9]+m|[0-9]+h|[0-9]+d)$/;
|
|
2234
|
-
if (!pattern.test(input)) {
|
|
2235
|
-
return 'SLO should be in format: NUMBER + m/h/d (e.g., 5m, 1h, 24h)';
|
|
2236
|
-
}
|
|
2237
|
-
return true;
|
|
2238
|
-
},
|
|
2239
|
-
},
|
|
2240
|
-
{
|
|
2241
|
-
type: 'editor',
|
|
2242
|
-
name: 'projectThreats',
|
|
2243
|
-
message: 'â ī¸ Project Threats & Risks (one per line, ESC to finish):',
|
|
2244
|
-
default: (answers) => {
|
|
2245
|
-
const baseThreats =
|
|
2246
|
-
'- Race condition in concurrent operations\n- Performance degradation under load';
|
|
2247
|
-
if (answers.dataMigration) {
|
|
2248
|
-
return baseThreats + '\n- Data migration failure\n- Inconsistent data state';
|
|
2249
|
-
}
|
|
2250
|
-
return baseThreats;
|
|
2251
|
-
},
|
|
2252
|
-
},
|
|
2253
|
-
{
|
|
2254
|
-
type: 'input',
|
|
2255
|
-
name: 'scopeIn',
|
|
2256
|
-
message: "đ¯ Scope IN - What's included (comma-separated):",
|
|
2257
|
-
default: (answers) => {
|
|
2258
|
-
if (answers.projectMode === 'feature') return 'user authentication, api endpoints';
|
|
2259
|
-
if (answers.projectMode === 'refactor') return 'authentication module, user service';
|
|
2260
|
-
if (answers.projectMode === 'fix') return 'error handling, validation';
|
|
2261
|
-
return 'project files';
|
|
2262
|
-
},
|
|
2263
|
-
validate: (input) => {
|
|
2264
|
-
if (!input.trim()) return 'At least one scope item must be specified';
|
|
2265
|
-
return true;
|
|
2266
|
-
},
|
|
2267
|
-
},
|
|
2268
|
-
{
|
|
2269
|
-
type: 'input',
|
|
2270
|
-
name: 'scopeOut',
|
|
2271
|
-
message: "đĢ Scope OUT - What's excluded (comma-separated):",
|
|
2272
|
-
default: (answers) => {
|
|
2273
|
-
if (answers.projectMode === 'feature')
|
|
2274
|
-
return 'legacy authentication, deprecated endpoints';
|
|
2275
|
-
if (answers.projectMode === 'refactor')
|
|
2276
|
-
return 'external dependencies, configuration files';
|
|
2277
|
-
return 'unrelated features';
|
|
2278
|
-
},
|
|
2279
|
-
},
|
|
2280
|
-
{
|
|
2281
|
-
type: 'editor',
|
|
2282
|
-
name: 'projectInvariants',
|
|
2283
|
-
message: 'đĄī¸ System Invariants (one per line, ESC to finish):',
|
|
2284
|
-
default:
|
|
2285
|
-
'- System remains available\n- Data consistency maintained\n- User sessions preserved',
|
|
2286
|
-
},
|
|
2287
|
-
{
|
|
2288
|
-
type: 'editor',
|
|
2289
|
-
name: 'acceptanceCriteria',
|
|
2290
|
-
message: 'â
Acceptance Criteria (GIVEN...WHEN...THEN, one per line, ESC to finish):',
|
|
2291
|
-
default: (answers) => {
|
|
2292
|
-
if (answers.projectMode === 'feature') {
|
|
2293
|
-
return 'GIVEN user is authenticated WHEN accessing protected endpoint THEN access is granted\nGIVEN invalid credentials WHEN attempting login THEN access is denied';
|
|
2294
|
-
}
|
|
2295
|
-
if (answers.projectMode === 'fix') {
|
|
2296
|
-
return 'GIVEN existing functionality WHEN applying fix THEN behavior is preserved\nGIVEN error condition WHEN fix is applied THEN error is resolved';
|
|
2297
|
-
}
|
|
2298
|
-
return 'GIVEN current system state WHEN change is applied THEN expected behavior occurs';
|
|
2299
|
-
},
|
|
2300
|
-
validate: (input) => {
|
|
2301
|
-
if (!input.trim()) return 'At least one acceptance criterion is required';
|
|
2302
|
-
const lines = input
|
|
2303
|
-
.trim()
|
|
2304
|
-
.split('\n')
|
|
2305
|
-
.filter((line) => line.trim());
|
|
2306
|
-
if (lines.length === 0) return 'At least one acceptance criterion is required';
|
|
2307
|
-
return true;
|
|
2308
|
-
},
|
|
2309
|
-
},
|
|
2310
|
-
{
|
|
2311
|
-
type: 'input',
|
|
2312
|
-
name: 'a11yRequirements',
|
|
2313
|
-
message: 'âŋ Accessibility Requirements (comma-separated):',
|
|
2314
|
-
default: 'keyboard navigation, screen reader support, color contrast',
|
|
2315
|
-
},
|
|
2316
|
-
{
|
|
2317
|
-
type: 'number',
|
|
2318
|
-
name: 'perfBudget',
|
|
2319
|
-
message: '⥠Performance Budget (API p95 latency in ms):',
|
|
2320
|
-
default: 250,
|
|
2321
|
-
validate: (input) => {
|
|
2322
|
-
if (input < 1) return 'Performance budget must be at least 1ms';
|
|
2323
|
-
if (input > 10000) return 'Performance budget seems too high (max 10s)';
|
|
2324
|
-
return true;
|
|
2325
|
-
},
|
|
2326
|
-
},
|
|
2327
|
-
{
|
|
2328
|
-
type: 'input',
|
|
2329
|
-
name: 'securityRequirements',
|
|
2330
|
-
message: 'đ Security Requirements (comma-separated):',
|
|
2331
|
-
default: 'input validation, rate limiting, authentication, authorization',
|
|
2332
|
-
},
|
|
2333
|
-
{
|
|
2334
|
-
type: 'list',
|
|
2335
|
-
name: 'contractType',
|
|
2336
|
-
message: 'đ Contract Type:',
|
|
2337
|
-
choices: [
|
|
2338
|
-
{ name: 'OpenAPI (REST APIs)', value: 'openapi' },
|
|
2339
|
-
{ name: 'GraphQL Schema', value: 'graphql' },
|
|
2340
|
-
{ name: 'Protocol Buffers', value: 'proto' },
|
|
2341
|
-
{ name: 'Pact (consumer-driven)', value: 'pact' },
|
|
2342
|
-
],
|
|
2343
|
-
default: 'openapi',
|
|
2344
|
-
},
|
|
2345
|
-
{
|
|
2346
|
-
type: 'input',
|
|
2347
|
-
name: 'contractPath',
|
|
2348
|
-
message: 'đ Contract File Path:',
|
|
2349
|
-
default: (answers) => {
|
|
2350
|
-
if (answers.contractType === 'openapi') return 'apps/contracts/api.yaml';
|
|
2351
|
-
if (answers.contractType === 'graphql') return 'apps/contracts/schema.graphql';
|
|
2352
|
-
if (answers.contractType === 'proto') return 'apps/contracts/service.proto';
|
|
2353
|
-
if (answers.contractType === 'pact') return 'apps/contracts/pacts/';
|
|
2354
|
-
return 'apps/contracts/api.yaml';
|
|
2355
|
-
},
|
|
2356
|
-
},
|
|
2357
|
-
{
|
|
2358
|
-
type: 'input',
|
|
2359
|
-
name: 'observabilityLogs',
|
|
2360
|
-
message: 'đ Observability - Log Events (comma-separated):',
|
|
2361
|
-
default: 'auth.success, auth.failure, api.request, api.response',
|
|
2362
|
-
},
|
|
2363
|
-
{
|
|
2364
|
-
type: 'input',
|
|
2365
|
-
name: 'observabilityMetrics',
|
|
2366
|
-
message: 'đ Observability - Metrics (comma-separated):',
|
|
2367
|
-
default: 'auth_attempts_total, auth_success_total, api_requests_total, api_errors_total',
|
|
2368
|
-
},
|
|
2369
|
-
{
|
|
2370
|
-
type: 'input',
|
|
2371
|
-
name: 'observabilityTraces',
|
|
2372
|
-
message: 'đ Observability - Traces (comma-separated):',
|
|
2373
|
-
default: 'auth_flow, api_request',
|
|
2374
|
-
},
|
|
2375
|
-
{
|
|
2376
|
-
type: 'editor',
|
|
2377
|
-
name: 'migrationPlan',
|
|
2378
|
-
message: 'đ Migration Plan (one per line, ESC to finish):',
|
|
2379
|
-
default: (answers) => {
|
|
2380
|
-
if (answers.dataMigration) {
|
|
2381
|
-
return '- Create new database schema\n- Add new auth table\n- Migrate existing users\n- Validate data integrity';
|
|
2382
|
-
}
|
|
2383
|
-
return '- Deploy feature flags\n- Roll out gradually\n- Monitor metrics';
|
|
2384
|
-
},
|
|
2385
|
-
validate: (input) => {
|
|
2386
|
-
if (!input.trim()) return 'Migration plan is required';
|
|
2387
|
-
return true;
|
|
2388
|
-
},
|
|
2389
|
-
},
|
|
2390
|
-
{
|
|
2391
|
-
type: 'editor',
|
|
2392
|
-
name: 'rollbackPlan',
|
|
2393
|
-
message: 'đ Rollback Plan (one per line, ESC to finish):',
|
|
2394
|
-
default: (answers) => {
|
|
2395
|
-
if (answers.dataMigration) {
|
|
2396
|
-
return '- Feature flag kill-switch\n- Database rollback script\n- Restore from backup\n- Verify system state';
|
|
2397
|
-
}
|
|
2398
|
-
return '- Feature flag disable\n- Deploy previous version\n- Monitor for issues';
|
|
2399
|
-
},
|
|
2400
|
-
validate: (input) => {
|
|
2401
|
-
if (!input.trim()) return 'Rollback plan is required';
|
|
2402
|
-
return true;
|
|
2403
|
-
},
|
|
2404
|
-
},
|
|
2405
|
-
{
|
|
2406
|
-
type: 'confirm',
|
|
2407
|
-
name: 'needsOverride',
|
|
2408
|
-
message: 'đ¨ Need human override for urgent/low-risk change?',
|
|
2409
|
-
default: false,
|
|
2410
|
-
},
|
|
2411
|
-
{
|
|
2412
|
-
type: 'input',
|
|
2413
|
-
name: 'overrideRationale',
|
|
2414
|
-
message: 'đ Override rationale (urgency, low risk, etc.):',
|
|
2415
|
-
when: (answers) => answers.needsOverride,
|
|
2416
|
-
validate: (input) => {
|
|
2417
|
-
if (!input.trim()) return 'Rationale is required for override';
|
|
2418
|
-
return true;
|
|
2419
|
-
},
|
|
2420
|
-
},
|
|
2421
|
-
{
|
|
2422
|
-
type: 'input',
|
|
2423
|
-
name: 'overrideApprover',
|
|
2424
|
-
message: 'đ¤ Override approver (GitHub username or email):',
|
|
2425
|
-
when: (answers) => answers.needsOverride,
|
|
2426
|
-
validate: (input) => {
|
|
2427
|
-
if (!input.trim()) return 'Approver is required for override';
|
|
2428
|
-
return true;
|
|
2429
|
-
},
|
|
2430
|
-
},
|
|
2431
|
-
{
|
|
2432
|
-
type: 'checkbox',
|
|
2433
|
-
name: 'waivedGates',
|
|
2434
|
-
message: 'â ī¸ Gates to waive (select with space):',
|
|
2435
|
-
choices: [
|
|
2436
|
-
{ name: 'Coverage testing', value: 'coverage' },
|
|
2437
|
-
{ name: 'Mutation testing', value: 'mutation' },
|
|
2438
|
-
{ name: 'Contract testing', value: 'contracts' },
|
|
2439
|
-
{ name: 'Manual review', value: 'manual_review' },
|
|
2440
|
-
{ name: 'Trust score check', value: 'trust_score' },
|
|
2441
|
-
],
|
|
2442
|
-
when: (answers) => answers.needsOverride,
|
|
2443
|
-
validate: (input) => {
|
|
2444
|
-
if (input.length === 0) return 'At least one gate must be waived';
|
|
2445
|
-
return true;
|
|
2446
|
-
},
|
|
2447
|
-
},
|
|
2448
|
-
{
|
|
2449
|
-
type: 'number',
|
|
2450
|
-
name: 'overrideExpiresDays',
|
|
2451
|
-
message: 'â° Override expires in how many days?',
|
|
2452
|
-
default: 7,
|
|
2453
|
-
when: (answers) => answers.needsOverride,
|
|
2454
|
-
validate: (input) => {
|
|
2455
|
-
if (input < 1) return 'Must expire in at least 1 day';
|
|
2456
|
-
if (input > 30) return 'Cannot exceed 30 days';
|
|
2457
|
-
return true;
|
|
2458
|
-
},
|
|
2459
|
-
},
|
|
2460
|
-
{
|
|
2461
|
-
type: 'confirm',
|
|
2462
|
-
name: 'isExperimental',
|
|
2463
|
-
message: 'đ§Ē Experimental/Prototype mode? (Reduced requirements for sandbox code)',
|
|
2464
|
-
default: false,
|
|
2465
|
-
},
|
|
2466
|
-
{
|
|
2467
|
-
type: 'input',
|
|
2468
|
-
name: 'experimentalRationale',
|
|
2469
|
-
message: 'đŦ Experimental rationale (what are you exploring?):',
|
|
2470
|
-
when: (answers) => answers.isExperimental,
|
|
2471
|
-
validate: (input) => {
|
|
2472
|
-
if (!input.trim()) return 'Rationale is required for experimental mode';
|
|
2473
|
-
return true;
|
|
2474
|
-
},
|
|
2475
|
-
},
|
|
2476
|
-
{
|
|
2477
|
-
type: 'input',
|
|
2478
|
-
name: 'experimentalSandbox',
|
|
2479
|
-
message: 'đ Sandbox location (directory or feature flag):',
|
|
2480
|
-
default: 'experimental/',
|
|
2481
|
-
when: (answers) => answers.isExperimental,
|
|
2482
|
-
validate: (input) => {
|
|
2483
|
-
if (!input.trim()) return 'Sandbox location is required';
|
|
2484
|
-
return true;
|
|
2485
|
-
},
|
|
2486
|
-
},
|
|
2487
|
-
{
|
|
2488
|
-
type: 'number',
|
|
2489
|
-
name: 'experimentalExpiresDays',
|
|
2490
|
-
message: 'â° Experimental code expires in how many days?',
|
|
2491
|
-
default: 14,
|
|
2492
|
-
when: (answers) => answers.isExperimental,
|
|
2493
|
-
validate: (input) => {
|
|
2494
|
-
if (input < 1) return 'Must expire in at least 1 day';
|
|
2495
|
-
if (input > 90) return 'Cannot exceed 90 days for experimental code';
|
|
2496
|
-
return true;
|
|
2497
|
-
},
|
|
2498
|
-
},
|
|
2499
|
-
{
|
|
2500
|
-
type: 'number',
|
|
2501
|
-
name: 'aiConfidence',
|
|
2502
|
-
message: 'đ¤ AI confidence level (1-10, 10 = very confident):',
|
|
2503
|
-
default: 7,
|
|
2504
|
-
validate: (input) => {
|
|
2505
|
-
if (input < 1 || input > 10) return 'Must be between 1 and 10';
|
|
2506
|
-
return true;
|
|
2507
|
-
},
|
|
2508
|
-
},
|
|
2509
|
-
{
|
|
2510
|
-
type: 'input',
|
|
2511
|
-
name: 'uncertaintyAreas',
|
|
2512
|
-
message: 'â Areas of uncertainty (comma-separated):',
|
|
2513
|
-
default: '',
|
|
2514
|
-
},
|
|
2515
|
-
{
|
|
2516
|
-
type: 'input',
|
|
2517
|
-
name: 'complexityFactors',
|
|
2518
|
-
message: 'đ§ Complexity factors (comma-separated):',
|
|
2519
|
-
default: '',
|
|
2520
|
-
},
|
|
2521
|
-
];
|
|
2522
|
-
|
|
2523
|
-
console.log(chalk.cyan('âŗ Gathering project requirements...'));
|
|
2524
|
-
|
|
2525
|
-
let answers;
|
|
2526
|
-
try {
|
|
2527
|
-
answers = await inquirer.prompt(questions);
|
|
2528
|
-
} catch (error) {
|
|
2529
|
-
if (error.isTtyError) {
|
|
2530
|
-
console.error(chalk.red('â Interactive prompts not supported in this environment'));
|
|
2531
|
-
console.error(chalk.blue('đĄ Run with --non-interactive flag to use defaults'));
|
|
2532
|
-
process.exit(1);
|
|
2533
|
-
} else {
|
|
2534
|
-
console.error(chalk.red('â Error during interactive setup:'), error.message);
|
|
2535
|
-
process.exit(1);
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
|
|
2539
|
-
console.log(chalk.green('â
Project requirements gathered successfully!'));
|
|
2540
|
-
|
|
2541
|
-
// Show summary before generating spec
|
|
2542
|
-
console.log(chalk.bold('\nđ Configuration Summary:'));
|
|
2543
|
-
console.log(` ${chalk.cyan('Project')}: ${answers.projectTitle} (${answers.projectId})`);
|
|
2544
|
-
console.log(
|
|
2545
|
-
` ${chalk.cyan('Mode')}: ${answers.projectMode} | ${chalk.cyan('Tier')}: ${answers.riskTier}`
|
|
2546
|
-
);
|
|
2547
|
-
console.log(` ${chalk.cyan('Budget')}: ${answers.maxFiles} files, ${answers.maxLoc} lines`);
|
|
2548
|
-
console.log(` ${chalk.cyan('Data Migration')}: ${answers.dataMigration ? 'Yes' : 'No'}`);
|
|
2549
|
-
console.log(` ${chalk.cyan('Rollback SLO')}: ${answers.rollbackSlo}`);
|
|
2550
|
-
|
|
2551
|
-
// Generate working spec
|
|
2552
|
-
const workingSpecContent = generateWorkingSpec(answers);
|
|
2553
|
-
|
|
2554
|
-
// Validate the generated spec
|
|
2555
|
-
validateGeneratedSpec(workingSpecContent, answers);
|
|
2556
|
-
|
|
2557
|
-
// Save the working spec
|
|
2558
|
-
await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
|
|
2559
|
-
|
|
2560
|
-
console.log(chalk.green('â
Working spec generated and validated'));
|
|
2561
|
-
|
|
2562
|
-
// Finalize project with provenance and git initialization
|
|
2563
|
-
await finalizeProject(projectName, options, answers);
|
|
2564
|
-
|
|
2565
|
-
continueToSuccess();
|
|
2566
|
-
}
|
|
2567
|
-
} catch (error) {
|
|
2568
|
-
console.error(chalk.red('â Error during project initialization:'), error.message);
|
|
2569
|
-
|
|
2570
|
-
// Cleanup on error
|
|
2571
|
-
if (fs.existsSync(projectName)) {
|
|
2572
|
-
console.log(chalk.cyan('đ§š Cleaning up failed initialization...'));
|
|
2573
|
-
try {
|
|
2574
|
-
await fs.remove(projectName);
|
|
2575
|
-
console.log(chalk.green('â
Cleanup completed'));
|
|
2576
|
-
} catch (cleanupError) {
|
|
2577
|
-
console.warn(
|
|
2578
|
-
chalk.yellow('â ī¸ Could not clean up:'),
|
|
2579
|
-
cleanupError?.message || cleanupError
|
|
2580
|
-
);
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
process.exit(1);
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
|
|
2588
|
-
// Generate provenance manifest and git initialization (for both modes)
|
|
2589
|
-
async function finalizeProject(projectName, options, answers) {
|
|
2590
|
-
try {
|
|
2591
|
-
// Detect and configure language support
|
|
2592
|
-
if (languageSupport) {
|
|
2593
|
-
console.log(chalk.cyan('đ Detecting project language...'));
|
|
2594
|
-
const detectedLanguage = languageSupport.detectProjectLanguage();
|
|
2595
|
-
|
|
2596
|
-
if (detectedLanguage !== 'unknown') {
|
|
2597
|
-
console.log(chalk.green(`â
Detected language: ${detectedLanguage}`));
|
|
2598
|
-
|
|
2599
|
-
// Generate language-specific configuration
|
|
2600
|
-
try {
|
|
2601
|
-
const langConfig = languageSupport.generateLanguageConfig(
|
|
2602
|
-
detectedLanguage,
|
|
2603
|
-
'.caws/language-config.json'
|
|
2604
|
-
);
|
|
2605
|
-
|
|
2606
|
-
console.log(chalk.green('â
Generated language-specific configuration'));
|
|
2607
|
-
console.log(` Language: ${langConfig.name}`);
|
|
2608
|
-
console.log(` Tier: ${langConfig.tier}`);
|
|
2609
|
-
console.log(
|
|
2610
|
-
` Thresholds: Branch âĨ${langConfig.thresholds.min_branch * 100}%, Mutation âĨ${langConfig.thresholds.min_mutation * 100}%`
|
|
2611
|
-
);
|
|
2612
|
-
} catch (langError) {
|
|
2613
|
-
console.warn(chalk.yellow('â ī¸ Could not generate language config:'), langError.message);
|
|
2614
|
-
}
|
|
2615
|
-
} else {
|
|
2616
|
-
console.log(
|
|
2617
|
-
chalk.blue('âšī¸ Could not detect project language - using default configuration')
|
|
2618
|
-
);
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
// Setup Cursor hooks if enabled
|
|
2623
|
-
if (answers && answers.enableCursorHooks) {
|
|
2624
|
-
console.log(chalk.cyan('đ Setting up Cursor hooks...'));
|
|
2625
|
-
await scaffoldCursorHooks(
|
|
2626
|
-
process.cwd(),
|
|
2627
|
-
answers.cursorHookLevels || ['safety', 'quality', 'scope', 'audit']
|
|
2628
|
-
);
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
// Generate provenance manifest
|
|
2632
|
-
console.log(chalk.cyan('đĻ Generating provenance manifest...'));
|
|
2633
|
-
|
|
2634
|
-
const provenanceData = {
|
|
2635
|
-
agent: 'caws-cli',
|
|
2636
|
-
model: 'cli-interactive',
|
|
2637
|
-
modelHash: CLI_VERSION,
|
|
2638
|
-
toolAllowlist: [
|
|
2639
|
-
'node',
|
|
2640
|
-
'npm',
|
|
2641
|
-
'git',
|
|
2642
|
-
'fs-extra',
|
|
2643
|
-
'inquirer',
|
|
2644
|
-
'commander',
|
|
2645
|
-
'js-yaml',
|
|
2646
|
-
'ajv',
|
|
2647
|
-
'chalk',
|
|
2648
|
-
],
|
|
2649
|
-
prompts: Object.keys(answers),
|
|
2650
|
-
commit: null, // Will be set after git init
|
|
2651
|
-
artifacts: ['.caws/working-spec.yaml'],
|
|
2652
|
-
results: {
|
|
2653
|
-
project_id: answers.projectId,
|
|
2654
|
-
project_title: answers.projectTitle,
|
|
2655
|
-
risk_tier: answers.riskTier,
|
|
2656
|
-
mode: answers.projectMode,
|
|
2657
|
-
change_budget: {
|
|
2658
|
-
max_files: answers.maxFiles,
|
|
2659
|
-
max_loc: answers.maxLoc,
|
|
2660
|
-
},
|
|
2661
|
-
},
|
|
2662
|
-
approvals: [],
|
|
2663
|
-
};
|
|
2664
|
-
|
|
2665
|
-
// Generate provenance if tools are available
|
|
2666
|
-
const tools = loadProvenanceTools();
|
|
2667
|
-
if (
|
|
2668
|
-
tools &&
|
|
2669
|
-
typeof tools.generateProvenance === 'function' &&
|
|
2670
|
-
typeof tools.saveProvenance === 'function'
|
|
2671
|
-
) {
|
|
2672
|
-
const provenance = tools.generateProvenance(provenanceData);
|
|
2673
|
-
await tools.saveProvenance(provenance, '.agent/provenance.json');
|
|
2674
|
-
console.log(chalk.green('â
Provenance manifest generated'));
|
|
2675
|
-
} else {
|
|
2676
|
-
console.log(
|
|
2677
|
-
chalk.yellow('â ī¸ Provenance tools not available - skipping manifest generation')
|
|
2678
|
-
);
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
// Initialize git repository
|
|
2682
|
-
if (options.git) {
|
|
2683
|
-
try {
|
|
2684
|
-
console.log(chalk.cyan('đ§ Initializing git repository...'));
|
|
2685
|
-
|
|
2686
|
-
// Check if git is available
|
|
2687
|
-
try {
|
|
2688
|
-
require('child_process').execSync('git --version', { stdio: 'ignore' });
|
|
2689
|
-
} catch (error) {
|
|
2690
|
-
console.warn(chalk.yellow('â ī¸ Git not found. Skipping git initialization.'));
|
|
2691
|
-
console.warn(chalk.blue('đĄ Install git to enable automatic repository setup.'));
|
|
2692
|
-
return;
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
// Configure git author information
|
|
2696
|
-
const gitConfig = answers.git_config || {};
|
|
2697
|
-
const authorName = process.env.GIT_AUTHOR_NAME || gitConfig.author_name;
|
|
2698
|
-
const authorEmail = process.env.GIT_AUTHOR_EMAIL || gitConfig.author_email;
|
|
2699
|
-
|
|
2700
|
-
if (authorName && authorEmail) {
|
|
2701
|
-
require('child_process').execSync(`git config user.name "${authorName}"`, {
|
|
2702
|
-
stdio: 'inherit',
|
|
2703
|
-
});
|
|
2704
|
-
require('child_process').execSync(`git config user.email "${authorEmail}"`, {
|
|
2705
|
-
stdio: 'inherit',
|
|
2706
|
-
});
|
|
2707
|
-
console.log(chalk.green(`â
Git configured: ${authorName} <${authorEmail}>`));
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
require('child_process').execSync('git init', { stdio: 'inherit' });
|
|
2711
|
-
require('child_process').execSync('git add .', { stdio: 'inherit' });
|
|
2712
|
-
require('child_process').execSync('git commit -m "Initial CAWS project setup"', {
|
|
2713
|
-
stdio: 'inherit',
|
|
2714
|
-
});
|
|
2715
|
-
console.log(chalk.green('â
Git repository initialized'));
|
|
2716
|
-
|
|
2717
|
-
// Update provenance with commit hash
|
|
2718
|
-
const commitHash = require('child_process')
|
|
2719
|
-
.execSync('git rev-parse HEAD', { encoding: 'utf8' })
|
|
2720
|
-
.trim();
|
|
2721
|
-
const currentProvenance = JSON.parse(fs.readFileSync('.agent/provenance.json', 'utf8'));
|
|
2722
|
-
currentProvenance.commit = commitHash;
|
|
2723
|
-
currentProvenance.hash = require('crypto')
|
|
2724
|
-
.createHash('sha256')
|
|
2725
|
-
.update(JSON.stringify(currentProvenance, Object.keys(currentProvenance).sort()))
|
|
2726
|
-
.digest('hex');
|
|
2727
|
-
await fs.writeFile('.agent/provenance.json', JSON.stringify(currentProvenance, null, 2));
|
|
2728
|
-
|
|
2729
|
-
console.log(chalk.green('â
Provenance updated with commit hash'));
|
|
2730
|
-
} catch (error) {
|
|
2731
|
-
console.warn(
|
|
2732
|
-
chalk.yellow('â ī¸ Failed to initialize git repository:'),
|
|
2733
|
-
error?.message || String(error)
|
|
2734
|
-
);
|
|
2735
|
-
console.warn(chalk.blue('đĄ You can initialize git manually later with:'));
|
|
2736
|
-
console.warn(" git init && git add . && git commit -m 'Initial CAWS project setup'");
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
} catch (error) {
|
|
2740
|
-
console.error(
|
|
2741
|
-
chalk.red('â Error during project finalization:'),
|
|
2742
|
-
error?.message || String(error)
|
|
2743
|
-
);
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
|
-
function continueToSuccess() {
|
|
2748
|
-
const isCurrentDir =
|
|
2749
|
-
process.cwd() ===
|
|
2750
|
-
path.resolve(process.argv[3] === '.' ? process.cwd() : process.argv[3] || 'caws-project');
|
|
2751
|
-
|
|
2752
|
-
console.log(chalk.green('\nđ CAWS project initialized successfully!'));
|
|
2753
|
-
|
|
2754
|
-
if (isCurrentDir) {
|
|
2755
|
-
console.log(
|
|
2756
|
-
`đ ${chalk.cyan('Initialized in current directory')}: ${path.resolve(process.cwd())}`
|
|
2757
|
-
);
|
|
2758
|
-
console.log(chalk.gray(' (CAWS files added to your existing project)'));
|
|
2759
|
-
} else {
|
|
2760
|
-
console.log(`đ ${chalk.cyan('Project location')}: ${path.resolve(process.cwd())}`);
|
|
2761
|
-
console.log(chalk.gray(' (New subdirectory created with CAWS structure)'));
|
|
2762
|
-
}
|
|
2763
|
-
|
|
2764
|
-
console.log(chalk.bold('\nNext steps:'));
|
|
2765
|
-
console.log('1. Customize .caws/working-spec.yaml');
|
|
2766
|
-
console.log('2. Review added CAWS tools and documentation');
|
|
2767
|
-
if (!isCurrentDir) {
|
|
2768
|
-
console.log('3. Move CAWS files to your main project if needed');
|
|
2769
|
-
}
|
|
2770
|
-
console.log('4. npm install (if using Node.js)');
|
|
2771
|
-
console.log('5. Set up your CI/CD pipeline');
|
|
2772
|
-
console.log(chalk.blue('\nFor help: caws --help'));
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
/**
|
|
2776
|
-
* Scaffold Cursor hooks for a CAWS project
|
|
2777
|
-
*
|
|
2778
|
-
* @param {string} projectDir - Project directory path
|
|
2779
|
-
* @param {string[]} levels - Hook levels to enable (safety, quality, scope, audit)
|
|
2780
|
-
* @author @darianrosebrook
|
|
2781
|
-
*/
|
|
2782
|
-
async function scaffoldCursorHooks(projectDir, levels = ['safety', 'quality', 'scope', 'audit']) {
|
|
2783
|
-
try {
|
|
2784
|
-
const cursorDir = path.join(projectDir, '.cursor');
|
|
2785
|
-
const cursorHooksDir = path.join(cursorDir, 'hooks');
|
|
2786
|
-
|
|
2787
|
-
// Create .cursor directory structure
|
|
2788
|
-
await fs.ensureDir(cursorDir);
|
|
2789
|
-
await fs.ensureDir(cursorHooksDir);
|
|
2790
|
-
await fs.ensureDir(path.join(cursorDir, 'logs'));
|
|
2791
|
-
|
|
2792
|
-
// Determine template directory
|
|
2793
|
-
const setup = detectCAWSSetup(projectDir);
|
|
2794
|
-
const templateDir = setup.templateDir || path.resolve(__dirname, '../templates');
|
|
2795
|
-
|
|
2796
|
-
const cursorTemplateDir = path.join(templateDir, '.cursor');
|
|
2797
|
-
const cursorHooksTemplateDir = path.join(cursorTemplateDir, 'hooks');
|
|
2798
|
-
|
|
2799
|
-
if (!fs.existsSync(cursorTemplateDir)) {
|
|
2800
|
-
console.warn(chalk.yellow('â ī¸ Cursor hooks templates not found'));
|
|
2801
|
-
console.warn(chalk.blue('đĄ Skipping Cursor hooks setup'));
|
|
2802
|
-
return;
|
|
2803
|
-
}
|
|
2804
|
-
|
|
2805
|
-
// Map levels to hook scripts
|
|
2806
|
-
const hookMapping = {
|
|
2807
|
-
safety: ['scan-secrets.sh', 'block-dangerous.sh'],
|
|
2808
|
-
quality: ['format.sh', 'validate-spec.sh'],
|
|
2809
|
-
scope: ['scope-guard.sh', 'naming-check.sh'],
|
|
2810
|
-
audit: ['audit.sh'],
|
|
2811
|
-
};
|
|
2812
|
-
|
|
2813
|
-
// Determine which hooks to enable
|
|
2814
|
-
const enabledHooks = new Set();
|
|
2815
|
-
levels.forEach((level) => {
|
|
2816
|
-
const hooks = hookMapping[level] || [];
|
|
2817
|
-
hooks.forEach((hook) => enabledHooks.add(hook));
|
|
2818
|
-
});
|
|
2819
|
-
|
|
2820
|
-
// Always enable audit.sh if any hooks are enabled
|
|
2821
|
-
if (enabledHooks.size > 0) {
|
|
2822
|
-
enabledHooks.add('audit.sh');
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2825
|
-
// Copy enabled hook scripts
|
|
2826
|
-
const allHookScripts = [
|
|
2827
|
-
'audit.sh',
|
|
2828
|
-
'validate-spec.sh',
|
|
2829
|
-
'format.sh',
|
|
2830
|
-
'scan-secrets.sh',
|
|
2831
|
-
'block-dangerous.sh',
|
|
2832
|
-
'scope-guard.sh',
|
|
2833
|
-
'naming-check.sh',
|
|
2834
|
-
];
|
|
2835
|
-
|
|
2836
|
-
for (const script of allHookScripts) {
|
|
2837
|
-
if (enabledHooks.has(script)) {
|
|
2838
|
-
const sourcePath = path.join(cursorHooksTemplateDir, script);
|
|
2839
|
-
const destPath = path.join(cursorHooksDir, script);
|
|
2840
|
-
|
|
2841
|
-
if (fs.existsSync(sourcePath)) {
|
|
2842
|
-
await fs.copy(sourcePath, destPath);
|
|
2843
|
-
// Make executable
|
|
2844
|
-
await fs.chmod(destPath, 0o755);
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2848
|
-
|
|
2849
|
-
// Generate hooks.json based on enabled hooks
|
|
2850
|
-
const hooksConfig = {
|
|
2851
|
-
version: 1,
|
|
2852
|
-
hooks: {},
|
|
2853
|
-
};
|
|
2854
|
-
|
|
2855
|
-
// Build hooks configuration based on enabled levels
|
|
2856
|
-
if (levels.includes('safety')) {
|
|
2857
|
-
hooksConfig.hooks.beforeShellExecution = [
|
|
2858
|
-
{ command: './.cursor/hooks/block-dangerous.sh' },
|
|
2859
|
-
{ command: './.cursor/hooks/audit.sh' },
|
|
2860
|
-
];
|
|
2861
|
-
hooksConfig.hooks.beforeMCPExecution = [{ command: './.cursor/hooks/audit.sh' }];
|
|
2862
|
-
hooksConfig.hooks.beforeReadFile = [{ command: './.cursor/hooks/scan-secrets.sh' }];
|
|
2863
|
-
}
|
|
2864
|
-
|
|
2865
|
-
if (levels.includes('quality')) {
|
|
2866
|
-
hooksConfig.hooks.afterFileEdit = hooksConfig.hooks.afterFileEdit || [];
|
|
2867
|
-
hooksConfig.hooks.afterFileEdit.push(
|
|
2868
|
-
{ command: './.cursor/hooks/format.sh' },
|
|
2869
|
-
{ command: './.cursor/hooks/validate-spec.sh' }
|
|
2870
|
-
);
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
if (levels.includes('scope')) {
|
|
2874
|
-
hooksConfig.hooks.afterFileEdit = hooksConfig.hooks.afterFileEdit || [];
|
|
2875
|
-
hooksConfig.hooks.afterFileEdit.push({ command: './.cursor/hooks/naming-check.sh' });
|
|
2876
|
-
hooksConfig.hooks.beforeSubmitPrompt = [
|
|
2877
|
-
{ command: './.cursor/hooks/scope-guard.sh' },
|
|
2878
|
-
{ command: './.cursor/hooks/audit.sh' },
|
|
2879
|
-
];
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
|
-
if (levels.includes('audit')) {
|
|
2883
|
-
// Add audit to all events
|
|
2884
|
-
if (!hooksConfig.hooks.afterFileEdit) {
|
|
2885
|
-
hooksConfig.hooks.afterFileEdit = [];
|
|
2886
|
-
}
|
|
2887
|
-
hooksConfig.hooks.afterFileEdit.push({ command: './.cursor/hooks/audit.sh' });
|
|
2888
|
-
|
|
2889
|
-
hooksConfig.hooks.stop = [{ command: './.cursor/hooks/audit.sh' }];
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
|
-
// Write hooks.json
|
|
2893
|
-
await fs.writeFile(path.join(cursorDir, 'hooks.json'), JSON.stringify(hooksConfig, null, 2));
|
|
2894
|
-
|
|
2895
|
-
// Copy README
|
|
2896
|
-
const readmePath = path.join(cursorTemplateDir, 'README.md');
|
|
2897
|
-
if (fs.existsSync(readmePath)) {
|
|
2898
|
-
await fs.copy(readmePath, path.join(cursorDir, 'README.md'));
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
|
-
console.log(chalk.green('â
Cursor hooks configured'));
|
|
2902
|
-
console.log(chalk.gray(` Enabled: ${levels.join(', ')}`));
|
|
2903
|
-
console.log(
|
|
2904
|
-
chalk.gray(` Scripts: ${Array.from(enabledHooks).length} hook scripts installed`)
|
|
2905
|
-
);
|
|
2906
|
-
console.log(chalk.blue('đĄ Restart Cursor to activate hooks'));
|
|
2907
|
-
} catch (error) {
|
|
2908
|
-
console.error(chalk.yellow('â ī¸ Failed to setup Cursor hooks:'), error.message);
|
|
2909
|
-
console.log(chalk.blue('đĄ You can manually copy .cursor/ directory later'));
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
/**
|
|
2914
|
-
* Scaffold existing project with CAWS components
|
|
2915
|
-
*/
|
|
2916
|
-
async function scaffoldProject(options) {
|
|
2917
|
-
const currentDir = process.cwd();
|
|
2918
|
-
const projectName = path.basename(currentDir);
|
|
2919
|
-
|
|
2920
|
-
try {
|
|
2921
|
-
// Detect existing CAWS setup FIRST before any logging
|
|
2922
|
-
const setup = detectCAWSSetup(currentDir);
|
|
2923
|
-
|
|
2924
|
-
// Check for CAWS setup immediately and exit with helpful message if not found
|
|
2925
|
-
if (!setup.hasCAWSDir) {
|
|
2926
|
-
console.log(chalk.red('â CAWS not initialized in this project'));
|
|
2927
|
-
console.log(chalk.blue('\nđĄ To get started:'));
|
|
2928
|
-
console.log(` 1. Initialize CAWS: ${chalk.cyan('caws init <project-name>')}`);
|
|
2929
|
-
console.log(` 2. Or initialize in current directory: ${chalk.cyan('caws init .')}`);
|
|
2930
|
-
console.log(chalk.blue('\nđ For more help:'));
|
|
2931
|
-
console.log(` ${chalk.cyan('caws --help')}`);
|
|
2932
|
-
process.exit(1);
|
|
2933
|
-
}
|
|
2934
|
-
|
|
2935
|
-
console.log(chalk.cyan(`đ§ Enhancing existing CAWS project: ${projectName}`));
|
|
2936
|
-
|
|
2937
|
-
// Preserve the original template directory from global cawsSetup
|
|
2938
|
-
// (needed because detectCAWSSetup from within a new project won't find the template)
|
|
2939
|
-
if (cawsSetup?.templateDir && !setup.templateDir) {
|
|
2940
|
-
setup.templateDir = cawsSetup.templateDir;
|
|
2941
|
-
setup.hasTemplateDir = true;
|
|
2942
|
-
} else if (!setup.templateDir) {
|
|
2943
|
-
// Try to find template directory using absolute paths that work in CI
|
|
2944
|
-
const possiblePaths = [
|
|
2945
|
-
'/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
|
|
2946
|
-
'/workspace/packages/caws-template',
|
|
2947
|
-
'/caws/packages/caws-template',
|
|
2948
|
-
path.resolve(process.cwd(), '../../../packages/caws-template'),
|
|
2949
|
-
path.resolve(process.cwd(), '../../packages/caws-template'),
|
|
2950
|
-
path.resolve(process.cwd(), '../packages/caws-template'),
|
|
2951
|
-
];
|
|
2952
|
-
|
|
2953
|
-
for (const testPath of possiblePaths) {
|
|
2954
|
-
if (fs.existsSync(testPath)) {
|
|
2955
|
-
setup.templateDir = testPath;
|
|
2956
|
-
setup.hasTemplateDir = true;
|
|
2957
|
-
break;
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
if (!setup.templateDir) {
|
|
2962
|
-
console.log(chalk.red(`â No template directory available!`));
|
|
2963
|
-
console.log(chalk.blue('đĄ To fix this issue:'));
|
|
2964
|
-
console.log(` 1. Ensure caws-template package is installed`);
|
|
2965
|
-
console.log(` 2. Run from the monorepo root directory`);
|
|
2966
|
-
console.log(` 3. Check that CAWS CLI was installed correctly`);
|
|
2967
|
-
console.log(chalk.blue('\nđ For installation help:'));
|
|
2968
|
-
console.log(` ${chalk.cyan('npm install -g @paths.design/caws-cli')}`);
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
// Override global cawsSetup with current context for scaffold operations
|
|
2973
|
-
cawsSetup = setup;
|
|
2974
|
-
|
|
2975
|
-
if (!setup.hasCAWSDir) {
|
|
2976
|
-
console.error(chalk.red('â No .caws directory found'));
|
|
2977
|
-
console.error(chalk.blue('đĄ Run "caws init <project-name>" first to create a CAWS project'));
|
|
2978
|
-
process.exit(1);
|
|
2979
|
-
}
|
|
2980
|
-
|
|
2981
|
-
// Adapt behavior based on setup type
|
|
2982
|
-
if (setup.isEnhanced) {
|
|
2983
|
-
console.log(chalk.green('đ¯ Enhanced CAWS detected - adding automated publishing'));
|
|
2984
|
-
} else if (setup.isAdvanced) {
|
|
2985
|
-
console.log(chalk.blue('đ§ Advanced CAWS detected - adding missing capabilities'));
|
|
2986
|
-
} else {
|
|
2987
|
-
console.log(chalk.blue('đ Basic CAWS detected - enhancing with additional tools'));
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
// Generate provenance for scaffolding operation
|
|
2991
|
-
const scaffoldProvenance = {
|
|
2992
|
-
agent: 'caws-cli',
|
|
2993
|
-
model: 'cli-scaffold',
|
|
2994
|
-
modelHash: CLI_VERSION,
|
|
2995
|
-
toolAllowlist: ['node', 'fs-extra'],
|
|
2996
|
-
prompts: ['scaffold', options.force ? 'force' : 'normal'],
|
|
2997
|
-
commit: null,
|
|
2998
|
-
artifacts: [],
|
|
2999
|
-
results: {
|
|
3000
|
-
operation: 'scaffold',
|
|
3001
|
-
force_mode: options.force,
|
|
3002
|
-
target_directory: currentDir,
|
|
3003
|
-
},
|
|
3004
|
-
approvals: [],
|
|
3005
|
-
timestamp: new Date().toISOString(),
|
|
3006
|
-
version: CLI_VERSION,
|
|
3007
|
-
};
|
|
3008
|
-
|
|
3009
|
-
// Calculate hash after object is fully defined
|
|
3010
|
-
scaffoldProvenance.hash = require('crypto')
|
|
3011
|
-
.createHash('sha256')
|
|
3012
|
-
.update(JSON.stringify(scaffoldProvenance))
|
|
3013
|
-
.digest('hex');
|
|
3014
|
-
|
|
3015
|
-
// Determine what enhancements to add based on setup type and options
|
|
3016
|
-
const enhancements = [];
|
|
3017
|
-
|
|
3018
|
-
// Add CAWS tools directory structure (matches test expectations)
|
|
3019
|
-
enhancements.push({
|
|
3020
|
-
name: 'apps/tools/caws',
|
|
3021
|
-
description: 'CAWS tools directory',
|
|
3022
|
-
required: true,
|
|
3023
|
-
});
|
|
3024
|
-
|
|
3025
|
-
// Add codemods if requested or not minimal
|
|
3026
|
-
if (options.withCodemods || (!options.minimal && !options.withCodemods)) {
|
|
3027
|
-
enhancements.push({
|
|
3028
|
-
name: 'codemod',
|
|
3029
|
-
description: 'Codemod transformation scripts',
|
|
3030
|
-
required: true,
|
|
3031
|
-
});
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
// Also add automated publishing for enhanced setups
|
|
3035
|
-
if (setup.isEnhanced) {
|
|
3036
|
-
enhancements.push({
|
|
3037
|
-
name: '.github/workflows/release.yml',
|
|
3038
|
-
description: 'GitHub Actions workflow for automated publishing',
|
|
3039
|
-
required: true,
|
|
3040
|
-
});
|
|
3041
|
-
|
|
3042
|
-
enhancements.push({
|
|
3043
|
-
name: '.releaserc.json',
|
|
3044
|
-
description: 'semantic-release configuration',
|
|
3045
|
-
required: true,
|
|
3046
|
-
});
|
|
3047
|
-
}
|
|
3048
|
-
|
|
3049
|
-
// Add commit conventions for setups that don't have them
|
|
3050
|
-
if (!setup.hasTemplates || !fs.existsSync(path.join(currentDir, 'COMMIT_CONVENTIONS.md'))) {
|
|
3051
|
-
enhancements.push({
|
|
3052
|
-
name: 'COMMIT_CONVENTIONS.md',
|
|
3053
|
-
description: 'Commit message guidelines',
|
|
3054
|
-
required: false,
|
|
3055
|
-
});
|
|
3056
|
-
}
|
|
3057
|
-
|
|
3058
|
-
// Add OIDC setup guide if requested or not minimal
|
|
3059
|
-
if (
|
|
3060
|
-
(options.withOidc || (!options.minimal && !options.withOidc)) &&
|
|
3061
|
-
(!setup.isEnhanced || !fs.existsSync(path.join(currentDir, 'OIDC_SETUP.md')))
|
|
3062
|
-
) {
|
|
3063
|
-
enhancements.push({
|
|
3064
|
-
name: 'OIDC_SETUP.md',
|
|
3065
|
-
description: 'OIDC trusted publisher setup guide',
|
|
3066
|
-
required: false,
|
|
3067
|
-
});
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
// For enhanced setups, preserve existing tools
|
|
3071
|
-
if (setup.isEnhanced) {
|
|
3072
|
-
console.log(chalk.blue('âšī¸ Preserving existing sophisticated CAWS tools'));
|
|
3073
|
-
}
|
|
3074
|
-
|
|
3075
|
-
let addedCount = 0;
|
|
3076
|
-
let skippedCount = 0;
|
|
3077
|
-
const addedFiles = [];
|
|
3078
|
-
|
|
3079
|
-
for (const enhancement of enhancements) {
|
|
3080
|
-
if (!setup?.templateDir) {
|
|
3081
|
-
console.warn(
|
|
3082
|
-
chalk.yellow(`â ī¸ Template directory not available for enhancement: ${enhancement.name}`)
|
|
3083
|
-
);
|
|
3084
|
-
continue;
|
|
3085
|
-
}
|
|
3086
|
-
const sourcePath = path.join(setup.templateDir, enhancement.name);
|
|
3087
|
-
const destPath = path.join(currentDir, enhancement.name);
|
|
3088
|
-
|
|
3089
|
-
if (!fs.existsSync(destPath)) {
|
|
3090
|
-
if (fs.existsSync(sourcePath)) {
|
|
3091
|
-
try {
|
|
3092
|
-
await fs.copy(sourcePath, destPath);
|
|
3093
|
-
console.log(chalk.green(`â
Added ${enhancement.description}`));
|
|
3094
|
-
addedCount++;
|
|
3095
|
-
addedFiles.push(enhancement.name);
|
|
3096
|
-
} catch (copyError) {
|
|
3097
|
-
console.warn(chalk.yellow(`â ī¸ Failed to add ${enhancement.name}:`), copyError.message);
|
|
3098
|
-
}
|
|
3099
|
-
} else {
|
|
3100
|
-
// If source doesn't exist in template, create the directory structure
|
|
3101
|
-
try {
|
|
3102
|
-
await fs.ensureDir(destPath);
|
|
3103
|
-
console.log(chalk.green(`â
Created ${enhancement.description}`));
|
|
3104
|
-
addedCount++;
|
|
3105
|
-
addedFiles.push(enhancement.name);
|
|
3106
|
-
} catch (createError) {
|
|
3107
|
-
console.warn(
|
|
3108
|
-
chalk.yellow(`â ī¸ Failed to create ${enhancement.name}:`),
|
|
3109
|
-
createError.message
|
|
3110
|
-
);
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
} else {
|
|
3114
|
-
if (options.force) {
|
|
3115
|
-
try {
|
|
3116
|
-
await fs.remove(destPath);
|
|
3117
|
-
if (fs.existsSync(sourcePath)) {
|
|
3118
|
-
await fs.copy(sourcePath, destPath);
|
|
3119
|
-
} else {
|
|
3120
|
-
await fs.ensureDir(destPath);
|
|
3121
|
-
}
|
|
3122
|
-
console.log(chalk.blue(`đ Updated ${enhancement.description}`));
|
|
3123
|
-
addedCount++;
|
|
3124
|
-
addedFiles.push(enhancement.name);
|
|
3125
|
-
} catch (overwriteError) {
|
|
3126
|
-
console.warn(
|
|
3127
|
-
chalk.yellow(`â ī¸ Failed to update ${enhancement.name}:`),
|
|
3128
|
-
overwriteError.message
|
|
3129
|
-
);
|
|
3130
|
-
}
|
|
3131
|
-
} else {
|
|
3132
|
-
console.log(`âī¸ Skipped ${enhancement.name} (already exists)`);
|
|
3133
|
-
skippedCount++;
|
|
3134
|
-
}
|
|
3135
|
-
}
|
|
3136
|
-
}
|
|
3137
|
-
|
|
3138
|
-
// Update provenance with results
|
|
3139
|
-
scaffoldProvenance.artifacts = addedFiles;
|
|
3140
|
-
scaffoldProvenance.results.files_added = addedCount;
|
|
3141
|
-
scaffoldProvenance.results.files_skipped = skippedCount;
|
|
3142
|
-
|
|
3143
|
-
// Show summary
|
|
3144
|
-
console.log(chalk.green(`\nđ Enhancement completed!`));
|
|
3145
|
-
console.log(chalk.bold(`đ Summary: ${addedCount} added, ${skippedCount} skipped`));
|
|
3146
|
-
|
|
3147
|
-
if (addedCount > 0) {
|
|
3148
|
-
console.log(chalk.bold('\nđ Next steps:'));
|
|
3149
|
-
console.log('1. Review the added files');
|
|
3150
|
-
|
|
3151
|
-
// Check if OIDC was added
|
|
3152
|
-
const oidcAdded = addedFiles.some((file) => file.includes('OIDC_SETUP'));
|
|
3153
|
-
if (oidcAdded) {
|
|
3154
|
-
console.log('2. Set up OIDC trusted publisher (see OIDC_SETUP.md)');
|
|
3155
|
-
console.log('3. Push to trigger automated publishing');
|
|
3156
|
-
console.log('4. Your existing CAWS tools remain unchanged');
|
|
3157
|
-
} else {
|
|
3158
|
-
console.log('2. Customize your working spec in .caws/working-spec.yaml');
|
|
3159
|
-
console.log('3. Run validation: caws validate --suggestions');
|
|
3160
|
-
console.log('4. Your existing CAWS tools remain unchanged');
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
|
|
3164
|
-
if (setup.isEnhanced) {
|
|
3165
|
-
console.log(
|
|
3166
|
-
chalk.blue('\nđ¯ Your enhanced CAWS setup has been improved with automated publishing!')
|
|
3167
|
-
);
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
|
-
if (options.force) {
|
|
3171
|
-
console.log(chalk.yellow('\nâ ī¸ Force mode was used - review changes carefully'));
|
|
3172
|
-
}
|
|
3173
|
-
|
|
3174
|
-
// Save provenance manifest if tools are available
|
|
3175
|
-
const tools = loadProvenanceTools();
|
|
3176
|
-
if (tools && typeof tools.saveProvenance === 'function') {
|
|
3177
|
-
await tools.saveProvenance(scaffoldProvenance, '.agent/scaffold-provenance.json');
|
|
3178
|
-
console.log(chalk.green('â
Scaffolding provenance saved'));
|
|
3179
|
-
} else {
|
|
3180
|
-
console.log(chalk.yellow('â ī¸ Provenance tools not available - skipping manifest save'));
|
|
3181
|
-
}
|
|
3182
|
-
} catch (error) {
|
|
3183
|
-
// Handle circular reference errors from Commander.js
|
|
3184
|
-
if (error.message && error.message.includes('Converting circular structure to JSON')) {
|
|
3185
|
-
console.log(
|
|
3186
|
-
chalk.yellow('â ī¸ Scaffolding completed with minor issues (circular reference handled)')
|
|
3187
|
-
);
|
|
3188
|
-
console.log(chalk.green('â
CAWS components scaffolded successfully'));
|
|
3189
|
-
} else {
|
|
3190
|
-
console.error(chalk.red('â Error during scaffolding:'), error.message);
|
|
3191
|
-
process.exit(1);
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
}
|
|
3195
|
-
|
|
3196
|
-
/**
|
|
3197
|
-
* Show version information
|
|
3198
|
-
*/
|
|
3199
|
-
// function showVersion() {
|
|
3200
|
-
// console.log(chalk.bold(`CAWS CLI v${CLI_VERSION}`));
|
|
3201
|
-
// console.log(chalk.cyan('Coding Agent Workflow System - Scaffolding Tool'));
|
|
3202
|
-
// console.log(chalk.gray('Author: @darianrosebrook'));
|
|
3203
|
-
// console.log(chalk.gray('License: MIT'));
|
|
3204
|
-
// }
|
|
3205
|
-
|
|
3206
|
-
// Initialize CAWS Tool System
|
|
3207
|
-
let toolLoader = null;
|
|
3208
|
-
let toolValidator = null;
|
|
3209
|
-
|
|
3210
|
-
async function initializeToolSystem() {
|
|
3211
|
-
if (toolLoader) return toolLoader; // Already initialized
|
|
3212
|
-
|
|
3213
|
-
try {
|
|
3214
|
-
toolLoader = new ToolLoader({
|
|
3215
|
-
toolsDir: path.join(process.cwd(), 'apps/tools/caws'),
|
|
3216
|
-
});
|
|
3217
|
-
|
|
3218
|
-
toolValidator = new ToolValidator();
|
|
3219
|
-
|
|
3220
|
-
// Set up event listeners for tool system
|
|
3221
|
-
toolLoader.on('discovery:complete', ({ tools: _tools, count }) => {
|
|
3222
|
-
if (count > 0) {
|
|
3223
|
-
console.log(chalk.blue(`đ§ Discovered ${count} tools`));
|
|
3224
|
-
}
|
|
3225
|
-
});
|
|
3226
|
-
|
|
3227
|
-
toolLoader.on('tool:loaded', ({ id, metadata }) => {
|
|
3228
|
-
console.log(chalk.gray(` â Loaded tool: ${metadata.name} (${id})`));
|
|
3229
|
-
});
|
|
3230
|
-
|
|
3231
|
-
toolLoader.on('tool:error', ({ id, error }) => {
|
|
3232
|
-
console.warn(chalk.yellow(`â ī¸ Failed to load tool ${id}: ${error}`));
|
|
3233
|
-
});
|
|
3234
|
-
|
|
3235
|
-
// Auto-discover tools on initialization
|
|
3236
|
-
await toolLoader.discoverTools();
|
|
3237
|
-
|
|
3238
|
-
return toolLoader;
|
|
3239
|
-
} catch (error) {
|
|
3240
|
-
console.warn(chalk.yellow('â ī¸ Tool system initialization failed:'), error.message);
|
|
3241
|
-
console.warn(chalk.blue('đĄ Continuing without dynamic tools'));
|
|
3242
|
-
return null;
|
|
3243
|
-
}
|
|
3244
|
-
}
|
|
3245
|
-
|
|
3246
|
-
// Agent-oriented helper functions
|
|
3247
|
-
async function runQualityGatesForSpec(spec, strictMode = false) {
|
|
3248
|
-
const loader = await initializeToolSystem();
|
|
3249
|
-
const WaiversManager = require('./waivers-manager');
|
|
3250
|
-
const waiversManager = new WaiversManager();
|
|
3251
|
-
|
|
3252
|
-
const criteria = [];
|
|
3253
|
-
let overallScore = 0;
|
|
3254
|
-
let totalWeight = 0;
|
|
3255
|
-
|
|
3256
|
-
// Spec completeness criteria
|
|
3257
|
-
const specScore = calculateSpecCompleteness(spec);
|
|
3258
|
-
criteria.push({
|
|
3259
|
-
id: 'spec_completeness',
|
|
3260
|
-
name: 'Specification Completeness',
|
|
3261
|
-
status: specScore >= 0.8 ? 'passed' : 'failed',
|
|
3262
|
-
score: specScore,
|
|
3263
|
-
weight: 0.2,
|
|
3264
|
-
feedback: `Spec completeness: ${(specScore * 100).toFixed(1)}%`,
|
|
3265
|
-
});
|
|
3266
|
-
overallScore += specScore * 0.2;
|
|
3267
|
-
totalWeight += 0.2;
|
|
3268
|
-
|
|
3269
|
-
// Risk-appropriate quality thresholds
|
|
3270
|
-
const tierThresholds = {
|
|
3271
|
-
1: { coverage: 0.9, mutation: 0.7, contracts: true },
|
|
3272
|
-
2: { coverage: 0.8, mutation: 0.5, contracts: true },
|
|
3273
|
-
3: { coverage: 0.7, mutation: 0.3, contracts: false },
|
|
3274
|
-
};
|
|
3275
|
-
|
|
3276
|
-
const thresholds = tierThresholds[spec.risk_tier] || tierThresholds[2];
|
|
3277
|
-
const appliedThresholds = strictMode
|
|
3278
|
-
? {
|
|
3279
|
-
coverage: thresholds.coverage,
|
|
3280
|
-
mutation: thresholds.mutation,
|
|
3281
|
-
contracts: thresholds.contracts,
|
|
3282
|
-
}
|
|
3283
|
-
: {
|
|
3284
|
-
coverage: Math.max(0.6, thresholds.coverage - 0.2),
|
|
3285
|
-
mutation: Math.max(0.2, thresholds.mutation - 0.2),
|
|
3286
|
-
contracts: thresholds.contracts,
|
|
3287
|
-
};
|
|
3288
|
-
|
|
3289
|
-
// Tool-based quality gates
|
|
3290
|
-
if (loader) {
|
|
3291
|
-
const tools = loader.getAllTools();
|
|
3292
|
-
|
|
3293
|
-
for (const [toolId, tool] of tools) {
|
|
3294
|
-
if (
|
|
3295
|
-
tool.metadata.capabilities?.includes('quality-gates') ||
|
|
3296
|
-
tool.metadata.capabilities?.includes('validation')
|
|
3297
|
-
) {
|
|
3298
|
-
const gateName = `gate_${toolId}`;
|
|
3299
|
-
let gateScore = 0.0;
|
|
3300
|
-
let gateStatus = 'failed';
|
|
3301
|
-
let gateFeedback = '';
|
|
3302
|
-
let weight = toolId === 'validate' ? 0.3 : 0.15;
|
|
3303
|
-
|
|
3304
|
-
// Check if this gate is waived
|
|
3305
|
-
const waiverCoverage = await waiversManager.checkWaiverCoverage([gateName]);
|
|
3306
|
-
|
|
3307
|
-
if (waiverCoverage.allCovered) {
|
|
3308
|
-
// Gate is waived - mark as passed but note the waiver
|
|
3309
|
-
gateScore = 1.0;
|
|
3310
|
-
gateStatus = 'waived';
|
|
3311
|
-
const waiver = waiverCoverage.waiverDetails[0];
|
|
3312
|
-
gateFeedback = `WAIVED: ${waiver.waiver_id} (${waiver.reason}) - Expires: ${waiver.expires_at}`;
|
|
3313
|
-
weight = weight * 0.8; // Reduce weight for waived gates
|
|
3314
|
-
} else {
|
|
3315
|
-
// Run the gate normally
|
|
3316
|
-
try {
|
|
3317
|
-
const result = await tool.module.execute(
|
|
3318
|
-
{},
|
|
3319
|
-
{
|
|
3320
|
-
workingDirectory: process.cwd(),
|
|
3321
|
-
spec: spec,
|
|
3322
|
-
}
|
|
3323
|
-
);
|
|
3324
|
-
|
|
3325
|
-
gateScore = result.success ? 1.0 : 0.0;
|
|
3326
|
-
gateStatus = result.success ? 'passed' : 'failed';
|
|
3327
|
-
gateFeedback = result.success
|
|
3328
|
-
? `${tool.metadata.name} passed`
|
|
3329
|
-
: `Failed: ${result.errors?.join(', ') || 'Unknown error'}`;
|
|
3330
|
-
} catch (error) {
|
|
3331
|
-
gateStatus = 'error';
|
|
3332
|
-
gateScore = 0;
|
|
3333
|
-
gateFeedback = `Gate execution failed: ${error.message}`;
|
|
3334
|
-
weight = 0.1;
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
|
|
3338
|
-
criteria.push({
|
|
3339
|
-
id: gateName,
|
|
3340
|
-
name: `${tool.metadata.name} Gate`,
|
|
3341
|
-
status: gateStatus,
|
|
3342
|
-
score: gateScore,
|
|
3343
|
-
weight: weight,
|
|
3344
|
-
feedback: gateFeedback,
|
|
3345
|
-
});
|
|
3346
|
-
|
|
3347
|
-
overallScore += gateScore * weight;
|
|
3348
|
-
totalWeight += weight;
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
|
|
3353
|
-
// Contract compliance (if applicable)
|
|
3354
|
-
if (spec.contracts && spec.contracts.length > 0) {
|
|
3355
|
-
const contractScore = spec.contracts.length > 0 ? 1.0 : 0.0;
|
|
3356
|
-
criteria.push({
|
|
3357
|
-
id: 'contract_compliance',
|
|
3358
|
-
name: 'Contract Compliance',
|
|
3359
|
-
status: contractScore >= (appliedThresholds.contracts ? 1.0 : 0.5) ? 'passed' : 'failed',
|
|
3360
|
-
score: contractScore,
|
|
3361
|
-
weight: 0.2,
|
|
3362
|
-
feedback: `${spec.contracts.length} contracts defined`,
|
|
3363
|
-
});
|
|
3364
|
-
overallScore += contractScore * 0.2;
|
|
3365
|
-
totalWeight += 0.2;
|
|
3366
|
-
}
|
|
3367
|
-
|
|
3368
|
-
const finalScore = totalWeight > 0 ? overallScore / totalWeight : 0;
|
|
3369
|
-
const overallPassed = finalScore >= 0.75; // 75% quality threshold
|
|
3370
|
-
|
|
3371
|
-
// Generate next actions based on results
|
|
3372
|
-
const nextActions = [];
|
|
3373
|
-
const failedCriteria = criteria.filter((c) => c.status === 'failed');
|
|
3374
|
-
|
|
3375
|
-
if (failedCriteria.length > 0) {
|
|
3376
|
-
nextActions.push('Address failed quality criteria:');
|
|
3377
|
-
failedCriteria.forEach((criterion) => {
|
|
3378
|
-
nextActions.push(` - ${criterion.name}: ${criterion.feedback}`);
|
|
3379
|
-
});
|
|
3380
|
-
} else if (overallPassed) {
|
|
3381
|
-
nextActions.push('All quality gates passed! Ready for integration.');
|
|
3382
|
-
nextActions.push('Consider: code review, additional testing, documentation updates');
|
|
3383
|
-
} else {
|
|
3384
|
-
nextActions.push('Improve overall quality score through:');
|
|
3385
|
-
nextActions.push(' - Better test coverage and mutation scores');
|
|
3386
|
-
nextActions.push(' - Contract testing implementation');
|
|
3387
|
-
nextActions.push(' - Tool-based quality gate compliance');
|
|
3388
|
-
}
|
|
3389
|
-
|
|
3390
|
-
return {
|
|
3391
|
-
overall_passed: overallPassed,
|
|
3392
|
-
quality_score: Number(finalScore.toFixed(3)),
|
|
3393
|
-
summary: overallPassed
|
|
3394
|
-
? `Quality standards met (${(finalScore * 100).toFixed(1)}% score)`
|
|
3395
|
-
: `Quality standards not met (${(finalScore * 100).toFixed(1)}% score)`,
|
|
3396
|
-
criteria,
|
|
3397
|
-
progress_indicators: {
|
|
3398
|
-
spec_complete: specScore >= 0.8,
|
|
3399
|
-
quality_gates: criteria.filter((c) => c.id.startsWith('gate_') && c.status === 'passed')
|
|
3400
|
-
.length,
|
|
3401
|
-
contracts_ready: spec.contracts && spec.contracts.length > 0,
|
|
3402
|
-
risk_appropriate: spec.risk_tier <= 2 || finalScore >= 0.6,
|
|
3403
|
-
},
|
|
3404
|
-
next_actions: nextActions,
|
|
3405
|
-
risk_assessment: {
|
|
3406
|
-
tier: spec.risk_tier,
|
|
3407
|
-
applied_thresholds: appliedThresholds,
|
|
3408
|
-
risk_level: spec.risk_tier === 1 ? 'high' : spec.risk_tier === 2 ? 'medium' : 'low',
|
|
3409
|
-
recommendations: generateRiskRecommendations(spec, finalScore),
|
|
3410
|
-
},
|
|
3411
|
-
};
|
|
3412
|
-
}
|
|
3413
|
-
|
|
3414
|
-
function calculateSpecCompleteness(spec) {
|
|
3415
|
-
let score = 0;
|
|
3416
|
-
let totalChecks = 0;
|
|
3417
|
-
|
|
3418
|
-
// Required fields
|
|
3419
|
-
const requiredFields = [
|
|
3420
|
-
'id',
|
|
3421
|
-
'title',
|
|
3422
|
-
'risk_tier',
|
|
3423
|
-
'mode',
|
|
3424
|
-
'change_budget',
|
|
3425
|
-
'scope',
|
|
3426
|
-
'invariants',
|
|
3427
|
-
'acceptance',
|
|
3428
|
-
];
|
|
3429
|
-
requiredFields.forEach((field) => {
|
|
3430
|
-
totalChecks++;
|
|
3431
|
-
if (spec[field]) score++;
|
|
3432
|
-
});
|
|
3433
|
-
|
|
3434
|
-
// Scope completeness
|
|
3435
|
-
totalChecks++;
|
|
3436
|
-
if (spec.scope && spec.scope.in && spec.scope.out) score++;
|
|
3437
|
-
|
|
3438
|
-
// Acceptance criteria quality
|
|
3439
|
-
totalChecks++;
|
|
3440
|
-
if (spec.acceptance && spec.acceptance.length >= 1) {
|
|
3441
|
-
const validCriteria = spec.acceptance.filter((a) => a.id && a.given && a.when && a.then);
|
|
3442
|
-
score += validCriteria.length / Math.max(spec.acceptance.length, 1);
|
|
3443
|
-
}
|
|
3444
|
-
|
|
3445
|
-
// Invariants presence
|
|
3446
|
-
totalChecks++;
|
|
3447
|
-
if (spec.invariants && spec.invariants.length >= 2) score++;
|
|
3448
|
-
|
|
3449
|
-
// Contracts for T1/T2
|
|
3450
|
-
if (spec.risk_tier <= 2) {
|
|
3451
|
-
totalChecks++;
|
|
3452
|
-
if (spec.contracts && spec.contracts.length > 0) score++;
|
|
3453
|
-
}
|
|
3454
|
-
|
|
3455
|
-
return score / totalChecks;
|
|
3456
|
-
}
|
|
3457
|
-
|
|
3458
|
-
function generateRiskRecommendations(spec, qualityScore) {
|
|
3459
|
-
const recommendations = [];
|
|
3460
|
-
|
|
3461
|
-
if (spec.risk_tier === 1 && qualityScore < 0.9) {
|
|
3462
|
-
recommendations.push('High-risk feature requires exceptional quality (>90%)');
|
|
3463
|
-
recommendations.push('Consider breaking into smaller, lower-risk changes');
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
|
-
if (spec.mode === 'feature' && (!spec.contracts || spec.contracts.length === 0)) {
|
|
3467
|
-
recommendations.push('Features should define contracts before implementation');
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3470
|
-
if (spec.change_budget && spec.change_budget.max_files > 25) {
|
|
3471
|
-
recommendations.push('Large change budgets increase risk - consider splitting');
|
|
3472
|
-
}
|
|
3473
|
-
|
|
3474
|
-
return recommendations;
|
|
3475
|
-
}
|
|
3476
|
-
|
|
3477
|
-
async function generateIterativeGuidance(spec, currentState) {
|
|
3478
|
-
const guidance = {
|
|
3479
|
-
guidance: '',
|
|
3480
|
-
next_steps: [],
|
|
3481
|
-
confidence: 0,
|
|
3482
|
-
focus_areas: [],
|
|
3483
|
-
risk_mitigation: [],
|
|
3484
|
-
};
|
|
3485
|
-
|
|
3486
|
-
// Analyze current implementation state
|
|
3487
|
-
const implementationStage = analyzeImplementationStage(spec, currentState);
|
|
3488
|
-
guidance.guidance = getStageGuidance(implementationStage);
|
|
3489
|
-
|
|
3490
|
-
// Generate specific next steps based on spec requirements
|
|
3491
|
-
guidance.next_steps = generateNextSteps(spec, implementationStage, currentState);
|
|
3492
|
-
|
|
3493
|
-
// Calculate confidence based on progress
|
|
3494
|
-
guidance.confidence = calculateImplementationConfidence(spec, currentState);
|
|
3495
|
-
|
|
3496
|
-
// Identify focus areas
|
|
3497
|
-
guidance.focus_areas = identifyFocusAreas(spec, currentState);
|
|
3498
|
-
|
|
3499
|
-
// Risk mitigation suggestions
|
|
3500
|
-
guidance.risk_mitigation = generateRiskMitigation(spec, currentState);
|
|
3501
|
-
|
|
3502
|
-
return guidance;
|
|
3503
|
-
}
|
|
3504
|
-
|
|
3505
|
-
function analyzeImplementationStage(spec, currentState) {
|
|
3506
|
-
// Simple heuristic based on current state description
|
|
3507
|
-
const stateDesc = currentState.description || '';
|
|
3508
|
-
|
|
3509
|
-
if (stateDesc.includes('started') || stateDesc.includes('initial')) return 'planning';
|
|
3510
|
-
if (stateDesc.includes('prototype') || stateDesc.includes('draft')) return 'prototyping';
|
|
3511
|
-
if (stateDesc.includes('core') || stateDesc.includes('basic')) return 'core_implementation';
|
|
3512
|
-
if (stateDesc.includes('testing') || stateDesc.includes('test')) return 'testing';
|
|
3513
|
-
if (stateDesc.includes('integration') || stateDesc.includes('integrate')) return 'integration';
|
|
3514
|
-
if (stateDesc.includes('complete') || stateDesc.includes('done')) return 'polishing';
|
|
3515
|
-
|
|
3516
|
-
return 'early_planning';
|
|
3517
|
-
}
|
|
3518
|
-
|
|
3519
|
-
function getStageGuidance(stage) {
|
|
3520
|
-
const guidance = {
|
|
3521
|
-
early_planning: 'Focus on understanding requirements and creating a solid implementation plan.',
|
|
3522
|
-
planning: 'Break down the feature into manageable tasks and establish success criteria.',
|
|
3523
|
-
prototyping:
|
|
3524
|
-
'Build a working prototype to validate the approach and identify technical challenges.',
|
|
3525
|
-
core_implementation:
|
|
3526
|
-
'Implement the core functionality with proper error handling and edge cases.',
|
|
3527
|
-
testing: 'Add comprehensive tests and validate against acceptance criteria.',
|
|
3528
|
-
integration: 'Ensure the feature integrates well with existing systems and contracts.',
|
|
3529
|
-
polishing: 'Refine the implementation, add documentation, and optimize performance.',
|
|
3530
|
-
};
|
|
3531
|
-
|
|
3532
|
-
return guidance[stage] || 'Continue systematic implementation following the working spec.';
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
function generateNextSteps(spec, stage, _currentState) {
|
|
3536
|
-
const steps = [];
|
|
3537
|
-
|
|
3538
|
-
switch (stage) {
|
|
3539
|
-
case 'early_planning':
|
|
3540
|
-
steps.push('Review and validate working spec completeness');
|
|
3541
|
-
steps.push('Create detailed implementation plan');
|
|
3542
|
-
steps.push('Set up development environment and dependencies');
|
|
3543
|
-
break;
|
|
3544
|
-
|
|
3545
|
-
case 'planning':
|
|
3546
|
-
steps.push('Implement core functionality skeleton');
|
|
3547
|
-
steps.push('Add basic error handling');
|
|
3548
|
-
steps.push('Create initial test structure');
|
|
3549
|
-
break;
|
|
3550
|
-
|
|
3551
|
-
case 'prototyping':
|
|
3552
|
-
steps.push('Refine core algorithms and logic');
|
|
3553
|
-
steps.push('Add comprehensive input validation');
|
|
3554
|
-
steps.push('Implement basic integration points');
|
|
3555
|
-
break;
|
|
3556
|
-
|
|
3557
|
-
case 'core_implementation':
|
|
3558
|
-
steps.push('Add comprehensive test coverage');
|
|
3559
|
-
steps.push('Implement contract testing if applicable');
|
|
3560
|
-
steps.push('Add performance optimizations');
|
|
3561
|
-
break;
|
|
3562
|
-
|
|
3563
|
-
case 'testing':
|
|
3564
|
-
steps.push('Run full quality gate evaluation');
|
|
3565
|
-
steps.push('Address any failing tests or gates');
|
|
3566
|
-
steps.push('Add integration tests');
|
|
3567
|
-
break;
|
|
3568
|
-
|
|
3569
|
-
case 'integration':
|
|
3570
|
-
steps.push('Test with dependent systems');
|
|
3571
|
-
steps.push('Validate contract compliance');
|
|
3572
|
-
steps.push('Perform load and stress testing');
|
|
3573
|
-
break;
|
|
3574
|
-
|
|
3575
|
-
case 'polishing':
|
|
3576
|
-
steps.push('Add comprehensive documentation');
|
|
3577
|
-
steps.push('Final performance optimization');
|
|
3578
|
-
steps.push('Code review and final validation');
|
|
3579
|
-
break;
|
|
3580
|
-
}
|
|
3581
|
-
|
|
3582
|
-
// Add spec-specific steps
|
|
3583
|
-
if (spec.mode === 'feature' && spec.contracts) {
|
|
3584
|
-
steps.push('Ensure contract definitions are complete and tested');
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
if (spec.risk_tier === 1) {
|
|
3588
|
-
steps.push('Prioritize security and reliability measures');
|
|
3589
|
-
}
|
|
3590
|
-
|
|
3591
|
-
return steps;
|
|
3592
|
-
}
|
|
3593
|
-
|
|
3594
|
-
function calculateImplementationConfidence(spec, currentState) {
|
|
3595
|
-
// Simple confidence calculation based on described progress
|
|
3596
|
-
let confidence = 0.5; // Base confidence
|
|
3597
|
-
|
|
3598
|
-
const stateDesc = (currentState.description || '').toLowerCase();
|
|
3599
|
-
|
|
3600
|
-
if (stateDesc.includes('complete') || stateDesc.includes('done')) confidence += 0.3;
|
|
3601
|
-
if (stateDesc.includes('tested') || stateDesc.includes('working')) confidence += 0.2;
|
|
3602
|
-
if (stateDesc.includes('prototype') || stateDesc.includes('basic')) confidence += 0.1;
|
|
3603
|
-
|
|
3604
|
-
if (stateDesc.includes('blocked') || stateDesc.includes('stuck')) confidence -= 0.2;
|
|
3605
|
-
if (stateDesc.includes('issues') || stateDesc.includes('problems')) confidence -= 0.1;
|
|
3606
|
-
|
|
3607
|
-
return Math.max(0, Math.min(1, confidence));
|
|
3608
|
-
}
|
|
3609
|
-
|
|
3610
|
-
function identifyFocusAreas(spec, _currentState) {
|
|
3611
|
-
const areas = [];
|
|
3612
|
-
|
|
3613
|
-
// Always include quality gates
|
|
3614
|
-
areas.push('Quality Gates Compliance');
|
|
3615
|
-
|
|
3616
|
-
if (spec.contracts && spec.contracts.length > 0) {
|
|
3617
|
-
areas.push('Contract Implementation');
|
|
3618
|
-
}
|
|
3619
|
-
|
|
3620
|
-
if (spec.risk_tier <= 2) {
|
|
3621
|
-
areas.push('Security & Reliability');
|
|
3622
|
-
}
|
|
3623
|
-
|
|
3624
|
-
if (spec.acceptance && spec.acceptance.length > 0) {
|
|
3625
|
-
areas.push('Acceptance Criteria Validation');
|
|
3626
|
-
}
|
|
3627
|
-
|
|
3628
|
-
return areas;
|
|
3629
|
-
}
|
|
3630
|
-
|
|
3631
|
-
function generateRiskMitigation(spec, _currentState) {
|
|
3632
|
-
const mitigation = [];
|
|
3633
|
-
|
|
3634
|
-
if (spec.risk_tier === 1) {
|
|
3635
|
-
mitigation.push('Implement comprehensive error handling');
|
|
3636
|
-
mitigation.push('Add extensive logging and monitoring');
|
|
3637
|
-
mitigation.push('Consider feature flags for gradual rollout');
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
if (spec.change_budget && spec.change_budget.max_files > 15) {
|
|
3641
|
-
mitigation.push('Regular commits and incremental validation');
|
|
3642
|
-
mitigation.push('Consider breaking into smaller PRs');
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
|
-
if (spec.mode === 'feature') {
|
|
3646
|
-
mitigation.push('Validate contracts before full implementation');
|
|
3647
|
-
mitigation.push('Implement feature flags for safe deployment');
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
return mitigation;
|
|
3651
|
-
}
|
|
3652
|
-
|
|
3653
|
-
// CLI Commands
|
|
3654
|
-
program
|
|
3655
|
-
.name('caws')
|
|
3656
|
-
.description('CAWS - Coding Agent Workflow System CLI')
|
|
3657
|
-
.version(CLI_VERSION, '-v, --version', 'Show version information');
|
|
3658
|
-
|
|
3659
|
-
program
|
|
3660
|
-
.command('init')
|
|
3661
|
-
.alias('i')
|
|
3662
|
-
.description('Initialize a new project with CAWS')
|
|
3663
|
-
.argument('<project-name>', 'Name of the new project')
|
|
3664
|
-
.option('-i, --interactive', 'Run interactive setup wizard')
|
|
3665
|
-
.option('-g, --git', 'Initialize git repository', true)
|
|
3666
|
-
.option('-n, --non-interactive', 'Skip interactive prompts')
|
|
3667
|
-
.option('--no-git', "Don't initialize git repository")
|
|
3668
|
-
.option('-t, --template <type>', 'Use project template (extension|library|api|cli|monorepo)')
|
|
3669
|
-
.action(initProject);
|
|
3670
|
-
|
|
3671
|
-
program
|
|
3672
|
-
.command('scaffold')
|
|
3673
|
-
.alias('s')
|
|
3674
|
-
.description('Add CAWS components to existing project')
|
|
3675
|
-
.option('-f, --force', 'Overwrite existing files')
|
|
3676
|
-
.option('--with-oidc', 'Include OIDC trusted publisher setup')
|
|
3677
|
-
.option('--with-codemods', 'Include codemod transformation scripts')
|
|
3678
|
-
.option('--minimal', 'Only essential components (no OIDC, no codemods)')
|
|
3679
|
-
.action(scaffoldProject);
|
|
3680
|
-
|
|
3681
|
-
program
|
|
3682
|
-
.command('validate')
|
|
3683
|
-
.alias('v')
|
|
3684
|
-
.description('Validate CAWS working spec with suggestions')
|
|
3685
|
-
.argument('[spec-file]', 'Path to working spec file', '.caws/working-spec.yaml')
|
|
3686
|
-
.option('-s, --suggestions', 'Show helpful suggestions for issues', true)
|
|
3687
|
-
.option('-f, --auto-fix', 'Automatically fix safe issues', false)
|
|
3688
|
-
.option('-q, --quiet', 'Only show errors, no suggestions', false)
|
|
3689
|
-
.action(async (specFile, options) => {
|
|
3690
|
-
try {
|
|
3691
|
-
// Check if spec file exists
|
|
3692
|
-
if (!fs.existsSync(specFile)) {
|
|
3693
|
-
console.error(chalk.red(`â Working spec file not found: ${specFile}`));
|
|
3694
|
-
console.error(chalk.blue('đĄ Initialize CAWS first:'));
|
|
3695
|
-
console.error(` ${chalk.cyan('caws init .')}`);
|
|
3696
|
-
process.exit(1);
|
|
3697
|
-
}
|
|
3698
|
-
|
|
3699
|
-
// Load and parse spec
|
|
3700
|
-
const specContent = fs.readFileSync(specFile, 'utf8');
|
|
3701
|
-
const spec = yaml.load(specContent);
|
|
3702
|
-
|
|
3703
|
-
if (!spec) {
|
|
3704
|
-
console.error(chalk.red('â Failed to parse working spec YAML'));
|
|
3705
|
-
process.exit(1);
|
|
3706
|
-
}
|
|
3707
|
-
|
|
3708
|
-
// Validate spec with suggestions
|
|
3709
|
-
const result = validateWorkingSpecWithSuggestions(spec, {
|
|
3710
|
-
autoFix: options.autoFix,
|
|
3711
|
-
suggestions: !options.quiet,
|
|
3712
|
-
});
|
|
3713
|
-
|
|
3714
|
-
// Save auto-fixed spec if changes were made
|
|
3715
|
-
if (options.autoFix && result.errors.length === 0) {
|
|
3716
|
-
const fixedContent = yaml.dump(spec, { indent: 2 });
|
|
3717
|
-
fs.writeFileSync(specFile, fixedContent);
|
|
3718
|
-
console.log(chalk.green(`â
Saved auto-fixed spec to ${specFile}`));
|
|
3719
|
-
}
|
|
3720
|
-
|
|
3721
|
-
// Execute quality gate tools if validation passed
|
|
3722
|
-
if (result.valid && !options.quiet) {
|
|
3723
|
-
console.log('');
|
|
3724
|
-
console.log(chalk.blue('đ Running quality gates...'));
|
|
3725
|
-
|
|
3726
|
-
const loader = await initializeToolSystem();
|
|
3727
|
-
if (loader) {
|
|
3728
|
-
await loader.loadAllTools();
|
|
3729
|
-
const tools = loader.getAllTools();
|
|
3730
|
-
|
|
3731
|
-
let gatesPassed = 0;
|
|
3732
|
-
let gatesTotal = 0;
|
|
3733
|
-
|
|
3734
|
-
for (const [, tool] of tools) {
|
|
3735
|
-
// Only run tools with quality-gates capability
|
|
3736
|
-
if (
|
|
3737
|
-
tool.metadata.capabilities?.includes('quality-gates') ||
|
|
3738
|
-
tool.metadata.capabilities?.includes('validation')
|
|
3739
|
-
) {
|
|
3740
|
-
gatesTotal++;
|
|
3741
|
-
console.log(chalk.gray(` Running ${tool.metadata.name}...`));
|
|
3742
|
-
|
|
3743
|
-
try {
|
|
3744
|
-
const gateResult = await tool.module.execute(
|
|
3745
|
-
{},
|
|
3746
|
-
{
|
|
3747
|
-
workingDirectory: process.cwd(),
|
|
3748
|
-
spec: spec,
|
|
3749
|
-
}
|
|
3750
|
-
);
|
|
3751
|
-
|
|
3752
|
-
if (gateResult.success) {
|
|
3753
|
-
console.log(chalk.green(` â
${tool.metadata.name} passed`));
|
|
3754
|
-
gatesPassed++;
|
|
3755
|
-
} else {
|
|
3756
|
-
console.log(chalk.red(` â ${tool.metadata.name} failed`));
|
|
3757
|
-
gateResult.errors?.forEach((error) => {
|
|
3758
|
-
console.log(chalk.red(` ${error}`));
|
|
3759
|
-
});
|
|
3760
|
-
}
|
|
3761
|
-
} catch (error) {
|
|
3762
|
-
console.log(chalk.red(` â ${tool.metadata.name} error: ${error.message}`));
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
}
|
|
3766
|
-
|
|
3767
|
-
if (gatesTotal > 0) {
|
|
3768
|
-
console.log('');
|
|
3769
|
-
console.log(chalk.blue(`đ¯ Quality Gates: ${gatesPassed}/${gatesTotal} passed`));
|
|
3770
|
-
|
|
3771
|
-
if (gatesPassed < gatesTotal) {
|
|
3772
|
-
console.log(chalk.yellow('â ī¸ Some quality gates failed - review output above'));
|
|
3773
|
-
process.exit(1);
|
|
3774
|
-
} else {
|
|
3775
|
-
console.log(chalk.green('đ All quality gates passed!'));
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
|
|
3781
|
-
// Exit with appropriate code
|
|
3782
|
-
process.exit(result.valid ? 0 : 1);
|
|
3783
|
-
} catch (error) {
|
|
3784
|
-
console.error(chalk.red('â Error during validation:'), error.message);
|
|
3785
|
-
process.exit(1);
|
|
3786
|
-
}
|
|
3787
|
-
});
|
|
3788
|
-
|
|
3789
|
-
program
|
|
3790
|
-
.command('agent')
|
|
3791
|
-
.description('Agent-oriented commands for programmatic evaluation')
|
|
3792
|
-
.addCommand(
|
|
3793
|
-
new Command('evaluate')
|
|
3794
|
-
.description('Evaluate work against CAWS quality standards')
|
|
3795
|
-
.argument('<spec-file>', 'Path to working spec file')
|
|
3796
|
-
.option('--json', 'Output results as structured JSON for agent parsing')
|
|
3797
|
-
.option('--strict', 'Apply strict quality thresholds (for production use)')
|
|
3798
|
-
.option('--feedback-only', 'Only return actionable feedback, no execution')
|
|
3799
|
-
.action(async (specFile, options) => {
|
|
3800
|
-
// Quiet mode for agent commands - suppress human-readable output
|
|
3801
|
-
const _originalLog = console.log;
|
|
3802
|
-
const _originalWarn = console.warn;
|
|
3803
|
-
const _originalError = console.error;
|
|
3804
|
-
|
|
3805
|
-
try {
|
|
3806
|
-
if (!options.json) {
|
|
3807
|
-
// Only suppress if not explicitly requesting JSON
|
|
3808
|
-
console.log = () => {};
|
|
3809
|
-
console.warn = () => {};
|
|
3810
|
-
console.error = () => {};
|
|
3811
|
-
}
|
|
3812
|
-
|
|
3813
|
-
await initializeToolSystem();
|
|
3814
|
-
|
|
3815
|
-
if (!fs.existsSync(specFile)) {
|
|
3816
|
-
const result = {
|
|
3817
|
-
success: false,
|
|
3818
|
-
evaluation: {
|
|
3819
|
-
overall_status: 'error',
|
|
3820
|
-
message: `Working spec file not found: ${specFile}`,
|
|
3821
|
-
criteria: [],
|
|
3822
|
-
next_actions: [`Create working spec at ${specFile}`],
|
|
3823
|
-
},
|
|
3824
|
-
};
|
|
3825
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3826
|
-
process.exit(1);
|
|
3827
|
-
}
|
|
3828
|
-
|
|
3829
|
-
const specContent = fs.readFileSync(specFile, 'utf8');
|
|
3830
|
-
const spec = yaml.load(specContent);
|
|
3831
|
-
|
|
3832
|
-
if (!spec) {
|
|
3833
|
-
const result = {
|
|
3834
|
-
success: false,
|
|
3835
|
-
evaluation: {
|
|
3836
|
-
overall_status: 'error',
|
|
3837
|
-
message: 'Invalid YAML in working spec',
|
|
3838
|
-
criteria: [],
|
|
3839
|
-
next_actions: ['Fix YAML syntax in working spec'],
|
|
3840
|
-
},
|
|
3841
|
-
};
|
|
3842
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3843
|
-
process.exit(1);
|
|
3844
|
-
}
|
|
3845
|
-
|
|
3846
|
-
// Validate spec structure
|
|
3847
|
-
const validationResult = validateWorkingSpecWithSuggestions(spec, {
|
|
3848
|
-
suggestions: false,
|
|
3849
|
-
autoFix: false,
|
|
3850
|
-
});
|
|
3851
|
-
|
|
3852
|
-
if (!validationResult.valid) {
|
|
3853
|
-
const result = {
|
|
3854
|
-
success: false,
|
|
3855
|
-
evaluation: {
|
|
3856
|
-
overall_status: 'spec_invalid',
|
|
3857
|
-
message: 'Working spec validation failed',
|
|
3858
|
-
criteria: [
|
|
3859
|
-
{
|
|
3860
|
-
id: 'spec_validity',
|
|
3861
|
-
name: 'Working Spec Validity',
|
|
3862
|
-
status: 'failed',
|
|
3863
|
-
score: 0,
|
|
3864
|
-
weight: 1.0,
|
|
3865
|
-
feedback: validationResult.errors.join('; '),
|
|
3866
|
-
},
|
|
3867
|
-
],
|
|
3868
|
-
next_actions: [
|
|
3869
|
-
'Fix working spec validation errors',
|
|
3870
|
-
'Run: caws validate --auto-fix for automatic fixes',
|
|
3871
|
-
],
|
|
3872
|
-
},
|
|
3873
|
-
};
|
|
3874
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3875
|
-
process.exit(1);
|
|
3876
|
-
}
|
|
3877
|
-
|
|
3878
|
-
// If feedback-only, just return spec evaluation
|
|
3879
|
-
if (options.feedbackOnly) {
|
|
3880
|
-
const result = {
|
|
3881
|
-
success: true,
|
|
3882
|
-
evaluation: {
|
|
3883
|
-
overall_status: 'spec_valid',
|
|
3884
|
-
message: 'Working spec is valid and ready for implementation',
|
|
3885
|
-
criteria: [
|
|
3886
|
-
{
|
|
3887
|
-
id: 'spec_completeness',
|
|
3888
|
-
name: 'Specification Completeness',
|
|
3889
|
-
status: 'passed',
|
|
3890
|
-
score: 1.0,
|
|
3891
|
-
weight: 1.0,
|
|
3892
|
-
feedback: `Valid ${spec.mode} spec for ${spec.title} (${spec.risk_tier})`,
|
|
3893
|
-
},
|
|
3894
|
-
],
|
|
3895
|
-
spec_summary: {
|
|
3896
|
-
id: spec.id,
|
|
3897
|
-
mode: spec.mode,
|
|
3898
|
-
tier: spec.risk_tier,
|
|
3899
|
-
title: spec.title,
|
|
3900
|
-
acceptance_criteria: spec.acceptance?.length || 0,
|
|
3901
|
-
invariants: spec.invariants?.length || 0,
|
|
3902
|
-
},
|
|
3903
|
-
next_actions: [
|
|
3904
|
-
`Begin ${spec.mode} implementation`,
|
|
3905
|
-
'Run: caws agent evaluate <spec> to check progress',
|
|
3906
|
-
'Run: caws agent iterate <spec> for guided development',
|
|
3907
|
-
],
|
|
3908
|
-
},
|
|
3909
|
-
};
|
|
3910
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3911
|
-
return;
|
|
3912
|
-
}
|
|
3913
|
-
|
|
3914
|
-
// Run quality gates
|
|
3915
|
-
const qualityGates = await runQualityGatesForSpec(spec, options.strict);
|
|
3916
|
-
|
|
3917
|
-
const result = {
|
|
3918
|
-
success: qualityGates.overall_passed,
|
|
3919
|
-
evaluation: {
|
|
3920
|
-
overall_status: qualityGates.overall_passed ? 'quality_passed' : 'quality_failed',
|
|
3921
|
-
message: qualityGates.summary,
|
|
3922
|
-
criteria: qualityGates.criteria,
|
|
3923
|
-
spec_summary: {
|
|
3924
|
-
id: spec.id,
|
|
3925
|
-
mode: spec.mode,
|
|
3926
|
-
tier: spec.risk_tier,
|
|
3927
|
-
title: spec.title,
|
|
3928
|
-
progress_indicators: qualityGates.progress_indicators,
|
|
3929
|
-
},
|
|
3930
|
-
next_actions: qualityGates.next_actions,
|
|
3931
|
-
quality_score: qualityGates.quality_score,
|
|
3932
|
-
risk_assessment: qualityGates.risk_assessment,
|
|
3933
|
-
},
|
|
3934
|
-
};
|
|
3935
|
-
|
|
3936
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3937
|
-
process.exit(qualityGates.overall_passed ? 0 : 1);
|
|
3938
|
-
} catch (error) {
|
|
3939
|
-
// Restore console functions for error output
|
|
3940
|
-
console.log = _originalLog;
|
|
3941
|
-
console.warn = _originalWarn;
|
|
3942
|
-
console.error = _originalError;
|
|
3943
|
-
|
|
3944
|
-
const result = {
|
|
3945
|
-
success: false,
|
|
3946
|
-
evaluation: {
|
|
3947
|
-
overall_status: 'error',
|
|
3948
|
-
message: `Evaluation failed: ${error.message}`,
|
|
3949
|
-
criteria: [],
|
|
3950
|
-
next_actions: ['Check CAWS setup and try again'],
|
|
3951
|
-
},
|
|
3952
|
-
};
|
|
3953
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3954
|
-
process.exit(1);
|
|
3955
|
-
}
|
|
3956
|
-
})
|
|
3957
|
-
)
|
|
3958
|
-
.addCommand(
|
|
3959
|
-
new Command('iterate')
|
|
3960
|
-
.description('Provide iterative development guidance based on current progress')
|
|
3961
|
-
.argument('<spec-file>', 'Path to working spec file')
|
|
3962
|
-
.option('--current-state <json>', 'JSON description of current implementation state')
|
|
3963
|
-
.option('--json', 'Output as structured JSON')
|
|
3964
|
-
.action(async (specFile, options) => {
|
|
3965
|
-
// Quiet mode for agent commands - suppress human-readable output
|
|
3966
|
-
const _originalLog = console.log;
|
|
3967
|
-
const _originalWarn = console.warn;
|
|
3968
|
-
const _originalError = console.error;
|
|
3969
|
-
|
|
3970
|
-
try {
|
|
3971
|
-
console.log = () => {};
|
|
3972
|
-
console.warn = () => {};
|
|
3973
|
-
console.error = () => {};
|
|
3974
|
-
|
|
3975
|
-
await initializeToolSystem();
|
|
3976
|
-
|
|
3977
|
-
if (!fs.existsSync(specFile)) {
|
|
3978
|
-
const result = {
|
|
3979
|
-
success: false,
|
|
3980
|
-
iteration: {
|
|
3981
|
-
guidance: 'Working spec not found',
|
|
3982
|
-
next_steps: [`Create working spec at ${specFile}`],
|
|
3983
|
-
confidence: 0,
|
|
3984
|
-
},
|
|
3985
|
-
};
|
|
3986
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3987
|
-
process.exit(1);
|
|
3988
|
-
}
|
|
3989
|
-
|
|
3990
|
-
const specContent = fs.readFileSync(specFile, 'utf8');
|
|
3991
|
-
const spec = yaml.load(specContent);
|
|
3992
|
-
|
|
3993
|
-
let currentState = {};
|
|
3994
|
-
if (options.currentState) {
|
|
3995
|
-
try {
|
|
3996
|
-
currentState = JSON.parse(options.currentState);
|
|
3997
|
-
} catch (error) {
|
|
3998
|
-
currentState = { description: options.currentState };
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
|
|
4002
|
-
const guidance = await generateIterativeGuidance(spec, currentState);
|
|
4003
|
-
|
|
4004
|
-
const result = {
|
|
4005
|
-
success: true,
|
|
4006
|
-
iteration: guidance,
|
|
4007
|
-
};
|
|
4008
|
-
|
|
4009
|
-
console.log(JSON.stringify(result, null, 2));
|
|
4010
|
-
} catch (error) {
|
|
4011
|
-
// Restore console functions for error output
|
|
4012
|
-
console.log = _originalLog;
|
|
4013
|
-
console.warn = _originalWarn;
|
|
4014
|
-
console.error = _originalError;
|
|
4015
|
-
|
|
4016
|
-
const result = {
|
|
4017
|
-
success: false,
|
|
4018
|
-
iteration: {
|
|
4019
|
-
guidance: 'Iteration guidance failed',
|
|
4020
|
-
error: error.message,
|
|
4021
|
-
next_steps: ['Check CAWS setup and working spec validity'],
|
|
4022
|
-
},
|
|
4023
|
-
};
|
|
4024
|
-
console.log(JSON.stringify(result, null, 2));
|
|
4025
|
-
process.exit(1);
|
|
4026
|
-
}
|
|
4027
|
-
})
|
|
4028
|
-
);
|
|
4029
|
-
|
|
4030
|
-
program
|
|
4031
|
-
.command('cicd')
|
|
4032
|
-
.description('CI/CD pipeline optimization and generation')
|
|
4033
|
-
.addCommand(
|
|
4034
|
-
new Command('analyze')
|
|
4035
|
-
.description('Analyze project and recommend CI/CD optimizations')
|
|
4036
|
-
.argument('[spec-file]', 'Path to working spec file', '.caws/working-spec.yaml')
|
|
4037
|
-
.action(async (specFile) => {
|
|
4038
|
-
try {
|
|
4039
|
-
const CICDOptimizer = require('./cicd-optimizer');
|
|
4040
|
-
const optimizer = new CICDOptimizer();
|
|
4041
|
-
|
|
4042
|
-
console.log('đ Analyzing project for CI/CD optimizations...\n');
|
|
4043
|
-
|
|
4044
|
-
const analysis = await optimizer.analyzeProject(specFile);
|
|
4045
|
-
|
|
4046
|
-
console.log(`đ Project Tier: ${analysis.project_tier}`);
|
|
4047
|
-
console.log(
|
|
4048
|
-
`âąī¸ Estimated Savings: ${analysis.estimated_savings.savings_percent}% faster builds`
|
|
4049
|
-
);
|
|
4050
|
-
console.log(
|
|
4051
|
-
`đ° Monthly Time Savings: ${analysis.estimated_savings.monthly_savings_hours} hours\n`
|
|
4052
|
-
);
|
|
4053
|
-
|
|
4054
|
-
console.log('đ¯ Recommended Optimizations:');
|
|
4055
|
-
analysis.recommended_optimizations.forEach((opt, i) => {
|
|
4056
|
-
console.log(` ${i + 1}. ${opt.description}`);
|
|
4057
|
-
console.log(` Impact: ${opt.impact} | Effort: ${opt.effort}`);
|
|
4058
|
-
});
|
|
4059
|
-
|
|
4060
|
-
console.log('\nâī¸ Conditional Execution Rules:');
|
|
4061
|
-
Object.entries(analysis.conditional_execution).forEach(([rule, enabled]) => {
|
|
4062
|
-
console.log(` ${enabled ? 'â
' : 'â'} ${rule.replace(/_/g, ' ')}`);
|
|
4063
|
-
});
|
|
4064
|
-
|
|
4065
|
-
console.log('\nđĻ Cache Strategy:');
|
|
4066
|
-
Object.entries(analysis.cache_strategy).forEach(([cache, config]) => {
|
|
4067
|
-
console.log(` ${cache}: ${config.paths.join(', ')}`);
|
|
4068
|
-
});
|
|
4069
|
-
|
|
4070
|
-
console.log('\nđ Parallel Execution Groups:');
|
|
4071
|
-
analysis.parallel_groups.forEach((group) => {
|
|
4072
|
-
console.log(
|
|
4073
|
-
` ${group.name}: ${group.jobs.join(', ')} (max ${group.max_parallel} parallel, ${group.timeout}min timeout)`
|
|
4074
|
-
);
|
|
4075
|
-
});
|
|
4076
|
-
} catch (error) {
|
|
4077
|
-
console.error('â Failed to analyze CI/CD optimizations:', error.message);
|
|
4078
|
-
process.exit(1);
|
|
4079
|
-
}
|
|
4080
|
-
})
|
|
4081
|
-
)
|
|
4082
|
-
.addCommand(
|
|
4083
|
-
new Command('generate')
|
|
4084
|
-
.description('Generate optimized CI/CD configuration')
|
|
4085
|
-
.argument('[platform]', 'CI/CD platform (github, gitlab, jenkins)', 'github')
|
|
4086
|
-
.option('-o, --output <file>', 'Output file path')
|
|
4087
|
-
.action(async (platform, options) => {
|
|
4088
|
-
try {
|
|
4089
|
-
const CICDOptimizer = require('./cicd-optimizer');
|
|
4090
|
-
const optimizer = new CICDOptimizer();
|
|
4091
|
-
|
|
4092
|
-
console.log(`đ§ Generating optimized ${platform} CI/CD configuration...\n`);
|
|
4093
|
-
|
|
4094
|
-
const config = await optimizer.generateOptimizedConfig(platform);
|
|
4095
|
-
|
|
4096
|
-
if (options.output) {
|
|
4097
|
-
const fs = require('fs');
|
|
4098
|
-
const yaml = require('js-yaml');
|
|
4099
|
-
|
|
4100
|
-
if (platform === 'github') {
|
|
4101
|
-
// GitHub Actions uses YAML
|
|
4102
|
-
const yamlConfig = yaml.dump(config, { indent: 2 });
|
|
4103
|
-
fs.writeFileSync(options.output, yamlConfig);
|
|
4104
|
-
console.log(`â
Generated GitHub Actions workflow: ${options.output}`);
|
|
4105
|
-
} else if (platform === 'gitlab') {
|
|
4106
|
-
// GitLab CI uses YAML
|
|
4107
|
-
const yamlConfig = yaml.dump(config, { indent: 2 });
|
|
4108
|
-
fs.writeFileSync(options.output, yamlConfig);
|
|
4109
|
-
console.log(`â
Generated GitLab CI config: ${options.output}`);
|
|
4110
|
-
} else if (platform === 'jenkins') {
|
|
4111
|
-
// Jenkins uses Groovy
|
|
4112
|
-
fs.writeFileSync(options.output, config);
|
|
4113
|
-
console.log(`â
Generated Jenkins pipeline: ${options.output}`);
|
|
4114
|
-
}
|
|
4115
|
-
} else {
|
|
4116
|
-
// Print to console
|
|
4117
|
-
console.log('Generated configuration:');
|
|
4118
|
-
console.log('='.repeat(50));
|
|
4119
|
-
if (platform === 'github' || platform === 'gitlab') {
|
|
4120
|
-
console.log(JSON.stringify(config, null, 2));
|
|
4121
|
-
} else {
|
|
4122
|
-
console.log(config);
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
} catch (error) {
|
|
4126
|
-
console.error('â Failed to generate CI/CD config:', error.message);
|
|
4127
|
-
process.exit(1);
|
|
4128
|
-
}
|
|
4129
|
-
})
|
|
4130
|
-
)
|
|
4131
|
-
.addCommand(
|
|
4132
|
-
new Command('test-selection')
|
|
4133
|
-
.description('Analyze changed files and recommend test execution')
|
|
4134
|
-
.option('--changed-files <files>', 'Comma-separated list of changed files')
|
|
4135
|
-
.option('--from-commit <commit>', 'Analyze changes from specific commit')
|
|
4136
|
-
.action(async (options) => {
|
|
4137
|
-
try {
|
|
4138
|
-
const CICDOptimizer = require('./cicd-optimizer');
|
|
4139
|
-
const optimizer = new CICDOptimizer();
|
|
4140
|
-
|
|
4141
|
-
let changedFiles = [];
|
|
4142
|
-
|
|
4143
|
-
if (options.changedFiles) {
|
|
4144
|
-
changedFiles = options.changedFiles.split(',').map((f) => f.trim());
|
|
4145
|
-
} else if (options.fromCommit) {
|
|
4146
|
-
// Use git to get changed files
|
|
4147
|
-
const { execSync } = require('child_process');
|
|
4148
|
-
try {
|
|
4149
|
-
const output = execSync(`git diff --name-only ${options.fromCommit}`, {
|
|
4150
|
-
encoding: 'utf8',
|
|
4151
|
-
});
|
|
4152
|
-
changedFiles = output
|
|
4153
|
-
.trim()
|
|
4154
|
-
.split('\n')
|
|
4155
|
-
.filter((f) => f.length > 0);
|
|
4156
|
-
} catch (error) {
|
|
4157
|
-
console.error('â Failed to get changed files from git:', error.message);
|
|
4158
|
-
process.exit(1);
|
|
4159
|
-
}
|
|
4160
|
-
} else {
|
|
4161
|
-
console.error('â Must specify either --changed-files or --from-commit');
|
|
4162
|
-
process.exit(1);
|
|
4163
|
-
}
|
|
4164
|
-
|
|
4165
|
-
console.log(`đ Analyzing ${changedFiles.length} changed files...\n`);
|
|
4166
|
-
|
|
4167
|
-
const affectedTests = await optimizer.analyzeChangedFiles(changedFiles);
|
|
4168
|
-
|
|
4169
|
-
console.log('đ Recommended Test Execution:');
|
|
4170
|
-
|
|
4171
|
-
if (affectedTests.unit.length > 0) {
|
|
4172
|
-
console.log('đ§Ē Unit Tests:');
|
|
4173
|
-
affectedTests.unit.forEach((test) => console.log(` âĸ ${test}`));
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
if (affectedTests.integration.length > 0) {
|
|
4177
|
-
console.log('đ Integration Tests:');
|
|
4178
|
-
affectedTests.integration.forEach((test) => console.log(` âĸ ${test}`));
|
|
4179
|
-
}
|
|
4180
|
-
|
|
4181
|
-
if (affectedTests.contract.length > 0) {
|
|
4182
|
-
console.log('đ Contract Tests:');
|
|
4183
|
-
affectedTests.contract.forEach((test) => console.log(` âĸ ${test}`));
|
|
4184
|
-
}
|
|
4185
|
-
|
|
4186
|
-
if (affectedTests.e2e.length > 0) {
|
|
4187
|
-
console.log('đ E2E Tests:');
|
|
4188
|
-
affectedTests.e2e.forEach((test) => console.log(` âĸ ${test}`));
|
|
4189
|
-
}
|
|
4190
|
-
|
|
4191
|
-
const totalTests = Object.values(affectedTests).flat().length;
|
|
4192
|
-
console.log(`\nđ Total recommended tests: ${totalTests}`);
|
|
4193
|
-
|
|
4194
|
-
if (totalTests === 0) {
|
|
4195
|
-
console.log('âšī¸ No specific tests recommended - consider running full test suite');
|
|
4196
|
-
}
|
|
4197
|
-
} catch (error) {
|
|
4198
|
-
console.error('â Failed to analyze test selection:', error.message);
|
|
4199
|
-
process.exit(1);
|
|
4200
|
-
}
|
|
4201
|
-
})
|
|
4202
|
-
);
|
|
4203
|
-
|
|
4204
|
-
program
|
|
4205
|
-
.command('experimental')
|
|
4206
|
-
.description('Experimental features and dry-run capabilities')
|
|
4207
|
-
.option('--dry-run', 'Run in dry-run mode without making actual changes', false)
|
|
4208
|
-
.addCommand(
|
|
4209
|
-
new Command('validate')
|
|
4210
|
-
.description('Validate working spec with experimental features')
|
|
4211
|
-
.argument('<spec-file>', 'Path to working spec file')
|
|
4212
|
-
.option('--enhanced-analysis', 'Use enhanced analysis features', false)
|
|
4213
|
-
.option('--predictive-scoring', 'Enable predictive quality scoring', false)
|
|
4214
|
-
.action(async (specFile, options, cmd) => {
|
|
4215
|
-
const isDryRun = cmd.parent.opts().dryRun;
|
|
4216
|
-
|
|
4217
|
-
if (isDryRun) {
|
|
4218
|
-
console.log('đī¸ EXPERIMENTAL MODE - DRY RUN');
|
|
4219
|
-
console.log('No actual validation will be performed\n');
|
|
4220
|
-
}
|
|
4221
|
-
|
|
4222
|
-
console.log('đ§Ē Experimental Features Enabled:');
|
|
4223
|
-
if (options.enhancedAnalysis) console.log(' âĸ Enhanced analysis features');
|
|
4224
|
-
if (options.predictiveScoring) console.log(' âĸ Predictive quality scoring');
|
|
4225
|
-
console.log('');
|
|
4226
|
-
|
|
4227
|
-
if (isDryRun) {
|
|
4228
|
-
console.log('đ Would validate:', specFile);
|
|
4229
|
-
console.log('đ¯ Would check enhanced analysis:', options.enhancedAnalysis);
|
|
4230
|
-
console.log('đŽ Would enable predictive scoring:', options.predictiveScoring);
|
|
4231
|
-
console.log('\nâ
Dry run completed - no changes made');
|
|
4232
|
-
return;
|
|
4233
|
-
}
|
|
4234
|
-
|
|
4235
|
-
// Implement experimental validation logic here
|
|
4236
|
-
try {
|
|
4237
|
-
console.log('đŦ Running experimental validation...');
|
|
4238
|
-
|
|
4239
|
-
// For now, fall back to standard validation
|
|
4240
|
-
const result = await validateWorkingSpec(specFile);
|
|
4241
|
-
|
|
4242
|
-
if (result.valid) {
|
|
4243
|
-
console.log('â
Experimental validation passed');
|
|
4244
|
-
if (options.enhancedAnalysis) {
|
|
4245
|
-
console.log('đ§Ē Enhanced analysis: Spec structure is optimal');
|
|
4246
|
-
}
|
|
4247
|
-
if (options.predictiveScoring) {
|
|
4248
|
-
console.log('đŽ Predictive score: High confidence in success');
|
|
4249
|
-
}
|
|
4250
|
-
} else {
|
|
4251
|
-
console.log('â Experimental validation failed');
|
|
4252
|
-
result.errors.forEach((error) => console.log(` ${error}`));
|
|
4253
|
-
}
|
|
4254
|
-
} catch (error) {
|
|
4255
|
-
console.error('â Experimental validation error:', error.message);
|
|
4256
|
-
process.exit(1);
|
|
4257
|
-
}
|
|
4258
|
-
})
|
|
4259
|
-
)
|
|
4260
|
-
.addCommand(
|
|
4261
|
-
new Command('quality-gates')
|
|
4262
|
-
.description('Run quality gates with experimental features')
|
|
4263
|
-
.argument('<spec-file>', 'Path to working spec file')
|
|
4264
|
-
.option('--smart-selection', 'Use smart test selection based on changes', false)
|
|
4265
|
-
.option('--parallel-execution', 'Enable parallel gate execution', false)
|
|
4266
|
-
.option('--predictive-failures', 'Predict and skip likely failures', false)
|
|
4267
|
-
.action(async (specFile, options, cmd) => {
|
|
4268
|
-
const isDryRun = cmd.parent.opts().dryRun;
|
|
4269
|
-
|
|
4270
|
-
if (isDryRun) {
|
|
4271
|
-
console.log('đī¸ EXPERIMENTAL MODE - DRY RUN');
|
|
4272
|
-
console.log('No quality gates will actually execute\n');
|
|
4273
|
-
}
|
|
4274
|
-
|
|
4275
|
-
console.log('đ§Ē Experimental Quality Gate Features:');
|
|
4276
|
-
if (options.smartSelection) console.log(' âĸ Smart test selection');
|
|
4277
|
-
if (options.parallelExecution) console.log(' âĸ Parallel execution');
|
|
4278
|
-
if (options.predictiveFailures) console.log(' âĸ Predictive failure detection');
|
|
4279
|
-
console.log('');
|
|
4280
|
-
|
|
4281
|
-
if (isDryRun) {
|
|
4282
|
-
console.log('đ¯ Would run quality gates for:', specFile);
|
|
4283
|
-
console.log('đ§ Smart selection:', options.smartSelection);
|
|
4284
|
-
console.log('⥠Parallel execution:', options.parallelExecution);
|
|
4285
|
-
console.log('đŽ Predictive failures:', options.predictiveFailures);
|
|
4286
|
-
console.log('\nâ
Dry run completed - no gates executed');
|
|
4287
|
-
return;
|
|
4288
|
-
}
|
|
4289
|
-
|
|
4290
|
-
// Implement experimental quality gate logic
|
|
4291
|
-
try {
|
|
4292
|
-
console.log('đ Running experimental quality gates...');
|
|
4293
|
-
|
|
4294
|
-
// For now, fall back to standard quality gates
|
|
4295
|
-
await runQualityGatesForSpec(require('fs').readFileSync(specFile, 'utf8'), false);
|
|
4296
|
-
|
|
4297
|
-
console.log('â
Experimental quality gates completed');
|
|
4298
|
-
if (options.parallelExecution) {
|
|
4299
|
-
console.log('⥠Parallel execution: Simulated parallel processing');
|
|
4300
|
-
}
|
|
4301
|
-
if (options.smartSelection) {
|
|
4302
|
-
console.log('đ§ Smart selection: Optimized test execution');
|
|
4303
|
-
}
|
|
4304
|
-
} catch (error) {
|
|
4305
|
-
console.error('â Experimental quality gates failed:', error.message);
|
|
4306
|
-
process.exit(1);
|
|
4307
|
-
}
|
|
4308
|
-
})
|
|
4309
|
-
);
|
|
4310
|
-
|
|
4311
|
-
program
|
|
4312
|
-
.command('waivers')
|
|
4313
|
-
.description('Manage CAWS waivers (fast-lane escape hatches)')
|
|
4314
|
-
.addCommand(
|
|
4315
|
-
new Command('create')
|
|
4316
|
-
.description('Create a new waiver')
|
|
4317
|
-
.requiredOption('-t, --title <title>', 'Waiver title (10-200 characters)')
|
|
4318
|
-
.requiredOption(
|
|
4319
|
-
'-r, --reason <reason>',
|
|
4320
|
-
'Waiver reason (emergency_hotfix, legacy_integration, experimental_feature, third_party_constraint, performance_critical, security_patch, infrastructure_limitation, other)'
|
|
4321
|
-
)
|
|
4322
|
-
.requiredOption('-d, --description <desc>', 'Detailed description (50-1000 characters)')
|
|
4323
|
-
.requiredOption('-g, --gates <gates>', 'Comma-separated list of gates to waive')
|
|
4324
|
-
.requiredOption('--expires-at <datetime>', 'Expiration date (ISO 8601 format)')
|
|
4325
|
-
.requiredOption('--approved-by <approver>', 'Person/entity approving the waiver')
|
|
4326
|
-
.requiredOption('--impact-level <level>', 'Risk impact level (low, medium, high, critical)')
|
|
4327
|
-
.requiredOption('--mitigation-plan <plan>', 'Risk mitigation plan (50+ characters)')
|
|
4328
|
-
.option('--review-required', 'Flag waiver as requiring manual review', false)
|
|
4329
|
-
.option('--environment <env>', 'Environment restriction (development, staging, production)')
|
|
4330
|
-
.option('--urgency <level>', 'Urgency level (low, normal, high, critical)', 'normal')
|
|
4331
|
-
.option('--related-pr <pr>', 'Related pull request URL')
|
|
4332
|
-
.option('--related-issue <issue>', 'Related issue URL')
|
|
4333
|
-
.action(async (options) => {
|
|
4334
|
-
try {
|
|
4335
|
-
const WaiversManager = require('./waivers-manager');
|
|
4336
|
-
const waiversManager = new WaiversManager();
|
|
4337
|
-
|
|
4338
|
-
const waiverData = {
|
|
4339
|
-
title: options.title,
|
|
4340
|
-
reason: options.reason,
|
|
4341
|
-
description: options.description,
|
|
4342
|
-
gates: options.gates.split(',').map((g) => g.trim()),
|
|
4343
|
-
expires_at: options.expiresAt,
|
|
4344
|
-
approved_by: options.approvedBy,
|
|
4345
|
-
risk_assessment: {
|
|
4346
|
-
impact_level: options.impactLevel,
|
|
4347
|
-
mitigation_plan: options.mitigationPlan,
|
|
4348
|
-
review_required: options.reviewRequired || false,
|
|
4349
|
-
},
|
|
4350
|
-
metadata: {
|
|
4351
|
-
environment: options.environment,
|
|
4352
|
-
urgency: options.urgency,
|
|
4353
|
-
related_pr: options.relatedPr,
|
|
4354
|
-
related_issue: options.relatedIssue,
|
|
4355
|
-
},
|
|
4356
|
-
};
|
|
4357
|
-
|
|
4358
|
-
const waiver = await waiversManager.createWaiver(waiverData);
|
|
4359
|
-
|
|
4360
|
-
console.log(`â
Waiver created successfully: ${waiver.id}`);
|
|
4361
|
-
console.log(`đ Title: ${waiver.title}`);
|
|
4362
|
-
console.log(`â° Expires: ${waiver.expires_at}`);
|
|
4363
|
-
console.log(`đ¯ Gates waived: ${waiver.gates.join(', ')}`);
|
|
4364
|
-
|
|
4365
|
-
if (
|
|
4366
|
-
waiver.risk_assessment.impact_level === 'critical' ||
|
|
4367
|
-
waiver.risk_assessment.review_required
|
|
4368
|
-
) {
|
|
4369
|
-
console.log('\nâ ī¸ HIGH RISK WAIVER - Manual review required');
|
|
4370
|
-
console.log(`đ Review file created: .caws/waivers/review-${waiver.id}.md`);
|
|
4371
|
-
console.log('đ Please have code owners review this waiver before use');
|
|
4372
|
-
}
|
|
4373
|
-
} catch (error) {
|
|
4374
|
-
console.error('â Failed to create waiver:', error.message);
|
|
4375
|
-
process.exit(1);
|
|
4376
|
-
}
|
|
4377
|
-
})
|
|
4378
|
-
)
|
|
4379
|
-
.addCommand(
|
|
4380
|
-
new Command('list')
|
|
4381
|
-
.alias('ls')
|
|
4382
|
-
.description('List active waivers')
|
|
4383
|
-
.option('-v, --verbose', 'Show detailed waiver information')
|
|
4384
|
-
.option('--expiring-soon', 'Show only waivers expiring within 7 days')
|
|
4385
|
-
.option('--high-risk', 'Show only high/critical risk waivers')
|
|
4386
|
-
.action(async (options) => {
|
|
4387
|
-
try {
|
|
4388
|
-
const WaiversManager = require('./waivers-manager');
|
|
4389
|
-
const waiversManager = new WaiversManager();
|
|
4390
|
-
|
|
4391
|
-
let waivers = await waiversManager.getActiveWaivers();
|
|
4392
|
-
|
|
4393
|
-
// Apply filters
|
|
4394
|
-
if (options.expiringSoon) {
|
|
4395
|
-
const sevenDaysFromNow = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
4396
|
-
waivers = waivers.filter((w) => new Date(w.expires_at) <= sevenDaysFromNow);
|
|
4397
|
-
}
|
|
4398
|
-
|
|
4399
|
-
if (options.highRisk) {
|
|
4400
|
-
waivers = waivers.filter(
|
|
4401
|
-
(w) =>
|
|
4402
|
-
w.risk_assessment.impact_level === 'high' ||
|
|
4403
|
-
w.risk_assessment.impact_level === 'critical'
|
|
4404
|
-
);
|
|
4405
|
-
}
|
|
4406
|
-
|
|
4407
|
-
if (waivers.length === 0) {
|
|
4408
|
-
console.log('âšī¸ No active waivers found');
|
|
4409
|
-
return;
|
|
4410
|
-
}
|
|
4411
|
-
|
|
4412
|
-
console.log(`đ Active waivers: ${waivers.length}`);
|
|
4413
|
-
console.log('');
|
|
4414
|
-
|
|
4415
|
-
for (const waiver of waivers) {
|
|
4416
|
-
const expiresAt = new Date(waiver.expires_at);
|
|
4417
|
-
const now = new Date();
|
|
4418
|
-
const daysRemaining = Math.ceil((expiresAt - now) / (24 * 60 * 60 * 1000));
|
|
4419
|
-
|
|
4420
|
-
console.log(`đ ${waiver.id}: ${waiver.title}`);
|
|
4421
|
-
console.log(` â° Expires: ${waiver.expires_at} (${daysRemaining} days remaining)`);
|
|
4422
|
-
console.log(` đ¯ Gates: ${waiver.gates.join(', ')}`);
|
|
4423
|
-
console.log(` â ī¸ Risk: ${waiver.risk_assessment.impact_level}`);
|
|
4424
|
-
console.log(` đ¤ Approved by: ${waiver.approved_by}`);
|
|
4425
|
-
|
|
4426
|
-
if (options.verbose) {
|
|
4427
|
-
console.log(` đ Reason: ${waiver.reason}`);
|
|
4428
|
-
console.log(
|
|
4429
|
-
` đ Description: ${waiver.description.substring(0, 100)}${waiver.description.length > 100 ? '...' : ''}`
|
|
4430
|
-
);
|
|
4431
|
-
console.log(
|
|
4432
|
-
` đĄī¸ Mitigation: ${waiver.risk_assessment.mitigation_plan.substring(0, 100)}${waiver.risk_assessment.mitigation_plan.length > 100 ? '...' : ''}`
|
|
4433
|
-
);
|
|
4434
|
-
|
|
4435
|
-
if (waiver.metadata) {
|
|
4436
|
-
const metadata = [];
|
|
4437
|
-
if (waiver.metadata.environment)
|
|
4438
|
-
metadata.push(`Env: ${waiver.metadata.environment}`);
|
|
4439
|
-
if (waiver.metadata.urgency) metadata.push(`Urgency: ${waiver.metadata.urgency}`);
|
|
4440
|
-
if (waiver.metadata.related_pr) metadata.push(`PR: ${waiver.metadata.related_pr}`);
|
|
4441
|
-
if (metadata.length > 0) {
|
|
4442
|
-
console.log(` đ Metadata: ${metadata.join(', ')}`);
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
|
-
console.log('');
|
|
4448
|
-
}
|
|
4449
|
-
} catch (error) {
|
|
4450
|
-
console.error('â Failed to list waivers:', error.message);
|
|
4451
|
-
process.exit(1);
|
|
4452
|
-
}
|
|
4453
|
-
})
|
|
4454
|
-
)
|
|
4455
|
-
.addCommand(
|
|
4456
|
-
new Command('revoke')
|
|
4457
|
-
.description('Revoke an active waiver')
|
|
4458
|
-
.argument('<waiver-id>', 'Waiver ID to revoke')
|
|
4459
|
-
.option('-r, --reason <reason>', 'Reason for revocation', 'Manual revocation')
|
|
4460
|
-
.action(async (waiverId, options) => {
|
|
4461
|
-
try {
|
|
4462
|
-
const WaiversManager = require('./waivers-manager');
|
|
4463
|
-
const waiversManager = new WaiversManager();
|
|
4464
|
-
|
|
4465
|
-
const waiver = await waiversManager.revokeWaiver(waiverId, options.reason);
|
|
4466
|
-
|
|
4467
|
-
console.log(`â
Waiver revoked: ${waiverId}`);
|
|
4468
|
-
console.log(`đ Reason: ${options.reason}`);
|
|
4469
|
-
console.log(`đ¯ Gates no longer waived: ${waiver.gates.join(', ')}`);
|
|
4470
|
-
} catch (error) {
|
|
4471
|
-
console.error('â Failed to revoke waiver:', error.message);
|
|
4472
|
-
process.exit(1);
|
|
4473
|
-
}
|
|
4474
|
-
})
|
|
4475
|
-
)
|
|
4476
|
-
.addCommand(
|
|
4477
|
-
new Command('extend')
|
|
4478
|
-
.description('Extend waiver expiration date')
|
|
4479
|
-
.argument('<waiver-id>', 'Waiver ID to extend')
|
|
4480
|
-
.requiredOption('--new-expiry <datetime>', 'New expiration date (ISO 8601 format)')
|
|
4481
|
-
.requiredOption('--approved-by <approver>', 'Person approving the extension')
|
|
4482
|
-
.action(async (waiverId, options) => {
|
|
4483
|
-
try {
|
|
4484
|
-
const WaiversManager = require('./waivers-manager');
|
|
4485
|
-
const waiversManager = new WaiversManager();
|
|
4486
|
-
|
|
4487
|
-
const waiver = await waiversManager.extendWaiver(
|
|
4488
|
-
waiverId,
|
|
4489
|
-
options.newExpiry,
|
|
4490
|
-
options.approvedBy
|
|
4491
|
-
);
|
|
4492
|
-
|
|
4493
|
-
console.log(`â
Waiver extended: ${waiverId}`);
|
|
4494
|
-
console.log(`â° New expiry: ${waiver.expires_at}`);
|
|
4495
|
-
console.log(`đ¤ Extended by: ${options.approvedBy}`);
|
|
4496
|
-
} catch (error) {
|
|
4497
|
-
console.error('â Failed to extend waiver:', error.message);
|
|
4498
|
-
process.exit(1);
|
|
4499
|
-
}
|
|
4500
|
-
})
|
|
4501
|
-
)
|
|
4502
|
-
.addCommand(
|
|
4503
|
-
new Command('stats')
|
|
4504
|
-
.description('Show waiver statistics and health metrics')
|
|
4505
|
-
.action(async () => {
|
|
4506
|
-
try {
|
|
4507
|
-
const WaiversManager = require('./waivers-manager');
|
|
4508
|
-
const waiversManager = new WaiversManager();
|
|
4509
|
-
|
|
4510
|
-
const stats = await waiversManager.getWaiverStats();
|
|
4511
|
-
|
|
4512
|
-
console.log('đ Waiver Statistics');
|
|
4513
|
-
console.log('==================');
|
|
4514
|
-
|
|
4515
|
-
console.log(`đ Total active waivers: ${stats.total_active}`);
|
|
4516
|
-
console.log(`đ¯ Total gates waived: ${stats.total_gates_waived}`);
|
|
4517
|
-
console.log(`đ
Average lifespan: ${stats.average_lifespan_days.toFixed(1)} days`);
|
|
4518
|
-
|
|
4519
|
-
if (stats.expiring_soon.length > 0) {
|
|
4520
|
-
console.log(`\nâ° Expiring soon (${stats.expiring_soon.length}):`);
|
|
4521
|
-
stats.expiring_soon.forEach((w) => {
|
|
4522
|
-
console.log(` ${w.id}: ${w.days_remaining} days (${w.title.substring(0, 50)}...)`);
|
|
4523
|
-
});
|
|
4524
|
-
}
|
|
4525
|
-
|
|
4526
|
-
if (stats.high_risk.length > 0) {
|
|
4527
|
-
console.log(`\nâ ī¸ High risk waivers (${stats.high_risk.length}):`);
|
|
4528
|
-
stats.high_risk.forEach((w) => {
|
|
4529
|
-
console.log(
|
|
4530
|
-
` ${w.id}: ${w.risk_level} - ${w.reason} (${w.title.substring(0, 40)}...)`
|
|
4531
|
-
);
|
|
4532
|
-
});
|
|
4533
|
-
}
|
|
4534
|
-
|
|
4535
|
-
console.log(`\nđ By reason:`);
|
|
4536
|
-
Object.entries(stats.by_reason).forEach(([reason, count]) => {
|
|
4537
|
-
console.log(` ${reason}: ${count}`);
|
|
4538
|
-
});
|
|
4539
|
-
|
|
4540
|
-
console.log(`\nâ ī¸ By risk level:`);
|
|
4541
|
-
Object.entries(stats.by_risk_level).forEach(([level, count]) => {
|
|
4542
|
-
console.log(` ${level}: ${count}`);
|
|
4543
|
-
});
|
|
4544
|
-
} catch (error) {
|
|
4545
|
-
console.error('â Failed to get waiver stats:', error.message);
|
|
4546
|
-
process.exit(1);
|
|
4547
|
-
}
|
|
4548
|
-
})
|
|
4549
|
-
);
|
|
4550
|
-
|
|
4551
|
-
program
|
|
4552
|
-
.command('tools')
|
|
4553
|
-
.description('Manage CAWS tools')
|
|
4554
|
-
.addCommand(
|
|
4555
|
-
new Command('list')
|
|
4556
|
-
.alias('ls')
|
|
4557
|
-
.description('List available tools')
|
|
4558
|
-
.option('-v, --verbose', 'Show detailed tool information')
|
|
4559
|
-
.action(async (options) => {
|
|
4560
|
-
try {
|
|
4561
|
-
const loader = await initializeToolSystem();
|
|
4562
|
-
if (!loader) {
|
|
4563
|
-
console.error(chalk.red('â Tool system not available'));
|
|
4564
|
-
process.exit(1);
|
|
4565
|
-
}
|
|
4566
|
-
|
|
4567
|
-
const tools = loader.getAllTools();
|
|
4568
|
-
|
|
4569
|
-
if (tools.size === 0) {
|
|
4570
|
-
console.log(chalk.yellow('â ī¸ No tools loaded'));
|
|
4571
|
-
console.log(chalk.blue('đĄ Add tools to apps/tools/caws/ directory'));
|
|
4572
|
-
return;
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
|
-
console.log(chalk.blue(`đ§ Available Tools (${tools.size}):`));
|
|
4576
|
-
console.log('');
|
|
4577
|
-
|
|
4578
|
-
for (const [id, tool] of tools) {
|
|
4579
|
-
const metadata = tool.metadata;
|
|
4580
|
-
console.log(chalk.green(`đĻ ${metadata.name} (${id})`));
|
|
4581
|
-
console.log(` ${metadata.description || 'No description'}`);
|
|
4582
|
-
if (metadata.version) {
|
|
4583
|
-
console.log(chalk.gray(` Version: ${metadata.version}`));
|
|
4584
|
-
}
|
|
4585
|
-
if (metadata.capabilities && metadata.capabilities.length > 0) {
|
|
4586
|
-
console.log(chalk.gray(` Capabilities: ${metadata.capabilities.join(', ')}`));
|
|
4587
|
-
}
|
|
4588
|
-
if (options.verbose && metadata.dependencies) {
|
|
4589
|
-
console.log(chalk.gray(` Dependencies: ${metadata.dependencies.join(', ')}`));
|
|
4590
|
-
}
|
|
4591
|
-
console.log('');
|
|
4592
|
-
}
|
|
4593
|
-
} catch (error) {
|
|
4594
|
-
console.error(chalk.red('â Error listing tools:'), error.message);
|
|
4595
|
-
process.exit(1);
|
|
4596
|
-
}
|
|
4597
|
-
})
|
|
4598
|
-
)
|
|
4599
|
-
.addCommand(
|
|
4600
|
-
new Command('run')
|
|
4601
|
-
.description('Execute a specific tool')
|
|
4602
|
-
.argument('<tool-id>', 'Tool identifier to execute')
|
|
4603
|
-
.option('-p, --params <json>', 'JSON parameters for tool execution')
|
|
4604
|
-
.option('-t, --timeout <ms>', 'Execution timeout in milliseconds', parseInt, 30000)
|
|
4605
|
-
.action(async (toolId, options) => {
|
|
4606
|
-
try {
|
|
4607
|
-
const loader = await initializeToolSystem();
|
|
4608
|
-
if (!loader) {
|
|
4609
|
-
console.error(chalk.red('â Tool system not available'));
|
|
4610
|
-
process.exit(1);
|
|
4611
|
-
}
|
|
4612
|
-
|
|
4613
|
-
// Load all tools first
|
|
4614
|
-
await loader.loadAllTools();
|
|
4615
|
-
const tool = loader.getTool(toolId);
|
|
4616
|
-
|
|
4617
|
-
if (!tool) {
|
|
4618
|
-
console.error(chalk.red(`â Tool '${toolId}' not found`));
|
|
4619
|
-
console.log(chalk.blue('đĄ Available tools:'));
|
|
4620
|
-
const tools = loader.getAllTools();
|
|
4621
|
-
for (const [id, t] of tools) {
|
|
4622
|
-
console.log(` - ${id}: ${t.metadata.name}`);
|
|
4623
|
-
}
|
|
4624
|
-
process.exit(1);
|
|
4625
|
-
}
|
|
4626
|
-
|
|
4627
|
-
// Validate tool before execution
|
|
4628
|
-
const validation = await toolValidator.validateTool(tool);
|
|
4629
|
-
if (!validation.valid) {
|
|
4630
|
-
console.error(chalk.red('â Tool validation failed:'));
|
|
4631
|
-
validation.errors.forEach((error) => {
|
|
4632
|
-
console.error(` ${chalk.red('â')} ${error}`);
|
|
4633
|
-
});
|
|
4634
|
-
process.exit(1);
|
|
4635
|
-
}
|
|
4636
|
-
|
|
4637
|
-
// Parse parameters
|
|
4638
|
-
let params = {};
|
|
4639
|
-
if (options.params) {
|
|
4640
|
-
try {
|
|
4641
|
-
params = JSON.parse(options.params);
|
|
4642
|
-
} catch (error) {
|
|
4643
|
-
console.error(chalk.red('â Invalid JSON parameters:'), error.message);
|
|
4644
|
-
process.exit(1);
|
|
4645
|
-
}
|
|
4646
|
-
}
|
|
4647
|
-
|
|
4648
|
-
console.log(chalk.blue(`đ Executing tool: ${tool.metadata.name}`));
|
|
4649
|
-
|
|
4650
|
-
// Execute tool
|
|
4651
|
-
const result = await tool.module.execute(params, {
|
|
4652
|
-
workingDirectory: process.cwd(),
|
|
4653
|
-
timeout: options.timeout,
|
|
4654
|
-
});
|
|
4655
|
-
|
|
4656
|
-
// Display results
|
|
4657
|
-
if (result.success) {
|
|
4658
|
-
console.log(chalk.green('â
Tool execution successful'));
|
|
4659
|
-
if (result.output && typeof result.output === 'object') {
|
|
4660
|
-
console.log(chalk.gray('Output:'), JSON.stringify(result.output, null, 2));
|
|
4661
|
-
}
|
|
4662
|
-
} else {
|
|
4663
|
-
console.error(chalk.red('â Tool execution failed'));
|
|
4664
|
-
result.errors.forEach((error) => {
|
|
4665
|
-
console.error(` ${chalk.red('â')} ${error}`);
|
|
4666
|
-
});
|
|
4667
|
-
process.exit(1);
|
|
4668
|
-
}
|
|
4669
|
-
} catch (error) {
|
|
4670
|
-
console.error(chalk.red(`â Error executing tool ${toolId}:`), error.message);
|
|
4671
|
-
process.exit(1);
|
|
4672
|
-
}
|
|
4673
|
-
})
|
|
4674
|
-
);
|
|
4675
|
-
|
|
4676
|
-
// Error handling
|
|
4677
|
-
program.exitOverride((err) => {
|
|
4678
|
-
if (
|
|
4679
|
-
err.code === 'commander.help' ||
|
|
4680
|
-
err.code === 'commander.version' ||
|
|
4681
|
-
err.message.includes('outputHelp')
|
|
4682
|
-
) {
|
|
4683
|
-
process.exit(0);
|
|
4684
|
-
}
|
|
4685
|
-
console.error(chalk.red('â Error:'), err.message);
|
|
4686
|
-
process.exit(1);
|
|
4687
|
-
});
|
|
4688
|
-
|
|
4689
|
-
// Parse and run (only when run directly, not when required as module)
|
|
4690
|
-
if (require.main === module) {
|
|
4691
|
-
try {
|
|
4692
|
-
program.parse();
|
|
4693
|
-
} catch (error) {
|
|
4694
|
-
if (
|
|
4695
|
-
error.code === 'commander.help' ||
|
|
4696
|
-
error.code === 'commander.version' ||
|
|
4697
|
-
error.message.includes('outputHelp')
|
|
4698
|
-
) {
|
|
4699
|
-
process.exit(0);
|
|
4700
|
-
} else {
|
|
4701
|
-
console.error(chalk.red('â Error:'), error.message);
|
|
4702
|
-
process.exit(1);
|
|
4703
|
-
}
|
|
4704
|
-
}
|
|
4705
|
-
}
|
|
4706
|
-
|
|
4707
|
-
// Export functions for testing
|
|
4708
|
-
module.exports = {
|
|
4709
|
-
generateWorkingSpec,
|
|
4710
|
-
validateGeneratedSpec,
|
|
4711
|
-
};
|