@paths.design/caws-cli 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -150
- package/dist/budget-derivation.d.ts +35 -0
- package/dist/budget-derivation.d.ts.map +1 -0
- package/dist/budget-derivation.js +204 -0
- package/dist/cicd-optimizer.d.ts +142 -0
- package/dist/cicd-optimizer.d.ts.map +1 -0
- package/dist/cicd-optimizer.js +504 -0
- package/dist/commands/burnup.d.ts +6 -0
- package/dist/commands/burnup.d.ts.map +1 -0
- package/dist/commands/burnup.js +90 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +514 -0
- package/dist/commands/provenance.d.ts +32 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +979 -0
- package/dist/commands/tool.d.ts +13 -0
- package/dist/commands/tool.d.ts.map +1 -0
- package/dist/commands/tool.js +138 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +80 -0
- package/dist/config/index.d.ts +29 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +132 -0
- package/dist/error-handler.d.ts +50 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +253 -0
- package/dist/generators/working-spec.d.ts +13 -0
- package/dist/generators/working-spec.d.ts.map +1 -0
- package/dist/generators/working-spec.js +204 -0
- package/dist/index.d.ts +3 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +193 -2983
- package/dist/scaffold/cursor-hooks.d.ts +7 -0
- package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
- package/dist/scaffold/cursor-hooks.js +152 -0
- 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 +20 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +486 -0
- package/dist/test-analysis.d.ts +182 -0
- package/dist/test-analysis.d.ts.map +1 -0
- package/dist/test-analysis.js +580 -0
- package/dist/tool-interface.d.ts +236 -0
- package/dist/tool-interface.d.ts.map +1 -0
- package/dist/tool-interface.js +314 -0
- package/dist/tool-loader.d.ts +77 -0
- package/dist/tool-loader.d.ts.map +1 -0
- package/dist/tool-loader.js +298 -0
- package/dist/tool-validator.d.ts +72 -0
- package/dist/tool-validator.d.ts.map +1 -0
- package/dist/tool-validator.js +387 -0
- package/dist/utils/detection.d.ts +7 -0
- package/dist/utils/detection.d.ts.map +1 -0
- package/dist/utils/detection.js +174 -0
- package/dist/utils/finalization.d.ts +17 -0
- package/dist/utils/finalization.d.ts.map +1 -0
- package/dist/utils/finalization.js +229 -0
- package/dist/utils/project-analysis.d.ts +14 -0
- package/dist/utils/project-analysis.d.ts.map +1 -0
- package/dist/utils/project-analysis.js +105 -0
- package/dist/validation/spec-validation.d.ts +29 -0
- package/dist/validation/spec-validation.d.ts.map +1 -0
- package/dist/validation/spec-validation.js +376 -0
- package/dist/waivers-manager.d.ts +167 -0
- package/dist/waivers-manager.d.ts.map +1 -0
- package/dist/waivers-manager.js +549 -0
- package/package.json +10 -12
- package/templates/.cursor/README.md +311 -0
- package/templates/.cursor/hooks/audit.sh +55 -0
- package/templates/.cursor/hooks/block-dangerous.sh +77 -0
- package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
- package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/templates/.cursor/hooks/format.sh +38 -0
- package/templates/.cursor/hooks/naming-check.sh +64 -0
- package/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/templates/.cursor/hooks/validate-spec.sh +38 -0
- package/templates/.cursor/hooks.json +59 -0
- package/templates/.github/copilot/instructions.md +311 -0
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/templates/.vscode/launch.json +56 -0
- package/templates/.vscode/settings.json +93 -0
- package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/templates/apps/tools/caws/README.md +1 -1
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
- package/templates/codemod/test.js +93 -1
- 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
CHANGED
|
@@ -7,3018 +7,228 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { Command } = require('commander');
|
|
10
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
11
|
const fs = require('fs-extra');
|
|
12
|
+
// eslint-disable-next-line no-unused-vars
|
|
11
13
|
const path = require('path');
|
|
12
|
-
|
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
|
13
15
|
const yaml = require('js-yaml');
|
|
14
16
|
const chalk = require('chalk');
|
|
15
17
|
|
|
16
|
-
// Import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(chalk.blue('🔍 Detecting CAWS setup...'));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Check for existing CAWS setup
|
|
60
|
-
const cawsDir = path.join(cwd, '.caws');
|
|
61
|
-
const hasCAWSDir = fs.existsSync(cawsDir);
|
|
62
|
-
|
|
63
|
-
if (!hasCAWSDir) {
|
|
64
|
-
if (!isQuietCommand) {
|
|
65
|
-
console.log(chalk.gray('ℹ️ No .caws directory found - new project setup'));
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
type: 'new',
|
|
69
|
-
hasCAWSDir: false,
|
|
70
|
-
cawsDir: null,
|
|
71
|
-
capabilities: [],
|
|
72
|
-
hasTemplateDir: false,
|
|
73
|
-
templateDir: null,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Analyze existing setup
|
|
78
|
-
const files = fs.readdirSync(cawsDir);
|
|
79
|
-
const hasWorkingSpec = fs.existsSync(path.join(cawsDir, 'working-spec.yaml'));
|
|
80
|
-
const hasValidateScript = fs.existsSync(path.join(cawsDir, 'validate.js'));
|
|
81
|
-
const hasPolicy = fs.existsSync(path.join(cawsDir, 'policy'));
|
|
82
|
-
const hasSchemas = fs.existsSync(path.join(cawsDir, 'schemas'));
|
|
83
|
-
const hasTemplates = fs.existsSync(path.join(cawsDir, 'templates'));
|
|
84
|
-
|
|
85
|
-
// Check for multiple spec files (enhanced project pattern)
|
|
86
|
-
const specFiles = files.filter((f) => f.endsWith('-spec.yaml'));
|
|
87
|
-
const hasMultipleSpecs = specFiles.length > 1;
|
|
88
|
-
|
|
89
|
-
// Check for tools directory (enhanced setup)
|
|
90
|
-
const toolsDir = path.join(cwd, 'apps/tools/caws');
|
|
91
|
-
const hasTools = fs.existsSync(toolsDir);
|
|
92
|
-
|
|
93
|
-
// Determine setup type
|
|
94
|
-
let setupType = 'basic';
|
|
95
|
-
let capabilities = [];
|
|
96
|
-
|
|
97
|
-
if (hasMultipleSpecs && hasWorkingSpec) {
|
|
98
|
-
setupType = 'enhanced';
|
|
99
|
-
capabilities.push('multiple-specs', 'working-spec', 'domain-specific');
|
|
100
|
-
} else if (hasWorkingSpec) {
|
|
101
|
-
setupType = 'standard';
|
|
102
|
-
capabilities.push('working-spec');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (hasValidateScript) {
|
|
106
|
-
capabilities.push('validation');
|
|
107
|
-
}
|
|
108
|
-
if (hasPolicy) {
|
|
109
|
-
capabilities.push('policies');
|
|
110
|
-
}
|
|
111
|
-
if (hasSchemas) {
|
|
112
|
-
capabilities.push('schemas');
|
|
113
|
-
}
|
|
114
|
-
if (hasTemplates) {
|
|
115
|
-
capabilities.push('templates');
|
|
116
|
-
}
|
|
117
|
-
if (hasTools) {
|
|
118
|
-
capabilities.push('tools');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!isQuietCommand) {
|
|
122
|
-
console.log(chalk.green(`✅ Detected ${setupType} CAWS setup`));
|
|
123
|
-
console.log(chalk.gray(` Capabilities: ${capabilities.join(', ')}`));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check for template directory - try multiple possible locations
|
|
127
|
-
let templateDir = null;
|
|
128
|
-
const possibleTemplatePaths = [
|
|
129
|
-
// FIRST: Try bundled templates (for npm-installed CLI)
|
|
130
|
-
{ path: path.resolve(__dirname, '../templates'), source: 'bundled with CLI' },
|
|
131
|
-
{ path: path.resolve(__dirname, 'templates'), source: 'bundled with CLI (fallback)' },
|
|
132
|
-
// Try relative to current working directory (for monorepo setups)
|
|
133
|
-
{ path: path.resolve(cwd, '../caws-template'), source: 'monorepo parent directory' },
|
|
134
|
-
{ path: path.resolve(cwd, '../../caws-template'), source: 'monorepo grandparent' },
|
|
135
|
-
{ path: path.resolve(cwd, '../../../caws-template'), source: 'workspace root' },
|
|
136
|
-
{ path: path.resolve(cwd, 'packages/caws-template'), source: 'packages/ subdirectory' },
|
|
137
|
-
{ path: path.resolve(cwd, 'caws-template'), source: 'caws-template/ subdirectory' },
|
|
138
|
-
// Try relative to CLI location (for installed CLI)
|
|
139
|
-
{ path: path.resolve(__dirname, '../caws-template'), source: 'CLI installation' },
|
|
140
|
-
{ path: path.resolve(__dirname, '../../caws-template'), source: 'CLI parent directory' },
|
|
141
|
-
{ path: path.resolve(__dirname, '../../../caws-template'), source: 'CLI workspace root' },
|
|
142
|
-
// Try absolute paths for CI environments
|
|
143
|
-
{ path: path.resolve(process.cwd(), 'packages/caws-template'), source: 'current packages/' },
|
|
144
|
-
{ path: path.resolve(process.cwd(), '../packages/caws-template'), source: 'parent packages/' },
|
|
145
|
-
{
|
|
146
|
-
path: path.resolve(process.cwd(), '../../packages/caws-template'),
|
|
147
|
-
source: 'grandparent packages/',
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
path: path.resolve(process.cwd(), '../../../packages/caws-template'),
|
|
151
|
-
source: 'workspace packages/',
|
|
152
|
-
},
|
|
153
|
-
// Try from workspace root
|
|
154
|
-
{ path: path.resolve(process.cwd(), 'caws-template'), source: 'workspace caws-template/' },
|
|
155
|
-
// Try various other common locations
|
|
156
|
-
{
|
|
157
|
-
path: '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
|
|
158
|
-
source: 'GitHub Actions CI',
|
|
159
|
-
},
|
|
160
|
-
{ path: '/workspace/packages/caws-template', source: 'Docker workspace' },
|
|
161
|
-
{ path: '/caws/packages/caws-template', source: 'Container workspace' },
|
|
162
|
-
];
|
|
163
|
-
|
|
164
|
-
for (const { path: testPath, source } of possibleTemplatePaths) {
|
|
165
|
-
if (fs.existsSync(testPath)) {
|
|
166
|
-
templateDir = testPath;
|
|
167
|
-
if (!isQuietCommand) {
|
|
168
|
-
console.log(`✅ Found CAWS templates in ${source}:`);
|
|
169
|
-
console.log(` ${chalk.gray(testPath)}`);
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (!templateDir && !isQuietCommand) {
|
|
176
|
-
console.warn(chalk.yellow('⚠️ CAWS templates not found in standard locations'));
|
|
177
|
-
console.warn(chalk.blue('💡 This may limit available scaffolding features'));
|
|
178
|
-
console.warn(
|
|
179
|
-
chalk.blue('💡 For full functionality, ensure caws-template package is available')
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const hasTemplateDir = templateDir !== null;
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
type: setupType,
|
|
187
|
-
hasCAWSDir: true,
|
|
188
|
-
cawsDir,
|
|
189
|
-
hasWorkingSpec,
|
|
190
|
-
hasMultipleSpecs,
|
|
191
|
-
hasValidateScript,
|
|
192
|
-
hasPolicy,
|
|
193
|
-
hasSchemas,
|
|
194
|
-
hasTemplates,
|
|
195
|
-
hasTools,
|
|
196
|
-
hasTemplateDir,
|
|
197
|
-
templateDir,
|
|
198
|
-
capabilities,
|
|
199
|
-
isEnhanced: setupType === 'enhanced',
|
|
200
|
-
isAdvanced: hasTools || hasValidateScript,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let cawsSetup = null;
|
|
205
|
-
|
|
206
|
-
// Initialize global setup detection
|
|
207
|
-
try {
|
|
208
|
-
cawsSetup = detectCAWSSetup();
|
|
209
|
-
|
|
210
|
-
// If no template dir found in current directory, check CLI installation location
|
|
211
|
-
if (!cawsSetup.hasTemplateDir) {
|
|
212
|
-
const cliTemplatePaths = [
|
|
213
|
-
path.resolve(__dirname, '../templates'),
|
|
214
|
-
path.resolve(__dirname, 'templates'),
|
|
215
|
-
];
|
|
216
|
-
|
|
217
|
-
for (const testPath of cliTemplatePaths) {
|
|
218
|
-
if (fs.existsSync(testPath)) {
|
|
219
|
-
cawsSetup.templateDir = testPath;
|
|
220
|
-
cawsSetup.hasTemplateDir = true;
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.warn('⚠️ Failed to detect CAWS setup globally:', error.message);
|
|
227
|
-
cawsSetup = {
|
|
228
|
-
type: 'unknown',
|
|
229
|
-
hasCAWSDir: false,
|
|
230
|
-
cawsDir: null,
|
|
231
|
-
hasWorkingSpec: false,
|
|
232
|
-
hasMultipleSpecs: false,
|
|
233
|
-
hasValidateScript: false,
|
|
234
|
-
hasPolicy: false,
|
|
235
|
-
hasSchemas: false,
|
|
236
|
-
hasTemplates: false,
|
|
237
|
-
hasTools: false,
|
|
238
|
-
hasTemplateDir: false,
|
|
239
|
-
templateDir: null,
|
|
240
|
-
capabilities: [],
|
|
241
|
-
isEnhanced: false,
|
|
242
|
-
isAdvanced: false,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Dynamic imports based on setup
|
|
247
|
-
let provenanceTools = null;
|
|
248
|
-
|
|
249
|
-
// Function to load provenance tools dynamically
|
|
250
|
-
function loadProvenanceTools() {
|
|
251
|
-
if (provenanceTools) return provenanceTools; // Already loaded
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
const setup = detectCAWSSetup();
|
|
255
|
-
if (setup?.hasTemplateDir && setup?.templateDir) {
|
|
256
|
-
const { generateProvenance, saveProvenance } = require(
|
|
257
|
-
path.join(setup.templateDir, 'apps/tools/caws/provenance.js')
|
|
258
|
-
);
|
|
259
|
-
provenanceTools = { generateProvenance, saveProvenance };
|
|
260
|
-
console.log('✅ Loaded provenance tools from:', setup.templateDir);
|
|
261
|
-
}
|
|
262
|
-
} catch (error) {
|
|
263
|
-
// Fallback for environments without template
|
|
264
|
-
provenanceTools = null;
|
|
265
|
-
console.warn('⚠️ Provenance tools not available:', error.message);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return provenanceTools;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const CLI_VERSION = require('../package.json').version;
|
|
272
|
-
|
|
273
|
-
// Initialize JSON Schema validator - using simplified validation for CLI stability
|
|
274
|
-
const validateWorkingSpec = (spec) => {
|
|
275
|
-
try {
|
|
276
|
-
// Basic structural validation for essential fields
|
|
277
|
-
const requiredFields = [
|
|
278
|
-
'id',
|
|
279
|
-
'title',
|
|
280
|
-
'risk_tier',
|
|
281
|
-
'mode',
|
|
282
|
-
'change_budget',
|
|
283
|
-
'blast_radius',
|
|
284
|
-
'operational_rollback_slo',
|
|
285
|
-
'scope',
|
|
286
|
-
'invariants',
|
|
287
|
-
'acceptance',
|
|
288
|
-
'non_functional',
|
|
289
|
-
'contracts',
|
|
290
|
-
];
|
|
291
|
-
|
|
292
|
-
for (const field of requiredFields) {
|
|
293
|
-
if (!spec[field]) {
|
|
294
|
-
return {
|
|
295
|
-
valid: false,
|
|
296
|
-
errors: [
|
|
297
|
-
{
|
|
298
|
-
instancePath: `/${field}`,
|
|
299
|
-
message: `Missing required field: ${field}`,
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Validate specific field formats
|
|
307
|
-
if (!/^[A-Z]+-\d+$/.test(spec.id)) {
|
|
308
|
-
return {
|
|
309
|
-
valid: false,
|
|
310
|
-
errors: [
|
|
311
|
-
{
|
|
312
|
-
instancePath: '/id',
|
|
313
|
-
message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
|
|
314
|
-
},
|
|
315
|
-
],
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Validate experimental mode
|
|
320
|
-
if (spec.experimental_mode) {
|
|
321
|
-
if (typeof spec.experimental_mode !== 'object') {
|
|
322
|
-
return {
|
|
323
|
-
valid: false,
|
|
324
|
-
errors: [
|
|
325
|
-
{
|
|
326
|
-
instancePath: '/experimental_mode',
|
|
327
|
-
message:
|
|
328
|
-
'Experimental mode must be an object with enabled, rationale, and expires_at fields',
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
|
|
335
|
-
for (const field of requiredExpFields) {
|
|
336
|
-
if (!(field in spec.experimental_mode)) {
|
|
337
|
-
return {
|
|
338
|
-
valid: false,
|
|
339
|
-
errors: [
|
|
340
|
-
{
|
|
341
|
-
instancePath: `/experimental_mode/${field}`,
|
|
342
|
-
message: `Missing required experimental mode field: ${field}`,
|
|
343
|
-
},
|
|
344
|
-
],
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
|
|
350
|
-
return {
|
|
351
|
-
valid: false,
|
|
352
|
-
errors: [
|
|
353
|
-
{
|
|
354
|
-
instancePath: '/experimental_mode',
|
|
355
|
-
message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (spec.risk_tier < 1 || spec.risk_tier > 3) {
|
|
363
|
-
return {
|
|
364
|
-
valid: false,
|
|
365
|
-
errors: [
|
|
366
|
-
{
|
|
367
|
-
instancePath: '/risk_tier',
|
|
368
|
-
message: 'Risk tier must be 1, 2, or 3',
|
|
369
|
-
},
|
|
370
|
-
],
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
|
|
375
|
-
return {
|
|
376
|
-
valid: false,
|
|
377
|
-
errors: [
|
|
378
|
-
{
|
|
379
|
-
instancePath: '/scope/in',
|
|
380
|
-
message: 'Scope IN must not be empty',
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return { valid: true };
|
|
387
|
-
} catch (error) {
|
|
388
|
-
return {
|
|
389
|
-
valid: false,
|
|
390
|
-
errors: [
|
|
391
|
-
{
|
|
392
|
-
instancePath: '',
|
|
393
|
-
message: `Validation error: ${error.message}`,
|
|
394
|
-
},
|
|
395
|
-
],
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Enhanced validation with suggestions and auto-fix
|
|
402
|
-
*/
|
|
403
|
-
function validateWorkingSpecWithSuggestions(spec, options = {}) {
|
|
404
|
-
const { autoFix = false, suggestions = true } = options;
|
|
405
|
-
|
|
406
|
-
try {
|
|
407
|
-
// Basic structural validation for essential fields
|
|
408
|
-
const requiredFields = [
|
|
409
|
-
'id',
|
|
410
|
-
'title',
|
|
411
|
-
'risk_tier',
|
|
412
|
-
'mode',
|
|
413
|
-
'change_budget',
|
|
414
|
-
'blast_radius',
|
|
415
|
-
'operational_rollback_slo',
|
|
416
|
-
'scope',
|
|
417
|
-
'invariants',
|
|
418
|
-
'acceptance',
|
|
419
|
-
'non_functional',
|
|
420
|
-
'contracts',
|
|
421
|
-
];
|
|
422
|
-
|
|
423
|
-
let errors = [];
|
|
424
|
-
let warnings = [];
|
|
425
|
-
let fixes = [];
|
|
426
|
-
|
|
427
|
-
for (const field of requiredFields) {
|
|
428
|
-
if (!spec[field]) {
|
|
429
|
-
errors.push({
|
|
430
|
-
instancePath: `/${field}`,
|
|
431
|
-
message: `Missing required field: ${field}`,
|
|
432
|
-
suggestion: getFieldSuggestion(field, spec),
|
|
433
|
-
canAutoFix: canAutoFixField(field, spec),
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Validate specific field formats
|
|
439
|
-
if (spec.id && !/^[A-Z]+-\d+$/.test(spec.id)) {
|
|
440
|
-
errors.push({
|
|
441
|
-
instancePath: '/id',
|
|
442
|
-
message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
|
|
443
|
-
suggestion: 'Use format like: PROJ-001, FEAT-002, FIX-003',
|
|
444
|
-
canAutoFix: false,
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Validate risk tier
|
|
449
|
-
if (spec.risk_tier !== undefined && (spec.risk_tier < 1 || spec.risk_tier > 3)) {
|
|
450
|
-
errors.push({
|
|
451
|
-
instancePath: '/risk_tier',
|
|
452
|
-
message: 'Risk tier must be 1, 2, or 3',
|
|
453
|
-
suggestion:
|
|
454
|
-
'Tier 1: Critical (auth, billing), Tier 2: Standard (features), Tier 3: Low risk (UI)',
|
|
455
|
-
canAutoFix: true,
|
|
456
|
-
});
|
|
457
|
-
fixes.push({ field: 'risk_tier', value: Math.max(1, Math.min(3, spec.risk_tier || 2)) });
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Validate scope.in is not empty
|
|
461
|
-
if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
|
|
462
|
-
errors.push({
|
|
463
|
-
instancePath: '/scope/in',
|
|
464
|
-
message: 'Scope IN must not be empty',
|
|
465
|
-
suggestion: 'Specify directories/files that are included in changes',
|
|
466
|
-
canAutoFix: false,
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Check for common issues
|
|
471
|
-
if (!spec.invariants || spec.invariants.length === 0) {
|
|
472
|
-
warnings.push({
|
|
473
|
-
instancePath: '/invariants',
|
|
474
|
-
message: 'No system invariants defined',
|
|
475
|
-
suggestion: 'Add 1-3 statements about what must always remain true',
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (!spec.acceptance || spec.acceptance.length === 0) {
|
|
480
|
-
warnings.push({
|
|
481
|
-
instancePath: '/acceptance',
|
|
482
|
-
message: 'No acceptance criteria defined',
|
|
483
|
-
suggestion: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Validate experimental mode
|
|
488
|
-
if (spec.experimental_mode) {
|
|
489
|
-
if (typeof spec.experimental_mode !== 'object') {
|
|
490
|
-
errors.push({
|
|
491
|
-
instancePath: '/experimental_mode',
|
|
492
|
-
message:
|
|
493
|
-
'Experimental mode must be an object with enabled, rationale, and expires_at fields',
|
|
494
|
-
suggestion: 'Fix experimental_mode structure',
|
|
495
|
-
canAutoFix: false,
|
|
496
|
-
});
|
|
497
|
-
} else {
|
|
498
|
-
const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
|
|
499
|
-
for (const field of requiredExpFields) {
|
|
500
|
-
if (!(field in spec.experimental_mode)) {
|
|
501
|
-
errors.push({
|
|
502
|
-
instancePath: `/experimental_mode/${field}`,
|
|
503
|
-
message: `Missing required experimental mode field: ${field}`,
|
|
504
|
-
suggestion: `Add ${field} to experimental_mode`,
|
|
505
|
-
canAutoFix: field === 'enabled' ? true : false,
|
|
506
|
-
});
|
|
507
|
-
if (field === 'enabled') {
|
|
508
|
-
fixes.push({ field: `experimental_mode.${field}`, value: true });
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
|
|
514
|
-
warnings.push({
|
|
515
|
-
instancePath: '/experimental_mode',
|
|
516
|
-
message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
|
|
517
|
-
suggestion: 'Either set risk_tier to 3 or disable experimental mode',
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Apply auto-fixes if requested
|
|
524
|
-
if (autoFix && fixes.length > 0) {
|
|
525
|
-
console.log(chalk.cyan('🔧 Applying auto-fixes...'));
|
|
526
|
-
for (const fix of fixes) {
|
|
527
|
-
if (fix.field.includes('.')) {
|
|
528
|
-
const [parent, child] = fix.field.split('.');
|
|
529
|
-
if (!spec[parent]) spec[parent] = {};
|
|
530
|
-
spec[parent][child] = fix.value;
|
|
531
|
-
} else {
|
|
532
|
-
spec[fix.field] = fix.value;
|
|
533
|
-
}
|
|
534
|
-
console.log(` Fixed: ${fix.field} = ${JSON.stringify(fix.value)}`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Display results
|
|
539
|
-
if (errors.length > 0) {
|
|
540
|
-
console.error(chalk.red('❌ Validation failed with errors:'));
|
|
541
|
-
errors.forEach((error, index) => {
|
|
542
|
-
console.error(`${index + 1}. ${error.instancePath || 'root'}: ${error.message}`);
|
|
543
|
-
if (suggestions && error.suggestion) {
|
|
544
|
-
console.error(` 💡 ${error.suggestion}`);
|
|
545
|
-
}
|
|
546
|
-
if (error.canAutoFix) {
|
|
547
|
-
console.error(` 🔧 Can auto-fix: ${autoFix ? 'applied' : 'run with --auto-fix'}`);
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
return { valid: false, errors, warnings };
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (warnings.length > 0 && suggestions) {
|
|
554
|
-
console.warn(chalk.yellow('⚠️ Validation passed with warnings:'));
|
|
555
|
-
warnings.forEach((warning, index) => {
|
|
556
|
-
console.warn(`${index + 1}. ${warning.instancePath || 'root'}: ${warning.message}`);
|
|
557
|
-
if (warning.suggestion) {
|
|
558
|
-
console.warn(` 💡 ${warning.suggestion}`);
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
console.log(chalk.green('✅ Working specification is valid'));
|
|
564
|
-
return { valid: true, errors: [], warnings };
|
|
565
|
-
} catch (error) {
|
|
566
|
-
console.error(chalk.red('❌ Error during validation:'), error.message);
|
|
567
|
-
return {
|
|
568
|
-
valid: false,
|
|
569
|
-
errors: [{ instancePath: '', message: `Validation error: ${error.message}` }],
|
|
570
|
-
warnings: [],
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function getFieldSuggestion(field, _spec) {
|
|
576
|
-
const suggestions = {
|
|
577
|
-
id: 'Use format like: PROJ-001, FEAT-002, FIX-003',
|
|
578
|
-
title: 'Add a descriptive project title',
|
|
579
|
-
risk_tier: 'Choose: 1 (critical), 2 (standard), or 3 (low risk)',
|
|
580
|
-
mode: 'Choose: feature, refactor, fix, doc, or chore',
|
|
581
|
-
change_budget: 'Set max_files and max_loc based on risk tier',
|
|
582
|
-
blast_radius: 'List affected modules and data migration needs',
|
|
583
|
-
operational_rollback_slo: 'Choose: 1m, 5m, 15m, or 1h',
|
|
584
|
-
scope: "Define what's included (in) and excluded (out) from changes",
|
|
585
|
-
invariants: 'Add 1-3 statements about what must always remain true',
|
|
586
|
-
acceptance: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
|
|
587
|
-
non_functional: 'Define accessibility, performance, and security requirements',
|
|
588
|
-
contracts: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
|
|
589
|
-
};
|
|
590
|
-
return suggestions[field] || `Add the ${field} field`;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function canAutoFixField(field, _spec) {
|
|
594
|
-
const autoFixable = ['risk_tier'];
|
|
595
|
-
return autoFixable.includes(field);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Generate a getting started guide based on project analysis
|
|
600
|
-
*/
|
|
601
|
-
function generateGettingStartedGuide(analysis) {
|
|
602
|
-
const { projectType, packageJson, hasTests, hasLinting } = analysis;
|
|
603
|
-
|
|
604
|
-
const projectName = packageJson.name || 'your-project';
|
|
605
|
-
const capitalizedType = projectType.charAt(0).toUpperCase() + projectType.slice(1);
|
|
606
|
-
|
|
607
|
-
let guide = `# Getting Started with CAWS - ${capitalizedType} Project
|
|
608
|
-
|
|
609
|
-
**Project**: ${projectName}
|
|
610
|
-
**Type**: ${capitalizedType}
|
|
611
|
-
**Generated**: ${new Date().toLocaleDateString()}
|
|
612
|
-
|
|
613
|
-
---
|
|
614
|
-
|
|
615
|
-
## Phase 1: Setup Verification (15 mins)
|
|
616
|
-
|
|
617
|
-
Complete these steps to ensure your CAWS setup is working:
|
|
618
|
-
|
|
619
|
-
### ✅ Already Done
|
|
620
|
-
- [x] Initialize CAWS project
|
|
621
|
-
- [x] Generate working spec
|
|
622
|
-
- [x] Set up basic structure
|
|
623
|
-
|
|
624
|
-
### Next Steps
|
|
625
|
-
- [ ] Review \`.caws/working-spec.yaml\` - customize for your needs
|
|
626
|
-
- [ ] Run validation: \`caws validate --suggestions\`
|
|
627
|
-
- [ ] Review tier policy in \`.caws/policy/\` (if applicable)
|
|
628
|
-
- [ ] Update \`.caws/templates/\` with project-specific examples
|
|
629
|
-
|
|
630
|
-
---
|
|
631
|
-
|
|
632
|
-
## Phase 2: First Feature (30 mins)
|
|
633
|
-
|
|
634
|
-
Time to create your first CAWS-managed feature:
|
|
635
|
-
|
|
636
|
-
### Steps
|
|
637
|
-
1. **Copy a template**:
|
|
638
|
-
\`\`\`bash
|
|
639
|
-
cp .caws/templates/feature.plan.md docs/plans/FEATURE-001.md
|
|
640
|
-
\`\`\`
|
|
641
|
-
|
|
642
|
-
2. **Customize the plan**:
|
|
643
|
-
- Update title and description
|
|
644
|
-
- Fill in acceptance criteria (GIVEN/WHEN/THEN format)
|
|
645
|
-
- Set appropriate risk tier
|
|
646
|
-
- Define scope and invariants
|
|
647
|
-
|
|
648
|
-
3. **Write tests first** (TDD approach):
|
|
649
|
-
\`\`\`bash
|
|
650
|
-
# For ${projectType} projects, focus on:
|
|
651
|
-
${getTestingGuidance(projectType)}
|
|
652
|
-
\`\`\`
|
|
653
|
-
|
|
654
|
-
4. **Implement the feature**:
|
|
655
|
-
- Stay within change budget limits
|
|
656
|
-
- Follow acceptance criteria
|
|
657
|
-
- Maintain system invariants
|
|
658
|
-
|
|
659
|
-
5. **Run full verification**:
|
|
660
|
-
\`\`\`bash
|
|
661
|
-
caws validate --suggestions
|
|
662
|
-
${hasTests ? 'npm test' : '# Add tests when ready'}
|
|
663
|
-
${hasLinting ? 'npm run lint' : '# Add linting when ready'}
|
|
664
|
-
\`\`\`
|
|
665
|
-
|
|
666
|
-
---
|
|
667
|
-
|
|
668
|
-
## Phase 3: CI/CD Setup (20 mins)
|
|
669
|
-
|
|
670
|
-
Set up automated quality gates:
|
|
671
|
-
|
|
672
|
-
### GitHub Actions (Recommended)
|
|
673
|
-
1. **Create workflow**: \`.github/workflows/caws.yml\`
|
|
674
|
-
\`\`\`yaml
|
|
675
|
-
name: CAWS Quality Gates
|
|
676
|
-
on: [pull_request]
|
|
677
|
-
|
|
678
|
-
jobs:
|
|
679
|
-
validate:
|
|
680
|
-
runs-on: ubuntu-latest
|
|
681
|
-
steps:
|
|
682
|
-
- uses: actions/checkout@v4
|
|
683
|
-
- uses: actions/setup-node@v4
|
|
684
|
-
with:
|
|
685
|
-
node-version: '18'
|
|
686
|
-
- run: npm ci
|
|
687
|
-
- run: npx caws validate --quiet
|
|
688
|
-
- run: npm test # Add when ready
|
|
689
|
-
\`\`\`
|
|
690
|
-
|
|
691
|
-
2. **Configure branch protection**:
|
|
692
|
-
- Require PR validation
|
|
693
|
-
- Require tests to pass
|
|
694
|
-
- Require CAWS spec validation
|
|
695
|
-
|
|
696
|
-
### Other CI Systems
|
|
697
|
-
- **GitLab CI**: Use \`caws validate\` in \`.gitlab-ci.yml\`
|
|
698
|
-
- **Jenkins**: Add validation step to pipeline
|
|
699
|
-
- **CircleCI**: Include in \`.circleci/config.yml\`
|
|
700
|
-
|
|
701
|
-
---
|
|
702
|
-
|
|
703
|
-
## Phase 4: Team Onboarding (ongoing)
|
|
704
|
-
|
|
705
|
-
### For Team Members
|
|
706
|
-
1. **Read the basics**: Start with this guide
|
|
707
|
-
2. **Learn by example**: Review completed features
|
|
708
|
-
3. **Practice**: Create small features following the process
|
|
709
|
-
4. **Contribute**: Help improve templates and processes
|
|
710
|
-
|
|
711
|
-
### For Project Leads
|
|
712
|
-
1. **Customize templates**: Adapt to team preferences
|
|
713
|
-
2. **Set standards**: Define project-specific conventions
|
|
714
|
-
3. **Monitor quality**: Review metrics and adjust gates
|
|
715
|
-
4. **Scale practices**: Apply CAWS to more complex work
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## Key Concepts Quick Reference
|
|
720
|
-
|
|
721
|
-
### Risk Tiers
|
|
722
|
-
- **Tier 1**: Critical (auth, billing, migrations) - Max rigor
|
|
723
|
-
- **Tier 2**: Standard (features, APIs) - Standard rigor
|
|
724
|
-
- **Tier 3**: Low risk (UI, tooling) - Basic rigor
|
|
725
|
-
|
|
726
|
-
### Change Budget
|
|
727
|
-
- Limits help maintain quality and reviewability
|
|
728
|
-
- Adjust based on risk tier and team experience
|
|
729
|
-
- Track actual vs. budgeted changes
|
|
730
|
-
|
|
731
|
-
### System Invariants
|
|
732
|
-
- Core guarantees that must always hold true
|
|
733
|
-
- Examples: "Data integrity maintained", "API contracts honored"
|
|
734
|
-
- Define 2-4 key invariants for your system
|
|
735
|
-
|
|
736
|
-
### Acceptance Criteria
|
|
737
|
-
- Use GIVEN/WHEN/THEN format
|
|
738
|
-
- Focus on observable behavior
|
|
739
|
-
- Include edge cases and error conditions
|
|
740
|
-
|
|
741
|
-
---
|
|
742
|
-
|
|
743
|
-
## Common Pitfalls to Avoid
|
|
744
|
-
|
|
745
|
-
### For ${capitalizedType} Projects
|
|
746
|
-
${getProjectSpecificPitfalls(projectType)}
|
|
747
|
-
|
|
748
|
-
### General Issues
|
|
749
|
-
1. **Over-customization**: Start with defaults, customize gradually
|
|
750
|
-
2. **Missing invariants**: Define what must never break
|
|
751
|
-
3. **Vague acceptance**: Make criteria measurable and testable
|
|
752
|
-
4. **Large changes**: Break big features into smaller, reviewable pieces
|
|
753
|
-
|
|
754
|
-
---
|
|
755
|
-
|
|
756
|
-
## Resources
|
|
757
|
-
|
|
758
|
-
### Documentation
|
|
759
|
-
- **Quick Reference**: This guide
|
|
760
|
-
- **Templates**: \`.caws/templates/\`
|
|
761
|
-
- **Examples**: \`.caws/examples/\` (when available)
|
|
762
|
-
|
|
763
|
-
### Commands
|
|
764
|
-
- \`caws validate --suggestions\` - Get help with issues
|
|
765
|
-
- \`caws validate --auto-fix\` - Fix safe problems automatically
|
|
766
|
-
- \`caws init --interactive\` - Customize existing setup
|
|
767
|
-
|
|
768
|
-
### Community
|
|
769
|
-
- **GitHub Issues**: Report problems and request features
|
|
770
|
-
- **Discussions**: Share experiences and best practices
|
|
771
|
-
- **Wiki**: Growing collection of examples and guides
|
|
772
|
-
|
|
773
|
-
---
|
|
774
|
-
|
|
775
|
-
## Next Steps
|
|
776
|
-
|
|
777
|
-
1. **Right now**: Review your working spec and customize it
|
|
778
|
-
2. **Today**: Create your first feature plan
|
|
779
|
-
3. **This week**: Set up CI/CD and branch protection
|
|
780
|
-
4. **Ongoing**: Refine processes based on team feedback
|
|
781
|
-
|
|
782
|
-
Remember: CAWS is a framework, not a straightjacket. Adapt it to your team's needs while maintaining the core principles of determinism and quality.
|
|
783
|
-
|
|
784
|
-
**Happy coding! 🎯**
|
|
785
|
-
`;
|
|
786
|
-
|
|
787
|
-
return guide;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
function getTestingGuidance(projectType) {
|
|
791
|
-
const guidance = {
|
|
792
|
-
extension: `- Webview rendering tests\n- Command registration tests\n- Extension activation tests`,
|
|
793
|
-
library: `- Component rendering tests\n- API function tests\n- Type export tests`,
|
|
794
|
-
api: `- Endpoint response tests\n- Error handling tests\n- Authentication tests`,
|
|
795
|
-
cli: `- Command parsing tests\n- Output formatting tests\n- Error code tests`,
|
|
796
|
-
monorepo: `- Cross-package integration tests\n- Shared module tests\n- Build pipeline tests`,
|
|
797
|
-
application: `- User interaction tests\n- State management tests\n- Integration tests`,
|
|
798
|
-
};
|
|
799
|
-
return (
|
|
800
|
-
guidance[projectType] || `- Unit tests for core functions\n- Integration tests for workflows`
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function getProjectSpecificPitfalls(projectType) {
|
|
805
|
-
const pitfalls = {
|
|
806
|
-
extension: `1. **Webview security**: Never use \`vscode.executeCommand\` from untrusted content
|
|
807
|
-
2. **Activation timing**: Test cold start performance
|
|
808
|
-
3. **API compatibility**: Check VS Code API version compatibility`,
|
|
809
|
-
library: `1. **Bundle size**: Monitor and limit package size
|
|
810
|
-
2. **Type exports**: Ensure all public APIs are typed
|
|
811
|
-
3. **Peer dependencies**: Handle React/Angular versions carefully`,
|
|
812
|
-
api: `1. **Backward compatibility**: Version APIs carefully
|
|
813
|
-
2. **Rate limiting**: Test and document limits
|
|
814
|
-
3. **Data validation**: Validate all inputs thoroughly`,
|
|
815
|
-
cli: `1. **Exit codes**: Use standard codes (0=success, 1=error)
|
|
816
|
-
2. **Help text**: Keep it concise and helpful
|
|
817
|
-
3. **Error messages**: Make them actionable`,
|
|
818
|
-
monorepo: `1. **Dependency cycles**: Avoid circular imports
|
|
819
|
-
2. **Version consistency**: Keep package versions aligned
|
|
820
|
-
3. **Build order**: Ensure correct build dependencies`,
|
|
821
|
-
application: `1. **State consistency**: Prevent invalid state transitions
|
|
822
|
-
2. **Performance**: Monitor and optimize critical paths
|
|
823
|
-
3. **Accessibility**: Test with screen readers and keyboard navigation`,
|
|
824
|
-
};
|
|
825
|
-
return (
|
|
826
|
-
pitfalls[projectType] ||
|
|
827
|
-
`1. **Test coverage**: Maintain adequate test coverage
|
|
828
|
-
2. **Documentation**: Keep code and APIs documented
|
|
829
|
-
3. **Dependencies**: Review and update regularly`
|
|
830
|
-
);
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* Generate smart .gitignore patterns for CAWS projects
|
|
835
|
-
*/
|
|
836
|
-
function generateGitignorePatterns(existingGitignore = '') {
|
|
837
|
-
const cawsPatterns = `
|
|
838
|
-
# CAWS Configuration (tracked - these should be versioned)
|
|
839
|
-
# Note: .caws/ and .agent/ are tracked for provenance
|
|
840
|
-
# But we exclude temporary/generated files:
|
|
841
|
-
|
|
842
|
-
# CAWS temporary files (ignored)
|
|
843
|
-
.agent/temp/
|
|
844
|
-
.agent/cache/
|
|
845
|
-
.caws/.cache/
|
|
846
|
-
.caws/tmp/
|
|
847
|
-
|
|
848
|
-
# Build outputs (common patterns)
|
|
849
|
-
dist/
|
|
850
|
-
build/
|
|
851
|
-
*.tsbuildinfo
|
|
852
|
-
.next/
|
|
853
|
-
.nuxt/
|
|
854
|
-
.vite/
|
|
855
|
-
|
|
856
|
-
# Dependencies
|
|
857
|
-
node_modules/
|
|
858
|
-
.pnpm-store/
|
|
859
|
-
|
|
860
|
-
# Environment files
|
|
861
|
-
.env
|
|
862
|
-
.env.local
|
|
863
|
-
.env.development.local
|
|
864
|
-
.env.test.local
|
|
865
|
-
.env.production.local
|
|
866
|
-
|
|
867
|
-
# IDE files
|
|
868
|
-
.vscode/settings.json
|
|
869
|
-
.idea/
|
|
870
|
-
*.swp
|
|
871
|
-
*.swo
|
|
872
|
-
|
|
873
|
-
# OS files
|
|
874
|
-
.DS_Store
|
|
875
|
-
Thumbs.db
|
|
876
|
-
|
|
877
|
-
# Logs
|
|
878
|
-
logs/
|
|
879
|
-
*.log
|
|
880
|
-
npm-debug.log*
|
|
881
|
-
yarn-debug.log*
|
|
882
|
-
yarn-error.log*
|
|
883
|
-
|
|
884
|
-
# Coverage reports
|
|
885
|
-
coverage/
|
|
886
|
-
.nyc_output/
|
|
887
|
-
|
|
888
|
-
# Test results
|
|
889
|
-
test-results/
|
|
890
|
-
playwright-report/
|
|
891
|
-
`;
|
|
892
|
-
|
|
893
|
-
// If there's an existing .gitignore, merge intelligently
|
|
894
|
-
if (existingGitignore.trim()) {
|
|
895
|
-
// Check if CAWS patterns are already present
|
|
896
|
-
if (existingGitignore.includes('# CAWS Configuration')) {
|
|
897
|
-
console.log(chalk.blue('ℹ️ .gitignore already contains CAWS patterns - skipping'));
|
|
898
|
-
return existingGitignore;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Append CAWS patterns to existing .gitignore
|
|
902
|
-
return existingGitignore.trim() + '\n\n' + cawsPatterns.trim() + '\n';
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
return cawsPatterns.trim();
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Only log schema validation if not running quiet commands
|
|
909
|
-
if (!process.argv.includes('--version') && !process.argv.includes('-V')) {
|
|
910
|
-
console.log(chalk.green('✅ Schema validation initialized successfully'));
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
/**
|
|
914
|
-
* Generate working spec YAML with user input
|
|
915
|
-
* @param {Object} answers - User responses
|
|
916
|
-
* @returns {string} - Generated YAML content
|
|
917
|
-
*/
|
|
918
|
-
function generateWorkingSpec(answers) {
|
|
919
|
-
const template = {
|
|
920
|
-
id: answers.projectId,
|
|
921
|
-
title: answers.projectTitle,
|
|
922
|
-
risk_tier: answers.riskTier,
|
|
923
|
-
mode: answers.projectMode,
|
|
924
|
-
change_budget: {
|
|
925
|
-
max_files: answers.maxFiles,
|
|
926
|
-
max_loc: answers.maxLoc,
|
|
927
|
-
},
|
|
928
|
-
blast_radius: {
|
|
929
|
-
modules: answers.blastModules
|
|
930
|
-
.split(',')
|
|
931
|
-
.map((m) => m.trim())
|
|
932
|
-
.filter((m) => m),
|
|
933
|
-
data_migration: answers.dataMigration,
|
|
934
|
-
},
|
|
935
|
-
operational_rollback_slo: answers.rollbackSlo,
|
|
936
|
-
threats: (answers.projectThreats || '')
|
|
937
|
-
.split('\n')
|
|
938
|
-
.map((t) => t.trim())
|
|
939
|
-
.filter((t) => t && !t.startsWith('-') === false), // Allow lines starting with -
|
|
940
|
-
scope: {
|
|
941
|
-
in: (answers.scopeIn || '')
|
|
942
|
-
.split(',')
|
|
943
|
-
.map((s) => s.trim())
|
|
944
|
-
.filter((s) => s),
|
|
945
|
-
out: (answers.scopeOut || '')
|
|
946
|
-
.split(',')
|
|
947
|
-
.map((s) => s.trim())
|
|
948
|
-
.filter((s) => s),
|
|
949
|
-
},
|
|
950
|
-
invariants: (answers.projectInvariants || '')
|
|
951
|
-
.split('\n')
|
|
952
|
-
.map((i) => i.trim())
|
|
953
|
-
.filter((i) => i),
|
|
954
|
-
acceptance: answers.acceptanceCriteria
|
|
955
|
-
.split('\n')
|
|
956
|
-
.filter((a) => a.trim())
|
|
957
|
-
.map((criteria, index) => {
|
|
958
|
-
const id = `A${index + 1}`;
|
|
959
|
-
const upperCriteria = criteria.toUpperCase();
|
|
960
|
-
|
|
961
|
-
// Try different variations of the format
|
|
962
|
-
let given = '';
|
|
963
|
-
let when = '';
|
|
964
|
-
let then = '';
|
|
965
|
-
|
|
966
|
-
if (
|
|
967
|
-
upperCriteria.includes('GIVEN') &&
|
|
968
|
-
upperCriteria.includes('WHEN') &&
|
|
969
|
-
upperCriteria.includes('THEN')
|
|
970
|
-
) {
|
|
971
|
-
given = criteria.split(/WHEN/i)[0]?.replace(/GIVEN/i, '').trim() || '';
|
|
972
|
-
const whenThen = criteria.split(/WHEN/i)[1];
|
|
973
|
-
when = whenThen?.split(/THEN/i)[0]?.trim() || '';
|
|
974
|
-
then = whenThen?.split(/THEN/i)[1]?.trim() || '';
|
|
975
|
-
} else {
|
|
976
|
-
// Fallback: just split by lines and create simple criteria
|
|
977
|
-
given = 'Current system state';
|
|
978
|
-
when = criteria.replace(/^(GIVEN|WHEN|THEN)/i, '').trim();
|
|
979
|
-
then = 'Expected behavior occurs';
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
return {
|
|
983
|
-
id,
|
|
984
|
-
given: given || 'Current system state',
|
|
985
|
-
when: when || criteria,
|
|
986
|
-
then: then || 'Expected behavior occurs',
|
|
987
|
-
};
|
|
988
|
-
}),
|
|
989
|
-
non_functional: {
|
|
990
|
-
a11y: answers.a11yRequirements
|
|
991
|
-
.split(',')
|
|
992
|
-
.map((a) => a.trim())
|
|
993
|
-
.filter((a) => a),
|
|
994
|
-
perf: { api_p95_ms: answers.perfBudget },
|
|
995
|
-
security: answers.securityRequirements
|
|
996
|
-
.split(',')
|
|
997
|
-
.map((s) => s.trim())
|
|
998
|
-
.filter((s) => s),
|
|
999
|
-
},
|
|
1000
|
-
contracts: [
|
|
1001
|
-
{
|
|
1002
|
-
type: answers.contractType,
|
|
1003
|
-
path: answers.contractPath,
|
|
1004
|
-
},
|
|
1005
|
-
],
|
|
1006
|
-
observability: {
|
|
1007
|
-
logs: answers.observabilityLogs
|
|
1008
|
-
.split(',')
|
|
1009
|
-
.map((l) => l.trim())
|
|
1010
|
-
.filter((l) => l),
|
|
1011
|
-
metrics: answers.observabilityMetrics
|
|
1012
|
-
.split(',')
|
|
1013
|
-
.map((m) => m.trim())
|
|
1014
|
-
.filter((m) => m),
|
|
1015
|
-
traces: answers.observabilityTraces
|
|
1016
|
-
.split(',')
|
|
1017
|
-
.map((t) => t.trim())
|
|
1018
|
-
.filter((t) => t),
|
|
1019
|
-
},
|
|
1020
|
-
migrations: (answers.migrationPlan || '')
|
|
1021
|
-
.split('\n')
|
|
1022
|
-
.map((m) => m.trim())
|
|
1023
|
-
.filter((m) => m),
|
|
1024
|
-
rollback: (answers.rollbackPlan || '')
|
|
1025
|
-
.split('\n')
|
|
1026
|
-
.map((r) => r.trim())
|
|
1027
|
-
.filter((r) => r),
|
|
1028
|
-
human_override: answers.needsOverride
|
|
1029
|
-
? {
|
|
1030
|
-
enabled: true,
|
|
1031
|
-
approver: answers.overrideApprover,
|
|
1032
|
-
rationale: answers.overrideRationale,
|
|
1033
|
-
waived_gates: answers.waivedGates,
|
|
1034
|
-
approved_at: new Date().toISOString(),
|
|
1035
|
-
expires_at: new Date(
|
|
1036
|
-
Date.now() + answers.overrideExpiresDays * 24 * 60 * 60 * 1000
|
|
1037
|
-
).toISOString(),
|
|
1038
|
-
}
|
|
1039
|
-
: undefined,
|
|
1040
|
-
experimental_mode: answers.isExperimental
|
|
1041
|
-
? {
|
|
1042
|
-
enabled: true,
|
|
1043
|
-
rationale: answers.experimentalRationale,
|
|
1044
|
-
expires_at: new Date(
|
|
1045
|
-
Date.now() + answers.experimentalExpiresDays * 24 * 60 * 60 * 1000
|
|
1046
|
-
).toISOString(),
|
|
1047
|
-
sandbox_location: answers.experimentalSandbox,
|
|
1048
|
-
}
|
|
1049
|
-
: undefined,
|
|
1050
|
-
ai_assessment: {
|
|
1051
|
-
confidence_level: answers.aiConfidence,
|
|
1052
|
-
uncertainty_areas: answers.uncertaintyAreas
|
|
1053
|
-
.split(',')
|
|
1054
|
-
.map((a) => a.trim())
|
|
1055
|
-
.filter((a) => a),
|
|
1056
|
-
complexity_factors: answers.complexityFactors
|
|
1057
|
-
.split(',')
|
|
1058
|
-
.map((f) => f.trim())
|
|
1059
|
-
.filter((f) => f),
|
|
1060
|
-
risk_factors: [], // Could be populated by AI analysis
|
|
1061
|
-
},
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
return yaml.dump(template, { indent: 2 });
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
/**
|
|
1068
|
-
* Validate generated working spec against JSON schema
|
|
1069
|
-
* @param {string} specContent - YAML spec content
|
|
1070
|
-
* @param {Object} answers - User responses for error context
|
|
1071
|
-
*/
|
|
1072
|
-
function validateGeneratedSpec(specContent, _answers) {
|
|
1073
|
-
try {
|
|
1074
|
-
const spec = yaml.load(specContent);
|
|
1075
|
-
|
|
1076
|
-
const isValid = validateWorkingSpec(spec);
|
|
1077
|
-
|
|
1078
|
-
if (!isValid) {
|
|
1079
|
-
console.error(chalk.red('❌ Generated working spec failed validation:'));
|
|
1080
|
-
validateWorkingSpec.errors.forEach((error) => {
|
|
1081
|
-
console.error(` - ${error.instancePath || 'root'}: ${error.message}`);
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
// Provide helpful guidance
|
|
1085
|
-
console.log(chalk.blue('\n💡 Validation Tips:'));
|
|
1086
|
-
console.log(' - Ensure risk_tier is 1, 2, or 3');
|
|
1087
|
-
console.log(' - Check that scope.in is not empty');
|
|
1088
|
-
console.log(' - Verify invariants and acceptance criteria are provided');
|
|
1089
|
-
console.log(' - For tier 1 and 2, ensure contracts are specified');
|
|
1090
|
-
|
|
1091
|
-
process.exit(1);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
console.log(chalk.green('✅ Generated working spec passed validation'));
|
|
1095
|
-
} catch (error) {
|
|
1096
|
-
console.error(chalk.red('❌ Error validating working spec:'), error.message);
|
|
1097
|
-
process.exit(1);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
/**
|
|
1102
|
-
* Detect project type from existing files and structure
|
|
1103
|
-
*/
|
|
1104
|
-
function detectProjectType(cwd = process.cwd()) {
|
|
1105
|
-
const files = fs.readdirSync(cwd);
|
|
1106
|
-
|
|
1107
|
-
// Check for various project indicators
|
|
1108
|
-
const hasPackageJson = files.includes('package.json');
|
|
1109
|
-
const hasPnpm = files.includes('pnpm-workspace.yaml');
|
|
1110
|
-
const hasYarn = files.includes('yarn.lock');
|
|
1111
|
-
|
|
1112
|
-
let packageJson = {};
|
|
1113
|
-
if (hasPackageJson) {
|
|
1114
|
-
try {
|
|
1115
|
-
packageJson = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
1116
|
-
} catch (e) {
|
|
1117
|
-
// Ignore parse errors
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// VS Code Extension detection
|
|
1122
|
-
const isVscodeExtension =
|
|
1123
|
-
packageJson.engines?.vscode ||
|
|
1124
|
-
packageJson.contributes ||
|
|
1125
|
-
packageJson.activationEvents ||
|
|
1126
|
-
packageJson.main?.includes('extension.js');
|
|
1127
|
-
|
|
1128
|
-
// Monorepo detection
|
|
1129
|
-
const isMonorepo = hasPnpm || hasYarn || files.includes('packages') || files.includes('apps');
|
|
1130
|
-
|
|
1131
|
-
// Library detection
|
|
1132
|
-
const isLibrary = packageJson.main || packageJson.module || packageJson.exports;
|
|
1133
|
-
|
|
1134
|
-
// CLI detection
|
|
1135
|
-
const isCli = packageJson.bin || packageJson.name?.startsWith('@') === false;
|
|
1136
|
-
|
|
1137
|
-
// API detection
|
|
1138
|
-
const isApi =
|
|
1139
|
-
packageJson.scripts?.start ||
|
|
1140
|
-
packageJson.dependencies?.express ||
|
|
1141
|
-
packageJson.dependencies?.fastify ||
|
|
1142
|
-
packageJson.dependencies?.['@types/express'];
|
|
1143
|
-
|
|
1144
|
-
// Determine primary type
|
|
1145
|
-
if (isVscodeExtension) return 'extension';
|
|
1146
|
-
if (isMonorepo) return 'monorepo';
|
|
1147
|
-
if (isApi) return 'api';
|
|
1148
|
-
if (isLibrary) return 'library';
|
|
1149
|
-
if (isCli) return 'cli';
|
|
1150
|
-
|
|
1151
|
-
// Default fallback
|
|
1152
|
-
return 'application';
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
/**
|
|
1156
|
-
* Generate working spec from project analysis
|
|
1157
|
-
*/
|
|
1158
|
-
function generateWorkingSpecFromAnalysis(analysis) {
|
|
1159
|
-
const { projectType, packageJson } = analysis;
|
|
1160
|
-
|
|
1161
|
-
const templates = {
|
|
1162
|
-
extension: {
|
|
1163
|
-
risk_tier: 2,
|
|
1164
|
-
mode: 'feature',
|
|
1165
|
-
change_budget: { max_files: 25, max_loc: 1000 },
|
|
1166
|
-
invariants: [
|
|
1167
|
-
'Webview only accesses workspace files via VS Code API',
|
|
1168
|
-
'Extension activates in <1s on typical machine',
|
|
1169
|
-
'All commands have keyboard shortcuts',
|
|
1170
|
-
],
|
|
1171
|
-
scope: {
|
|
1172
|
-
in: ['src/', 'package.json', 'tsconfig.json'],
|
|
1173
|
-
out: ['node_modules/', '*.vsix'],
|
|
1174
|
-
},
|
|
1175
|
-
acceptance: [
|
|
1176
|
-
{
|
|
1177
|
-
id: 'A1',
|
|
1178
|
-
given: 'User has workspace open',
|
|
1179
|
-
when: 'Extension activates',
|
|
1180
|
-
then: 'Webview loads within 1 second',
|
|
1181
|
-
},
|
|
1182
|
-
],
|
|
1183
|
-
non_functional: {
|
|
1184
|
-
a11y: ['keyboard navigation', 'screen reader support', 'high contrast theme'],
|
|
1185
|
-
perf: { api_p95_ms: 100 },
|
|
1186
|
-
security: ['CSP enforcement for webviews', 'No arbitrary filesystem access'],
|
|
1187
|
-
},
|
|
1188
|
-
},
|
|
1189
|
-
library: {
|
|
1190
|
-
risk_tier: 2,
|
|
1191
|
-
mode: 'feature',
|
|
1192
|
-
change_budget: { max_files: 20, max_loc: 800 },
|
|
1193
|
-
invariants: [
|
|
1194
|
-
'No runtime dependencies except React',
|
|
1195
|
-
'Tree-shakeable exports',
|
|
1196
|
-
'TypeScript types exported',
|
|
1197
|
-
],
|
|
1198
|
-
scope: {
|
|
1199
|
-
in: ['src/', 'lib/', 'package.json'],
|
|
1200
|
-
out: ['examples/', 'docs/', 'node_modules/'],
|
|
1201
|
-
},
|
|
1202
|
-
acceptance: [
|
|
1203
|
-
{
|
|
1204
|
-
id: 'A1',
|
|
1205
|
-
given: 'Library is imported',
|
|
1206
|
-
when: 'Component is rendered',
|
|
1207
|
-
then: 'No runtime errors occur',
|
|
1208
|
-
},
|
|
1209
|
-
],
|
|
1210
|
-
non_functional: {
|
|
1211
|
-
a11y: ['WCAG 2.1 AA compliance', 'Semantic HTML'],
|
|
1212
|
-
perf: { bundle_size_kb: 50 },
|
|
1213
|
-
security: ['Input validation', 'XSS prevention'],
|
|
1214
|
-
},
|
|
1215
|
-
},
|
|
1216
|
-
api: {
|
|
1217
|
-
risk_tier: 1,
|
|
1218
|
-
mode: 'feature',
|
|
1219
|
-
change_budget: { max_files: 40, max_loc: 1500 },
|
|
1220
|
-
invariants: [
|
|
1221
|
-
'API maintains backward compatibility',
|
|
1222
|
-
'All endpoints respond within 100ms',
|
|
1223
|
-
'Data consistency maintained across requests',
|
|
1224
|
-
],
|
|
1225
|
-
scope: {
|
|
1226
|
-
in: ['src/', 'routes/', 'models/', 'tests/'],
|
|
1227
|
-
out: ['node_modules/', 'logs/', 'temp/'],
|
|
1228
|
-
},
|
|
1229
|
-
acceptance: [
|
|
1230
|
-
{
|
|
1231
|
-
id: 'A1',
|
|
1232
|
-
given: 'Valid request is made',
|
|
1233
|
-
when: 'Endpoint is called',
|
|
1234
|
-
then: 'Correct response returned within SLO',
|
|
1235
|
-
},
|
|
1236
|
-
],
|
|
1237
|
-
non_functional: {
|
|
1238
|
-
a11y: ['API documentation accessible'],
|
|
1239
|
-
perf: { api_p95_ms: 100 },
|
|
1240
|
-
security: ['Input validation', 'Rate limiting', 'Authentication'],
|
|
1241
|
-
},
|
|
1242
|
-
},
|
|
1243
|
-
cli: {
|
|
1244
|
-
risk_tier: 3,
|
|
1245
|
-
mode: 'feature',
|
|
1246
|
-
change_budget: { max_files: 15, max_loc: 600 },
|
|
1247
|
-
invariants: [
|
|
1248
|
-
'CLI exits with appropriate codes',
|
|
1249
|
-
'Help text is informative',
|
|
1250
|
-
'Error messages are clear',
|
|
1251
|
-
],
|
|
1252
|
-
scope: {
|
|
1253
|
-
in: ['src/', 'bin/', 'lib/', 'tests/'],
|
|
1254
|
-
out: ['node_modules/', 'dist/'],
|
|
1255
|
-
},
|
|
1256
|
-
acceptance: [
|
|
1257
|
-
{
|
|
1258
|
-
id: 'A1',
|
|
1259
|
-
given: 'User runs command with --help',
|
|
1260
|
-
when: 'Help flag is provided',
|
|
1261
|
-
then: 'Help text displays clearly',
|
|
1262
|
-
},
|
|
1263
|
-
],
|
|
1264
|
-
non_functional: {
|
|
1265
|
-
a11y: ['Color contrast in terminal output'],
|
|
1266
|
-
perf: { api_p95_ms: 50 },
|
|
1267
|
-
security: ['Input validation', 'No arbitrary execution'],
|
|
1268
|
-
},
|
|
1269
|
-
},
|
|
1270
|
-
monorepo: {
|
|
1271
|
-
risk_tier: 1,
|
|
1272
|
-
mode: 'feature',
|
|
1273
|
-
change_budget: { max_files: 50, max_loc: 2000 },
|
|
1274
|
-
invariants: [
|
|
1275
|
-
'All packages remain compatible',
|
|
1276
|
-
'Cross-package dependencies work',
|
|
1277
|
-
'Build system remains stable',
|
|
1278
|
-
],
|
|
1279
|
-
scope: {
|
|
1280
|
-
in: ['packages/', 'apps/', 'tools/', 'scripts/'],
|
|
1281
|
-
out: ['node_modules/', 'dist/', 'build/'],
|
|
1282
|
-
},
|
|
1283
|
-
acceptance: [
|
|
1284
|
-
{
|
|
1285
|
-
id: 'A1',
|
|
1286
|
-
given: 'Change is made to shared package',
|
|
1287
|
-
when: 'All dependent packages build',
|
|
1288
|
-
then: 'No breaking changes introduced',
|
|
1289
|
-
},
|
|
1290
|
-
],
|
|
1291
|
-
non_functional: {
|
|
1292
|
-
a11y: ['Documentation accessible across packages'],
|
|
1293
|
-
perf: { api_p95_ms: 200 },
|
|
1294
|
-
security: ['Dependency audit passes', 'No vulnerable packages'],
|
|
1295
|
-
},
|
|
1296
|
-
},
|
|
1297
|
-
application: {
|
|
1298
|
-
risk_tier: 2,
|
|
1299
|
-
mode: 'feature',
|
|
1300
|
-
change_budget: { max_files: 30, max_loc: 1200 },
|
|
1301
|
-
invariants: [
|
|
1302
|
-
'Application remains functional',
|
|
1303
|
-
'User data is preserved',
|
|
1304
|
-
'Performance does not degrade',
|
|
1305
|
-
],
|
|
1306
|
-
scope: {
|
|
1307
|
-
in: ['src/', 'components/', 'pages/', 'lib/'],
|
|
1308
|
-
out: ['node_modules/', 'build/', 'dist/'],
|
|
1309
|
-
},
|
|
1310
|
-
acceptance: [
|
|
1311
|
-
{
|
|
1312
|
-
id: 'A1',
|
|
1313
|
-
given: 'User interacts with application',
|
|
1314
|
-
when: 'Feature is used',
|
|
1315
|
-
then: 'Expected behavior occurs',
|
|
1316
|
-
},
|
|
1317
|
-
],
|
|
1318
|
-
non_functional: {
|
|
1319
|
-
a11y: ['WCAG 2.1 AA compliance', 'Keyboard navigation'],
|
|
1320
|
-
perf: { api_p95_ms: 250 },
|
|
1321
|
-
security: ['Input validation', 'Authentication', 'Authorization'],
|
|
1322
|
-
},
|
|
1323
|
-
},
|
|
1324
|
-
};
|
|
1325
|
-
|
|
1326
|
-
const baseSpec = templates[projectType] || templates.application;
|
|
1327
|
-
|
|
1328
|
-
return {
|
|
1329
|
-
id: `${packageJson.name?.toUpperCase().replace(/[^A-Z0-9]/g, '-') || 'PROJECT'}-001`,
|
|
1330
|
-
title: packageJson.name || 'Project',
|
|
1331
|
-
risk_tier: baseSpec.risk_tier,
|
|
1332
|
-
mode: baseSpec.mode,
|
|
1333
|
-
change_budget: baseSpec.change_budget,
|
|
1334
|
-
blast_radius: {
|
|
1335
|
-
modules: ['core', 'api', 'ui'],
|
|
1336
|
-
data_migration: false,
|
|
1337
|
-
},
|
|
1338
|
-
operational_rollback_slo: '5m',
|
|
1339
|
-
scope: baseSpec.scope,
|
|
1340
|
-
invariants: baseSpec.invariants,
|
|
1341
|
-
acceptance: baseSpec.acceptance,
|
|
1342
|
-
non_functional: baseSpec.non_functional,
|
|
1343
|
-
contracts: [
|
|
1344
|
-
{
|
|
1345
|
-
type: projectType === 'api' ? 'openapi' : 'none',
|
|
1346
|
-
path: projectType === 'api' ? 'docs/api.yaml' : '',
|
|
1347
|
-
},
|
|
1348
|
-
],
|
|
1349
|
-
observability: {
|
|
1350
|
-
logs: ['error', 'warn', 'info'],
|
|
1351
|
-
metrics: ['requests_total', 'errors_total'],
|
|
1352
|
-
traces: ['request_flow'],
|
|
1353
|
-
},
|
|
1354
|
-
ai_assessment: {
|
|
1355
|
-
confidence_level: 8,
|
|
1356
|
-
uncertainty_areas: [],
|
|
1357
|
-
complexity_factors: [],
|
|
1358
|
-
risk_factors: [],
|
|
1359
|
-
},
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
/**
|
|
1364
|
-
* Detect if current directory appears to be a project that should be initialized directly
|
|
1365
|
-
*/
|
|
1366
|
-
function shouldInitInCurrentDirectory(projectName, currentDir) {
|
|
1367
|
-
// If explicitly '.', always init in current directory
|
|
1368
|
-
if (projectName === '.') return true;
|
|
1369
|
-
|
|
1370
|
-
// Check for common project indicators
|
|
1371
|
-
const projectIndicators = [
|
|
1372
|
-
'package.json',
|
|
1373
|
-
'tsconfig.json',
|
|
1374
|
-
'jest.config.js',
|
|
1375
|
-
'eslint.config.js',
|
|
1376
|
-
'README.md',
|
|
1377
|
-
'src/',
|
|
1378
|
-
'lib/',
|
|
1379
|
-
'app/',
|
|
1380
|
-
'packages/',
|
|
1381
|
-
'.git/',
|
|
1382
|
-
'node_modules/', // Even if empty, suggests intent to be a project
|
|
1383
|
-
];
|
|
1384
|
-
|
|
1385
|
-
const files = fs.readdirSync(currentDir);
|
|
1386
|
-
const hasProjectIndicators = projectIndicators.some((indicator) => {
|
|
1387
|
-
if (indicator.endsWith('/')) {
|
|
1388
|
-
return files.includes(indicator.slice(0, -1));
|
|
1389
|
-
}
|
|
1390
|
-
return files.includes(indicator);
|
|
1391
|
-
});
|
|
1392
|
-
|
|
1393
|
-
return hasProjectIndicators;
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
/**
|
|
1397
|
-
* Initialize a new project with CAWS
|
|
1398
|
-
*/
|
|
1399
|
-
async function initProject(projectName, options) {
|
|
1400
|
-
const currentDir = process.cwd();
|
|
1401
|
-
const isCurrentDirInit = shouldInitInCurrentDirectory(projectName, currentDir);
|
|
1402
|
-
|
|
1403
|
-
if (!isCurrentDirInit && projectName !== '.') {
|
|
1404
|
-
console.log(chalk.cyan(`🚀 Initializing new CAWS project: ${projectName}`));
|
|
1405
|
-
console.log(chalk.gray(` (Creating subdirectory: ${projectName}/)`));
|
|
1406
|
-
} else {
|
|
1407
|
-
console.log(
|
|
1408
|
-
chalk.cyan(`🚀 Initializing CAWS in current project: ${path.basename(currentDir)}`)
|
|
1409
|
-
);
|
|
1410
|
-
console.log(chalk.gray(` (Adding CAWS files to existing project)`));
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
let answers; // Will be set either interactively or with defaults
|
|
1414
|
-
|
|
1415
|
-
try {
|
|
1416
|
-
// Validate project name
|
|
1417
|
-
if (!projectName || projectName.trim() === '') {
|
|
1418
|
-
console.error(chalk.red('❌ Project name is required'));
|
|
1419
|
-
console.error(chalk.blue('💡 Usage: caws init <project-name>'));
|
|
1420
|
-
process.exit(1);
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
// Special case: '.' means current directory, don't sanitize
|
|
1424
|
-
if (projectName !== '.') {
|
|
1425
|
-
// Sanitize project name
|
|
1426
|
-
const sanitizedName = projectName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
|
|
1427
|
-
if (sanitizedName !== projectName) {
|
|
1428
|
-
console.warn(chalk.yellow(`⚠️ Project name sanitized to: ${sanitizedName}`));
|
|
1429
|
-
projectName = sanitizedName;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
// Validate project name length
|
|
1434
|
-
if (projectName.length > 50) {
|
|
1435
|
-
console.error(chalk.red('❌ Project name is too long (max 50 characters)'));
|
|
1436
|
-
console.error(chalk.blue('💡 Usage: caws init <project-name>'));
|
|
1437
|
-
process.exit(1);
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
// Validate project name format
|
|
1441
|
-
if (projectName.length === 0) {
|
|
1442
|
-
console.error(chalk.red('❌ Project name cannot be empty'));
|
|
1443
|
-
console.error(chalk.blue('💡 Usage: caws init <project-name>'));
|
|
1444
|
-
process.exit(1);
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
// Check for invalid characters that should cause immediate failure
|
|
1448
|
-
if (projectName.includes('/') || projectName.includes('\\') || projectName.includes('..')) {
|
|
1449
|
-
console.error(chalk.red('❌ Project name contains invalid characters'));
|
|
1450
|
-
console.error(chalk.blue('💡 Usage: caws init <project-name>'));
|
|
1451
|
-
console.error(chalk.blue('💡 Project name should not contain: / \\ ..'));
|
|
1452
|
-
process.exit(1);
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
// Determine if initializing in current directory
|
|
1456
|
-
const initInCurrentDir = projectName === '.';
|
|
1457
|
-
const targetDir = initInCurrentDir ? process.cwd() : path.resolve(process.cwd(), projectName);
|
|
1458
|
-
|
|
1459
|
-
// Check if target directory already exists and has content (skip check for current directory)
|
|
1460
|
-
if (!initInCurrentDir && fs.existsSync(projectName)) {
|
|
1461
|
-
const existingFiles = fs.readdirSync(projectName);
|
|
1462
|
-
if (existingFiles.length > 0) {
|
|
1463
|
-
console.error(chalk.red(`❌ Directory '${projectName}' already exists and contains files`));
|
|
1464
|
-
console.error(chalk.blue('💡 To initialize CAWS in current directory instead:'));
|
|
1465
|
-
console.error(` ${chalk.cyan('caws init .')}`);
|
|
1466
|
-
console.error(chalk.blue('💡 Or choose a different name/remove existing directory'));
|
|
1467
|
-
process.exit(1);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
// Check if current directory has project files when trying to init in subdirectory
|
|
1472
|
-
if (!initInCurrentDir) {
|
|
1473
|
-
const currentDirFiles = fs.readdirSync(process.cwd());
|
|
1474
|
-
const hasProjectFiles = currentDirFiles.some(
|
|
1475
|
-
(file) => !file.startsWith('.') && file !== 'node_modules' && file !== '.git'
|
|
1476
|
-
);
|
|
1477
|
-
|
|
1478
|
-
if (hasProjectFiles) {
|
|
1479
|
-
console.warn(chalk.yellow('⚠️ Current directory contains project files'));
|
|
1480
|
-
console.warn(
|
|
1481
|
-
chalk.blue('💡 You might want to initialize CAWS in current directory instead:')
|
|
1482
|
-
);
|
|
1483
|
-
console.warn(` ${chalk.cyan('caws init .')}`);
|
|
1484
|
-
console.warn(chalk.blue(' Or continue to create subdirectory (Ctrl+C to cancel)'));
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
// Save the original template directory before changing directories
|
|
1489
|
-
const originalTemplateDir = cawsSetup?.hasTemplateDir ? cawsSetup.templateDir : null;
|
|
1490
|
-
|
|
1491
|
-
// Check for existing agents.md/caws.md in target directory
|
|
1492
|
-
const existingAgentsMd = fs.existsSync(path.join(targetDir, 'agents.md'));
|
|
1493
|
-
const existingCawsMd = fs.existsSync(path.join(targetDir, 'caws.md'));
|
|
1494
|
-
|
|
1495
|
-
// Create project directory and change to it (unless already in current directory)
|
|
1496
|
-
if (!initInCurrentDir) {
|
|
1497
|
-
await fs.ensureDir(projectName);
|
|
1498
|
-
process.chdir(projectName);
|
|
1499
|
-
console.log(chalk.green(`📁 Created project directory: ${projectName}`));
|
|
1500
|
-
} else {
|
|
1501
|
-
console.log(chalk.green(`📁 Initializing in current directory`));
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
// Detect and adapt to existing setup
|
|
1505
|
-
const currentSetup = detectCAWSSetup(process.cwd());
|
|
1506
|
-
|
|
1507
|
-
if (currentSetup.type === 'new') {
|
|
1508
|
-
// Create minimal CAWS structure
|
|
1509
|
-
await fs.ensureDir('.caws');
|
|
1510
|
-
await fs.ensureDir('.agent');
|
|
1511
|
-
console.log(chalk.blue('ℹ️ Created basic CAWS structure'));
|
|
1512
|
-
|
|
1513
|
-
// Copy agents.md guide if templates are available
|
|
1514
|
-
if (originalTemplateDir) {
|
|
1515
|
-
try {
|
|
1516
|
-
const agentsMdSource = path.join(originalTemplateDir, 'agents.md');
|
|
1517
|
-
let targetFile = 'agents.md';
|
|
1518
|
-
|
|
1519
|
-
if (fs.existsSync(agentsMdSource)) {
|
|
1520
|
-
// Use the pre-checked values for conflicts
|
|
1521
|
-
if (existingAgentsMd) {
|
|
1522
|
-
// Conflict: user already has agents.md
|
|
1523
|
-
if (options.interactive && !options.nonInteractive) {
|
|
1524
|
-
// Interactive mode: ask user
|
|
1525
|
-
const overwriteAnswer = await inquirer.prompt([
|
|
1526
|
-
{
|
|
1527
|
-
type: 'confirm',
|
|
1528
|
-
name: 'overwrite',
|
|
1529
|
-
message: '⚠️ agents.md already exists. Overwrite with CAWS guide?',
|
|
1530
|
-
default: false,
|
|
1531
|
-
},
|
|
1532
|
-
]);
|
|
1533
|
-
|
|
1534
|
-
if (overwriteAnswer.overwrite) {
|
|
1535
|
-
targetFile = 'agents.md';
|
|
1536
|
-
} else {
|
|
1537
|
-
targetFile = 'caws.md';
|
|
1538
|
-
}
|
|
1539
|
-
} else {
|
|
1540
|
-
// Non-interactive mode: use caws.md instead
|
|
1541
|
-
targetFile = 'caws.md';
|
|
1542
|
-
console.log(chalk.blue('ℹ️ agents.md exists, using caws.md for CAWS guide'));
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
// If caws.md also exists and that's our target, skip
|
|
1547
|
-
if (targetFile === 'caws.md' && existingCawsMd) {
|
|
1548
|
-
console.log(
|
|
1549
|
-
chalk.yellow('⚠️ Both agents.md and caws.md exist, skipping guide copy')
|
|
1550
|
-
);
|
|
1551
|
-
} else {
|
|
1552
|
-
const agentsMdDest = path.join(process.cwd(), targetFile);
|
|
1553
|
-
await fs.copyFile(agentsMdSource, agentsMdDest);
|
|
1554
|
-
console.log(chalk.green(`✅ Added ${targetFile} guide`));
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
} catch (templateError) {
|
|
1558
|
-
console.warn(chalk.yellow('⚠️ Could not copy agents guide:'), templateError.message);
|
|
1559
|
-
console.warn(
|
|
1560
|
-
chalk.blue('💡 You can manually copy the guide from the caws-template package')
|
|
1561
|
-
);
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
} else {
|
|
1565
|
-
// Already has CAWS setup
|
|
1566
|
-
console.log(chalk.green('✅ CAWS project detected - skipping template copy'));
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
// Handle interactive wizard or template-based setup
|
|
1570
|
-
if (options.interactive && !options.nonInteractive) {
|
|
1571
|
-
console.log(chalk.cyan('🎯 CAWS Interactive Setup Wizard'));
|
|
1572
|
-
console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1573
|
-
console.log(chalk.gray('This wizard will guide you through creating a CAWS working spec\n'));
|
|
1574
|
-
|
|
1575
|
-
// Detect project type
|
|
1576
|
-
const detectedType = detectProjectType(process.cwd());
|
|
1577
|
-
console.log(chalk.blue(`📦 Detected project type: ${chalk.cyan(detectedType)}`));
|
|
1578
|
-
|
|
1579
|
-
// Get package.json info if available
|
|
1580
|
-
let packageJson = {};
|
|
1581
|
-
try {
|
|
1582
|
-
packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
|
|
1583
|
-
} catch (e) {
|
|
1584
|
-
// No package.json, that's fine
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
const wizardQuestions = [
|
|
1588
|
-
{
|
|
1589
|
-
type: 'list',
|
|
1590
|
-
name: 'projectType',
|
|
1591
|
-
message: '❓ What type of project is this?',
|
|
1592
|
-
choices: [
|
|
1593
|
-
{
|
|
1594
|
-
name: '🔌 VS Code Extension (webview, commands, integrations)',
|
|
1595
|
-
value: 'extension',
|
|
1596
|
-
short: 'VS Code Extension',
|
|
1597
|
-
},
|
|
1598
|
-
{
|
|
1599
|
-
name: '📚 Library/Package (reusable components, utilities)',
|
|
1600
|
-
value: 'library',
|
|
1601
|
-
short: 'Library',
|
|
1602
|
-
},
|
|
1603
|
-
{
|
|
1604
|
-
name: '🌐 API Service (REST, GraphQL, microservices)',
|
|
1605
|
-
value: 'api',
|
|
1606
|
-
short: 'API Service',
|
|
1607
|
-
},
|
|
1608
|
-
{
|
|
1609
|
-
name: '💻 CLI Tool (command-line interface)',
|
|
1610
|
-
value: 'cli',
|
|
1611
|
-
short: 'CLI Tool',
|
|
1612
|
-
},
|
|
1613
|
-
{
|
|
1614
|
-
name: '🏗️ Monorepo (multiple packages/apps)',
|
|
1615
|
-
value: 'monorepo',
|
|
1616
|
-
short: 'Monorepo',
|
|
1617
|
-
},
|
|
1618
|
-
{
|
|
1619
|
-
name: '📱 Application (standalone app)',
|
|
1620
|
-
value: 'application',
|
|
1621
|
-
short: 'Application',
|
|
1622
|
-
},
|
|
1623
|
-
],
|
|
1624
|
-
default: detectedType,
|
|
1625
|
-
},
|
|
1626
|
-
{
|
|
1627
|
-
type: 'input',
|
|
1628
|
-
name: 'projectTitle',
|
|
1629
|
-
message: '📝 Project Title (descriptive name):',
|
|
1630
|
-
default:
|
|
1631
|
-
packageJson.name ||
|
|
1632
|
-
projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
|
|
1633
|
-
},
|
|
1634
|
-
{
|
|
1635
|
-
type: 'list',
|
|
1636
|
-
name: 'riskTier',
|
|
1637
|
-
message: '⚠️ Risk Tier (higher tier = more rigor):',
|
|
1638
|
-
choices: [
|
|
1639
|
-
{
|
|
1640
|
-
name: '🔴 Tier 1 - Critical (auth, billing, migrations) - Max rigor',
|
|
1641
|
-
value: 1,
|
|
1642
|
-
short: 'Critical',
|
|
1643
|
-
},
|
|
1644
|
-
{
|
|
1645
|
-
name: '🟡 Tier 2 - Standard (features, APIs) - Standard rigor',
|
|
1646
|
-
value: 2,
|
|
1647
|
-
short: 'Standard',
|
|
1648
|
-
},
|
|
1649
|
-
{
|
|
1650
|
-
name: '🟢 Tier 3 - Low Risk (UI, tooling) - Basic rigor',
|
|
1651
|
-
value: 3,
|
|
1652
|
-
short: 'Low Risk',
|
|
1653
|
-
},
|
|
1654
|
-
],
|
|
1655
|
-
default: (answers) => {
|
|
1656
|
-
const typeDefaults = {
|
|
1657
|
-
extension: 2,
|
|
1658
|
-
library: 2,
|
|
1659
|
-
api: 1,
|
|
1660
|
-
cli: 3,
|
|
1661
|
-
monorepo: 1,
|
|
1662
|
-
application: 2,
|
|
1663
|
-
};
|
|
1664
|
-
return typeDefaults[answers.projectType] || 2;
|
|
1665
|
-
},
|
|
1666
|
-
},
|
|
1667
|
-
{
|
|
1668
|
-
type: 'list',
|
|
1669
|
-
name: 'projectMode',
|
|
1670
|
-
message: '🎯 Primary development mode:',
|
|
1671
|
-
choices: [
|
|
1672
|
-
{ name: '✨ feature (new functionality)', value: 'feature' },
|
|
1673
|
-
{ name: '🔄 refactor (code restructuring)', value: 'refactor' },
|
|
1674
|
-
{ name: '🐛 fix (bug fixes)', value: 'fix' },
|
|
1675
|
-
{ name: '📚 doc (documentation)', value: 'doc' },
|
|
1676
|
-
{ name: '🧹 chore (maintenance)', value: 'chore' },
|
|
1677
|
-
],
|
|
1678
|
-
default: 'feature',
|
|
1679
|
-
},
|
|
1680
|
-
{
|
|
1681
|
-
type: 'number',
|
|
1682
|
-
name: 'maxFiles',
|
|
1683
|
-
message: '📊 Max files to change per feature:',
|
|
1684
|
-
default: (answers) => {
|
|
1685
|
-
const tierDefaults = { 1: 40, 2: 25, 3: 15 };
|
|
1686
|
-
const typeAdjustments = {
|
|
1687
|
-
extension: -5,
|
|
1688
|
-
library: -10,
|
|
1689
|
-
api: 10,
|
|
1690
|
-
cli: -10,
|
|
1691
|
-
monorepo: 25,
|
|
1692
|
-
application: 0,
|
|
1693
|
-
};
|
|
1694
|
-
return Math.max(
|
|
1695
|
-
5,
|
|
1696
|
-
tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
|
|
1697
|
-
);
|
|
1698
|
-
},
|
|
1699
|
-
},
|
|
1700
|
-
{
|
|
1701
|
-
type: 'number',
|
|
1702
|
-
name: 'maxLoc',
|
|
1703
|
-
message: '📏 Max lines of code to change per feature:',
|
|
1704
|
-
default: (answers) => {
|
|
1705
|
-
const tierDefaults = { 1: 1500, 2: 1000, 3: 600 };
|
|
1706
|
-
const typeAdjustments = {
|
|
1707
|
-
extension: -200,
|
|
1708
|
-
library: -300,
|
|
1709
|
-
api: 500,
|
|
1710
|
-
cli: -400,
|
|
1711
|
-
monorepo: 1000,
|
|
1712
|
-
application: 0,
|
|
1713
|
-
};
|
|
1714
|
-
return Math.max(
|
|
1715
|
-
50,
|
|
1716
|
-
tierDefaults[answers.riskTier] + (typeAdjustments[answers.projectType] || 0)
|
|
1717
|
-
);
|
|
1718
|
-
},
|
|
1719
|
-
},
|
|
1720
|
-
{
|
|
1721
|
-
type: 'input',
|
|
1722
|
-
name: 'blastModules',
|
|
1723
|
-
message: '💥 Affected modules (comma-separated):',
|
|
1724
|
-
default: (answers) => {
|
|
1725
|
-
const typeDefaults = {
|
|
1726
|
-
extension: 'core,webview',
|
|
1727
|
-
library: 'components,utils',
|
|
1728
|
-
api: 'routes,models,controllers',
|
|
1729
|
-
cli: 'commands,utils',
|
|
1730
|
-
monorepo: 'shared,packages',
|
|
1731
|
-
application: 'ui,logic,data',
|
|
1732
|
-
};
|
|
1733
|
-
return typeDefaults[answers.projectType] || 'core,ui';
|
|
1734
|
-
},
|
|
1735
|
-
},
|
|
1736
|
-
{
|
|
1737
|
-
type: 'confirm',
|
|
1738
|
-
name: 'dataMigration',
|
|
1739
|
-
message: '🗄️ Requires data migration?',
|
|
1740
|
-
default: false,
|
|
1741
|
-
},
|
|
1742
|
-
{
|
|
1743
|
-
type: 'list',
|
|
1744
|
-
name: 'rollbackSlo',
|
|
1745
|
-
message: '⏱️ Operational rollback SLO:',
|
|
1746
|
-
choices: [
|
|
1747
|
-
{ name: '⚡ 1 minute (critical systems)', value: '1m' },
|
|
1748
|
-
{ name: '🟡 5 minutes (standard)', value: '5m' },
|
|
1749
|
-
{ name: '🟠 15 minutes (complex)', value: '15m' },
|
|
1750
|
-
{ name: '🔴 1 hour (data migration)', value: '1h' },
|
|
1751
|
-
],
|
|
1752
|
-
default: '5m',
|
|
1753
|
-
},
|
|
1754
|
-
];
|
|
1755
|
-
|
|
1756
|
-
console.log(chalk.cyan('⏳ Gathering project requirements...'));
|
|
1757
|
-
|
|
1758
|
-
let wizardAnswers;
|
|
1759
|
-
try {
|
|
1760
|
-
wizardAnswers = await inquirer.prompt(wizardQuestions);
|
|
1761
|
-
} catch (error) {
|
|
1762
|
-
if (error.isTtyError) {
|
|
1763
|
-
console.error(chalk.red('❌ Interactive prompts not supported in this environment'));
|
|
1764
|
-
console.error(chalk.blue('💡 Run with --non-interactive flag to use defaults'));
|
|
1765
|
-
process.exit(1);
|
|
1766
|
-
} else {
|
|
1767
|
-
console.error(chalk.red('❌ Error during interactive setup:'), error.message);
|
|
1768
|
-
process.exit(1);
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
console.log(chalk.green('✅ Project requirements gathered successfully!'));
|
|
1773
|
-
|
|
1774
|
-
// Show summary before generating spec
|
|
1775
|
-
console.log(chalk.bold('\n📋 Configuration Summary:'));
|
|
1776
|
-
console.log(` ${chalk.cyan('Type')}: ${wizardAnswers.projectType}`);
|
|
1777
|
-
console.log(` ${chalk.cyan('Project')}: ${wizardAnswers.projectTitle}`);
|
|
1778
|
-
console.log(
|
|
1779
|
-
` ${chalk.cyan('Mode')}: ${wizardAnswers.projectMode} | ${chalk.cyan('Tier')}: ${wizardAnswers.riskTier}`
|
|
1780
|
-
);
|
|
1781
|
-
console.log(
|
|
1782
|
-
` ${chalk.cyan('Budget')}: ${wizardAnswers.maxFiles} files, ${wizardAnswers.maxLoc} lines`
|
|
1783
|
-
);
|
|
1784
|
-
console.log(
|
|
1785
|
-
` ${chalk.cyan('Data Migration')}: ${wizardAnswers.dataMigration ? 'Yes' : 'No'}`
|
|
1786
|
-
);
|
|
1787
|
-
console.log(` ${chalk.cyan('Rollback SLO')}: ${wizardAnswers.rollbackSlo}`);
|
|
1788
|
-
|
|
1789
|
-
// Generate working spec using the template system
|
|
1790
|
-
const analysis = {
|
|
1791
|
-
projectType: wizardAnswers.projectType,
|
|
1792
|
-
packageJson: { name: wizardAnswers.projectTitle },
|
|
1793
|
-
hasTests: false,
|
|
1794
|
-
hasLinting: false,
|
|
1795
|
-
hasCi: false,
|
|
1796
|
-
};
|
|
1797
|
-
|
|
1798
|
-
const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
|
|
1799
|
-
|
|
1800
|
-
// Override template-generated values with wizard answers
|
|
1801
|
-
const spec = yaml.load(workingSpecContent);
|
|
1802
|
-
spec.title = wizardAnswers.projectTitle;
|
|
1803
|
-
spec.risk_tier = wizardAnswers.riskTier;
|
|
1804
|
-
spec.mode = wizardAnswers.projectMode;
|
|
1805
|
-
spec.change_budget = {
|
|
1806
|
-
max_files: wizardAnswers.maxFiles,
|
|
1807
|
-
max_loc: wizardAnswers.maxLoc,
|
|
1808
|
-
};
|
|
1809
|
-
spec.blast_radius = {
|
|
1810
|
-
modules: wizardAnswers.blastModules
|
|
1811
|
-
.split(',')
|
|
1812
|
-
.map((m) => m.trim())
|
|
1813
|
-
.filter((m) => m),
|
|
1814
|
-
data_migration: wizardAnswers.dataMigration,
|
|
1815
|
-
};
|
|
1816
|
-
spec.operational_rollback_slo = wizardAnswers.rollbackSlo;
|
|
1817
|
-
|
|
1818
|
-
// Validate the generated spec
|
|
1819
|
-
validateGeneratedSpec(yaml.dump(spec), wizardAnswers);
|
|
1820
|
-
|
|
1821
|
-
// Save the working spec
|
|
1822
|
-
await fs.writeFile('.caws/working-spec.yaml', yaml.dump(spec, { indent: 2 }));
|
|
1823
|
-
|
|
1824
|
-
console.log(chalk.green('✅ Working spec generated and validated'));
|
|
1825
|
-
|
|
1826
|
-
// Generate getting started guide
|
|
1827
|
-
const wizardAnalysis = {
|
|
1828
|
-
projectType: wizardAnswers.projectType,
|
|
1829
|
-
packageJson: { name: wizardAnswers.projectTitle },
|
|
1830
|
-
hasTests: false,
|
|
1831
|
-
hasLinting: false,
|
|
1832
|
-
hasCi: false,
|
|
1833
|
-
};
|
|
1834
|
-
|
|
1835
|
-
const guideContent = generateGettingStartedGuide(wizardAnalysis);
|
|
1836
|
-
await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
|
|
1837
|
-
console.log(chalk.green('✅ Getting started guide created'));
|
|
1838
|
-
|
|
1839
|
-
// Generate or update .gitignore with CAWS patterns
|
|
1840
|
-
const existingGitignore = fs.existsSync('.gitignore')
|
|
1841
|
-
? fs.readFileSync('.gitignore', 'utf8')
|
|
1842
|
-
: '';
|
|
1843
|
-
|
|
1844
|
-
const updatedGitignore = generateGitignorePatterns(existingGitignore);
|
|
1845
|
-
if (updatedGitignore !== existingGitignore) {
|
|
1846
|
-
await fs.writeFile('.gitignore', updatedGitignore);
|
|
1847
|
-
const action = existingGitignore.trim() ? 'updated' : 'created';
|
|
1848
|
-
console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
// Finalize project with provenance and git initialization
|
|
1852
|
-
await finalizeProject(projectName, options, wizardAnswers);
|
|
1853
|
-
|
|
1854
|
-
continueToSuccess();
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
// Handle template-based setup
|
|
1859
|
-
if (options.template) {
|
|
1860
|
-
console.log(chalk.cyan(`🎯 Using ${options.template} template`));
|
|
1861
|
-
|
|
1862
|
-
const validTemplates = ['extension', 'library', 'api', 'cli', 'monorepo'];
|
|
1863
|
-
if (!validTemplates.includes(options.template)) {
|
|
1864
|
-
console.error(chalk.red(`❌ Invalid template: ${options.template}`));
|
|
1865
|
-
console.error(chalk.blue(`💡 Valid templates: ${validTemplates.join(', ')}`));
|
|
1866
|
-
process.exit(1);
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
const analysis = {
|
|
1870
|
-
projectType: options.template,
|
|
1871
|
-
packageJson: { name: projectName },
|
|
1872
|
-
hasTests: false,
|
|
1873
|
-
hasLinting: false,
|
|
1874
|
-
hasCi: false,
|
|
1875
|
-
};
|
|
1876
|
-
|
|
1877
|
-
const workingSpecContent = yaml.dump(generateWorkingSpecFromAnalysis(analysis));
|
|
1878
|
-
|
|
1879
|
-
// Validate the generated spec
|
|
1880
|
-
validateGeneratedSpec(workingSpecContent, { projectType: options.template });
|
|
1881
|
-
|
|
1882
|
-
// Save the working spec
|
|
1883
|
-
await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
|
|
1884
|
-
|
|
1885
|
-
console.log(chalk.green('✅ Working spec generated from template'));
|
|
1886
|
-
|
|
1887
|
-
// Generate getting started guide
|
|
1888
|
-
const templateAnalysis = {
|
|
1889
|
-
projectType: options.template,
|
|
1890
|
-
packageJson: { name: projectName },
|
|
1891
|
-
hasTests: false,
|
|
1892
|
-
hasLinting: false,
|
|
1893
|
-
hasCi: false,
|
|
1894
|
-
};
|
|
1895
|
-
|
|
1896
|
-
const guideContent = generateGettingStartedGuide(templateAnalysis);
|
|
1897
|
-
await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
|
|
1898
|
-
console.log(chalk.green('✅ Getting started guide created'));
|
|
1899
|
-
|
|
1900
|
-
// Generate or update .gitignore with CAWS patterns
|
|
1901
|
-
const existingGitignore = fs.existsSync('.gitignore')
|
|
1902
|
-
? fs.readFileSync('.gitignore', 'utf8')
|
|
1903
|
-
: '';
|
|
1904
|
-
|
|
1905
|
-
const updatedGitignore = generateGitignorePatterns(existingGitignore);
|
|
1906
|
-
if (updatedGitignore !== existingGitignore) {
|
|
1907
|
-
await fs.writeFile('.gitignore', updatedGitignore);
|
|
1908
|
-
const action = existingGitignore.trim() ? 'updated' : 'created';
|
|
1909
|
-
console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// Finalize project
|
|
1913
|
-
await finalizeProject(projectName, options, { projectType: options.template });
|
|
1914
|
-
|
|
1915
|
-
continueToSuccess();
|
|
1916
|
-
return;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
// Set default answers for non-interactive mode
|
|
1920
|
-
if (!options.interactive || options.nonInteractive) {
|
|
1921
|
-
// Use directory name for current directory init
|
|
1922
|
-
const displayName = initInCurrentDir ? path.basename(process.cwd()) : projectName;
|
|
1923
|
-
|
|
1924
|
-
answers = {
|
|
1925
|
-
projectId: displayName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
|
|
1926
|
-
projectTitle: displayName.charAt(0).toUpperCase() + displayName.slice(1).replace(/-/g, ' '),
|
|
1927
|
-
riskTier: 2,
|
|
1928
|
-
projectMode: 'feature',
|
|
1929
|
-
maxFiles: 25,
|
|
1930
|
-
maxLoc: 1000,
|
|
1931
|
-
blastModules: 'core,ui',
|
|
1932
|
-
dataMigration: false,
|
|
1933
|
-
rollbackSlo: '5m',
|
|
1934
|
-
projectThreats: 'Standard project threats',
|
|
1935
|
-
scopeIn: 'project files',
|
|
1936
|
-
scopeOut: 'external dependencies',
|
|
1937
|
-
projectInvariants: 'System maintains consistency',
|
|
1938
|
-
acceptanceCriteria: 'GIVEN current state WHEN action THEN expected result',
|
|
1939
|
-
a11yRequirements: 'keyboard navigation, screen reader support',
|
|
1940
|
-
perfBudget: 250,
|
|
1941
|
-
securityRequirements: 'input validation, authentication',
|
|
1942
|
-
contractType: 'openapi',
|
|
1943
|
-
contractPath: 'apps/contracts/api.yaml',
|
|
1944
|
-
observabilityLogs: 'auth.success,api.request',
|
|
1945
|
-
observabilityMetrics: 'requests_total',
|
|
1946
|
-
observabilityTraces: 'api_flow',
|
|
1947
|
-
migrationPlan: 'Standard deployment process',
|
|
1948
|
-
rollbackPlan: 'Feature flag disable and rollback',
|
|
1949
|
-
needsOverride: false,
|
|
1950
|
-
overrideRationale: '',
|
|
1951
|
-
overrideApprover: '',
|
|
1952
|
-
waivedGates: [],
|
|
1953
|
-
overrideExpiresDays: 7,
|
|
1954
|
-
isExperimental: false,
|
|
1955
|
-
experimentalRationale: '',
|
|
1956
|
-
experimentalSandbox: 'experimental/',
|
|
1957
|
-
experimentalExpiresDays: 14,
|
|
1958
|
-
aiConfidence: 7,
|
|
1959
|
-
uncertaintyAreas: '',
|
|
1960
|
-
complexityFactors: '',
|
|
1961
|
-
};
|
|
1962
|
-
|
|
1963
|
-
// Generate working spec for non-interactive mode
|
|
1964
|
-
const workingSpecContent = generateWorkingSpec(answers);
|
|
1965
|
-
|
|
1966
|
-
// Validate the generated spec
|
|
1967
|
-
validateGeneratedSpec(workingSpecContent, answers);
|
|
1968
|
-
|
|
1969
|
-
// Save the working spec
|
|
1970
|
-
await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
|
|
1971
|
-
|
|
1972
|
-
console.log(chalk.green('✅ Working spec generated and validated'));
|
|
1973
|
-
|
|
1974
|
-
// Generate getting started guide (detect project type)
|
|
1975
|
-
const detectedType = detectProjectType(process.cwd());
|
|
1976
|
-
const defaultAnalysis = {
|
|
1977
|
-
projectType: detectedType,
|
|
1978
|
-
packageJson: { name: displayName },
|
|
1979
|
-
hasTests: false,
|
|
1980
|
-
hasLinting: false,
|
|
1981
|
-
hasCi: false,
|
|
1982
|
-
};
|
|
1983
|
-
|
|
1984
|
-
const guideContent = generateGettingStartedGuide(defaultAnalysis);
|
|
1985
|
-
await fs.writeFile('.caws/GETTING_STARTED.md', guideContent);
|
|
1986
|
-
console.log(chalk.green('✅ Getting started guide created'));
|
|
1987
|
-
|
|
1988
|
-
// Generate or update .gitignore with CAWS patterns
|
|
1989
|
-
const existingGitignore = fs.existsSync('.gitignore')
|
|
1990
|
-
? fs.readFileSync('.gitignore', 'utf8')
|
|
1991
|
-
: '';
|
|
1992
|
-
|
|
1993
|
-
const updatedGitignore = generateGitignorePatterns(existingGitignore);
|
|
1994
|
-
if (updatedGitignore !== existingGitignore) {
|
|
1995
|
-
await fs.writeFile('.gitignore', updatedGitignore);
|
|
1996
|
-
const action = existingGitignore.trim() ? 'updated' : 'created';
|
|
1997
|
-
console.log(chalk.green(`✅ .gitignore ${action} with CAWS patterns`));
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
// Finalize project with provenance and git initialization
|
|
2001
|
-
await finalizeProject(projectName, options, answers);
|
|
2002
|
-
|
|
2003
|
-
continueToSuccess();
|
|
2004
|
-
return;
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
if (options.interactive && !options.nonInteractive) {
|
|
2008
|
-
// Interactive setup with enhanced prompts
|
|
2009
|
-
console.log(chalk.cyan('🔧 Starting interactive project configuration...'));
|
|
2010
|
-
console.log(chalk.blue('💡 Press Ctrl+C at any time to exit and use defaults'));
|
|
2011
|
-
|
|
2012
|
-
const questions = [
|
|
2013
|
-
{
|
|
2014
|
-
type: 'input',
|
|
2015
|
-
name: 'projectId',
|
|
2016
|
-
message: '📋 Project ID (e.g., FEAT-1234, AUTH-456):',
|
|
2017
|
-
default: projectName.toUpperCase().replace(/[^A-Z0-9]/g, '-') + '-001',
|
|
2018
|
-
validate: (input) => {
|
|
2019
|
-
if (!input.trim()) return 'Project ID is required';
|
|
2020
|
-
const pattern = /^[A-Z]+-\d+$/;
|
|
2021
|
-
if (!pattern.test(input)) {
|
|
2022
|
-
return 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)';
|
|
2023
|
-
}
|
|
2024
|
-
return true;
|
|
2025
|
-
},
|
|
2026
|
-
},
|
|
2027
|
-
{
|
|
2028
|
-
type: 'input',
|
|
2029
|
-
name: 'projectTitle',
|
|
2030
|
-
message: '📝 Project Title (descriptive name):',
|
|
2031
|
-
default: projectName.charAt(0).toUpperCase() + projectName.slice(1).replace(/-/g, ' '),
|
|
2032
|
-
validate: (input) => {
|
|
2033
|
-
if (!input.trim()) return 'Project title is required';
|
|
2034
|
-
if (input.trim().length < 8) {
|
|
2035
|
-
return 'Project title should be at least 8 characters long';
|
|
2036
|
-
}
|
|
2037
|
-
return true;
|
|
2038
|
-
},
|
|
2039
|
-
},
|
|
2040
|
-
{
|
|
2041
|
-
type: 'list',
|
|
2042
|
-
name: 'riskTier',
|
|
2043
|
-
message: '⚠️ Risk Tier (higher tier = more rigor):',
|
|
2044
|
-
choices: [
|
|
2045
|
-
{
|
|
2046
|
-
name: '🔴 Tier 1 - Critical (auth, billing, migrations) - Max rigor',
|
|
2047
|
-
value: 1,
|
|
2048
|
-
},
|
|
2049
|
-
{
|
|
2050
|
-
name: '🟡 Tier 2 - Standard (features, APIs) - Standard rigor',
|
|
2051
|
-
value: 2,
|
|
2052
|
-
},
|
|
2053
|
-
{
|
|
2054
|
-
name: '🟢 Tier 3 - Low Risk (UI, tooling) - Basic rigor',
|
|
2055
|
-
value: 3,
|
|
2056
|
-
},
|
|
2057
|
-
],
|
|
2058
|
-
default: 2,
|
|
2059
|
-
},
|
|
2060
|
-
{
|
|
2061
|
-
type: 'list',
|
|
2062
|
-
name: 'projectMode',
|
|
2063
|
-
message: '🎯 Project Mode:',
|
|
2064
|
-
choices: [
|
|
2065
|
-
{ name: '✨ feature (new functionality)', value: 'feature' },
|
|
2066
|
-
{ name: '🔄 refactor (code restructuring)', value: 'refactor' },
|
|
2067
|
-
{ name: '🐛 fix (bug fixes)', value: 'fix' },
|
|
2068
|
-
{ name: '📚 doc (documentation)', value: 'doc' },
|
|
2069
|
-
{ name: '🧹 chore (maintenance)', value: 'chore' },
|
|
2070
|
-
],
|
|
2071
|
-
default: 'feature',
|
|
2072
|
-
},
|
|
2073
|
-
{
|
|
2074
|
-
type: 'number',
|
|
2075
|
-
name: 'maxFiles',
|
|
2076
|
-
message: '📊 Max files to change:',
|
|
2077
|
-
default: (answers) => {
|
|
2078
|
-
// Dynamic defaults based on risk tier
|
|
2079
|
-
switch (answers.riskTier) {
|
|
2080
|
-
case 1:
|
|
2081
|
-
return 40;
|
|
2082
|
-
case 2:
|
|
2083
|
-
return 25;
|
|
2084
|
-
case 3:
|
|
2085
|
-
return 15;
|
|
2086
|
-
default:
|
|
2087
|
-
return 25;
|
|
2088
|
-
}
|
|
2089
|
-
},
|
|
2090
|
-
validate: (input) => {
|
|
2091
|
-
if (input < 1) return 'Must change at least 1 file';
|
|
2092
|
-
return true;
|
|
2093
|
-
},
|
|
2094
|
-
},
|
|
2095
|
-
{
|
|
2096
|
-
type: 'number',
|
|
2097
|
-
name: 'maxLoc',
|
|
2098
|
-
message: '📏 Max lines of code to change:',
|
|
2099
|
-
default: (answers) => {
|
|
2100
|
-
// Dynamic defaults based on risk tier
|
|
2101
|
-
switch (answers.riskTier) {
|
|
2102
|
-
case 1:
|
|
2103
|
-
return 1500;
|
|
2104
|
-
case 2:
|
|
2105
|
-
return 1000;
|
|
2106
|
-
case 3:
|
|
2107
|
-
return 600;
|
|
2108
|
-
default:
|
|
2109
|
-
return 1000;
|
|
2110
|
-
}
|
|
2111
|
-
},
|
|
2112
|
-
validate: (input) => {
|
|
2113
|
-
if (input < 1) return 'Must change at least 1 line';
|
|
2114
|
-
return true;
|
|
2115
|
-
},
|
|
2116
|
-
},
|
|
2117
|
-
{
|
|
2118
|
-
type: 'input',
|
|
2119
|
-
name: 'blastModules',
|
|
2120
|
-
message: '💥 Blast Radius - Affected modules (comma-separated):',
|
|
2121
|
-
default: 'core,api',
|
|
2122
|
-
validate: (input) => {
|
|
2123
|
-
if (!input.trim()) return 'At least one module must be specified';
|
|
2124
|
-
return true;
|
|
2125
|
-
},
|
|
2126
|
-
},
|
|
2127
|
-
{
|
|
2128
|
-
type: 'confirm',
|
|
2129
|
-
name: 'dataMigration',
|
|
2130
|
-
message: '🗄️ Requires data migration?',
|
|
2131
|
-
default: false,
|
|
2132
|
-
},
|
|
2133
|
-
{
|
|
2134
|
-
type: 'input',
|
|
2135
|
-
name: 'rollbackSlo',
|
|
2136
|
-
message: '⏱️ Operational rollback SLO (e.g., 5m, 1h, 24h):',
|
|
2137
|
-
default: '5m',
|
|
2138
|
-
validate: (input) => {
|
|
2139
|
-
const pattern = /^([0-9]+m|[0-9]+h|[0-9]+d)$/;
|
|
2140
|
-
if (!pattern.test(input)) {
|
|
2141
|
-
return 'SLO should be in format: NUMBER + m/h/d (e.g., 5m, 1h, 24h)';
|
|
2142
|
-
}
|
|
2143
|
-
return true;
|
|
2144
|
-
},
|
|
2145
|
-
},
|
|
2146
|
-
{
|
|
2147
|
-
type: 'editor',
|
|
2148
|
-
name: 'projectThreats',
|
|
2149
|
-
message: '⚠️ Project Threats & Risks (one per line, ESC to finish):',
|
|
2150
|
-
default: (answers) => {
|
|
2151
|
-
const baseThreats =
|
|
2152
|
-
'- Race condition in concurrent operations\n- Performance degradation under load';
|
|
2153
|
-
if (answers.dataMigration) {
|
|
2154
|
-
return baseThreats + '\n- Data migration failure\n- Inconsistent data state';
|
|
2155
|
-
}
|
|
2156
|
-
return baseThreats;
|
|
2157
|
-
},
|
|
2158
|
-
},
|
|
2159
|
-
{
|
|
2160
|
-
type: 'input',
|
|
2161
|
-
name: 'scopeIn',
|
|
2162
|
-
message: "🎯 Scope IN - What's included (comma-separated):",
|
|
2163
|
-
default: (answers) => {
|
|
2164
|
-
if (answers.projectMode === 'feature') return 'user authentication, api endpoints';
|
|
2165
|
-
if (answers.projectMode === 'refactor') return 'authentication module, user service';
|
|
2166
|
-
if (answers.projectMode === 'fix') return 'error handling, validation';
|
|
2167
|
-
return 'project files';
|
|
2168
|
-
},
|
|
2169
|
-
validate: (input) => {
|
|
2170
|
-
if (!input.trim()) return 'At least one scope item must be specified';
|
|
2171
|
-
return true;
|
|
2172
|
-
},
|
|
2173
|
-
},
|
|
2174
|
-
{
|
|
2175
|
-
type: 'input',
|
|
2176
|
-
name: 'scopeOut',
|
|
2177
|
-
message: "🚫 Scope OUT - What's excluded (comma-separated):",
|
|
2178
|
-
default: (answers) => {
|
|
2179
|
-
if (answers.projectMode === 'feature')
|
|
2180
|
-
return 'legacy authentication, deprecated endpoints';
|
|
2181
|
-
if (answers.projectMode === 'refactor')
|
|
2182
|
-
return 'external dependencies, configuration files';
|
|
2183
|
-
return 'unrelated features';
|
|
2184
|
-
},
|
|
2185
|
-
},
|
|
2186
|
-
{
|
|
2187
|
-
type: 'editor',
|
|
2188
|
-
name: 'projectInvariants',
|
|
2189
|
-
message: '🛡️ System Invariants (one per line, ESC to finish):',
|
|
2190
|
-
default:
|
|
2191
|
-
'- System remains available\n- Data consistency maintained\n- User sessions preserved',
|
|
2192
|
-
},
|
|
2193
|
-
{
|
|
2194
|
-
type: 'editor',
|
|
2195
|
-
name: 'acceptanceCriteria',
|
|
2196
|
-
message: '✅ Acceptance Criteria (GIVEN...WHEN...THEN, one per line, ESC to finish):',
|
|
2197
|
-
default: (answers) => {
|
|
2198
|
-
if (answers.projectMode === 'feature') {
|
|
2199
|
-
return 'GIVEN user is authenticated WHEN accessing protected endpoint THEN access is granted\nGIVEN invalid credentials WHEN attempting login THEN access is denied';
|
|
2200
|
-
}
|
|
2201
|
-
if (answers.projectMode === 'fix') {
|
|
2202
|
-
return 'GIVEN existing functionality WHEN applying fix THEN behavior is preserved\nGIVEN error condition WHEN fix is applied THEN error is resolved';
|
|
2203
|
-
}
|
|
2204
|
-
return 'GIVEN current system state WHEN change is applied THEN expected behavior occurs';
|
|
2205
|
-
},
|
|
2206
|
-
validate: (input) => {
|
|
2207
|
-
if (!input.trim()) return 'At least one acceptance criterion is required';
|
|
2208
|
-
const lines = input
|
|
2209
|
-
.trim()
|
|
2210
|
-
.split('\n')
|
|
2211
|
-
.filter((line) => line.trim());
|
|
2212
|
-
if (lines.length === 0) return 'At least one acceptance criterion is required';
|
|
2213
|
-
return true;
|
|
2214
|
-
},
|
|
2215
|
-
},
|
|
2216
|
-
{
|
|
2217
|
-
type: 'input',
|
|
2218
|
-
name: 'a11yRequirements',
|
|
2219
|
-
message: '♿ Accessibility Requirements (comma-separated):',
|
|
2220
|
-
default: 'keyboard navigation, screen reader support, color contrast',
|
|
2221
|
-
},
|
|
2222
|
-
{
|
|
2223
|
-
type: 'number',
|
|
2224
|
-
name: 'perfBudget',
|
|
2225
|
-
message: '⚡ Performance Budget (API p95 latency in ms):',
|
|
2226
|
-
default: 250,
|
|
2227
|
-
validate: (input) => {
|
|
2228
|
-
if (input < 1) return 'Performance budget must be at least 1ms';
|
|
2229
|
-
if (input > 10000) return 'Performance budget seems too high (max 10s)';
|
|
2230
|
-
return true;
|
|
2231
|
-
},
|
|
2232
|
-
},
|
|
2233
|
-
{
|
|
2234
|
-
type: 'input',
|
|
2235
|
-
name: 'securityRequirements',
|
|
2236
|
-
message: '🔒 Security Requirements (comma-separated):',
|
|
2237
|
-
default: 'input validation, rate limiting, authentication, authorization',
|
|
2238
|
-
},
|
|
2239
|
-
{
|
|
2240
|
-
type: 'list',
|
|
2241
|
-
name: 'contractType',
|
|
2242
|
-
message: '📄 Contract Type:',
|
|
2243
|
-
choices: [
|
|
2244
|
-
{ name: 'OpenAPI (REST APIs)', value: 'openapi' },
|
|
2245
|
-
{ name: 'GraphQL Schema', value: 'graphql' },
|
|
2246
|
-
{ name: 'Protocol Buffers', value: 'proto' },
|
|
2247
|
-
{ name: 'Pact (consumer-driven)', value: 'pact' },
|
|
2248
|
-
],
|
|
2249
|
-
default: 'openapi',
|
|
2250
|
-
},
|
|
2251
|
-
{
|
|
2252
|
-
type: 'input',
|
|
2253
|
-
name: 'contractPath',
|
|
2254
|
-
message: '📁 Contract File Path:',
|
|
2255
|
-
default: (answers) => {
|
|
2256
|
-
if (answers.contractType === 'openapi') return 'apps/contracts/api.yaml';
|
|
2257
|
-
if (answers.contractType === 'graphql') return 'apps/contracts/schema.graphql';
|
|
2258
|
-
if (answers.contractType === 'proto') return 'apps/contracts/service.proto';
|
|
2259
|
-
if (answers.contractType === 'pact') return 'apps/contracts/pacts/';
|
|
2260
|
-
return 'apps/contracts/api.yaml';
|
|
2261
|
-
},
|
|
2262
|
-
},
|
|
2263
|
-
{
|
|
2264
|
-
type: 'input',
|
|
2265
|
-
name: 'observabilityLogs',
|
|
2266
|
-
message: '📝 Observability - Log Events (comma-separated):',
|
|
2267
|
-
default: 'auth.success, auth.failure, api.request, api.response',
|
|
2268
|
-
},
|
|
2269
|
-
{
|
|
2270
|
-
type: 'input',
|
|
2271
|
-
name: 'observabilityMetrics',
|
|
2272
|
-
message: '📊 Observability - Metrics (comma-separated):',
|
|
2273
|
-
default: 'auth_attempts_total, auth_success_total, api_requests_total, api_errors_total',
|
|
2274
|
-
},
|
|
2275
|
-
{
|
|
2276
|
-
type: 'input',
|
|
2277
|
-
name: 'observabilityTraces',
|
|
2278
|
-
message: '🔍 Observability - Traces (comma-separated):',
|
|
2279
|
-
default: 'auth_flow, api_request',
|
|
2280
|
-
},
|
|
2281
|
-
{
|
|
2282
|
-
type: 'editor',
|
|
2283
|
-
name: 'migrationPlan',
|
|
2284
|
-
message: '🔄 Migration Plan (one per line, ESC to finish):',
|
|
2285
|
-
default: (answers) => {
|
|
2286
|
-
if (answers.dataMigration) {
|
|
2287
|
-
return '- Create new database schema\n- Add new auth table\n- Migrate existing users\n- Validate data integrity';
|
|
2288
|
-
}
|
|
2289
|
-
return '- Deploy feature flags\n- Roll out gradually\n- Monitor metrics';
|
|
2290
|
-
},
|
|
2291
|
-
validate: (input) => {
|
|
2292
|
-
if (!input.trim()) return 'Migration plan is required';
|
|
2293
|
-
return true;
|
|
2294
|
-
},
|
|
2295
|
-
},
|
|
2296
|
-
{
|
|
2297
|
-
type: 'editor',
|
|
2298
|
-
name: 'rollbackPlan',
|
|
2299
|
-
message: '🔙 Rollback Plan (one per line, ESC to finish):',
|
|
2300
|
-
default: (answers) => {
|
|
2301
|
-
if (answers.dataMigration) {
|
|
2302
|
-
return '- Feature flag kill-switch\n- Database rollback script\n- Restore from backup\n- Verify system state';
|
|
2303
|
-
}
|
|
2304
|
-
return '- Feature flag disable\n- Deploy previous version\n- Monitor for issues';
|
|
2305
|
-
},
|
|
2306
|
-
validate: (input) => {
|
|
2307
|
-
if (!input.trim()) return 'Rollback plan is required';
|
|
2308
|
-
return true;
|
|
2309
|
-
},
|
|
2310
|
-
},
|
|
2311
|
-
{
|
|
2312
|
-
type: 'confirm',
|
|
2313
|
-
name: 'needsOverride',
|
|
2314
|
-
message: '🚨 Need human override for urgent/low-risk change?',
|
|
2315
|
-
default: false,
|
|
2316
|
-
},
|
|
2317
|
-
{
|
|
2318
|
-
type: 'input',
|
|
2319
|
-
name: 'overrideRationale',
|
|
2320
|
-
message: '📝 Override rationale (urgency, low risk, etc.):',
|
|
2321
|
-
when: (answers) => answers.needsOverride,
|
|
2322
|
-
validate: (input) => {
|
|
2323
|
-
if (!input.trim()) return 'Rationale is required for override';
|
|
2324
|
-
return true;
|
|
2325
|
-
},
|
|
2326
|
-
},
|
|
2327
|
-
{
|
|
2328
|
-
type: 'input',
|
|
2329
|
-
name: 'overrideApprover',
|
|
2330
|
-
message: '👤 Override approver (GitHub username or email):',
|
|
2331
|
-
when: (answers) => answers.needsOverride,
|
|
2332
|
-
validate: (input) => {
|
|
2333
|
-
if (!input.trim()) return 'Approver is required for override';
|
|
2334
|
-
return true;
|
|
2335
|
-
},
|
|
2336
|
-
},
|
|
2337
|
-
{
|
|
2338
|
-
type: 'checkbox',
|
|
2339
|
-
name: 'waivedGates',
|
|
2340
|
-
message: '⚠️ Gates to waive (select with space):',
|
|
2341
|
-
choices: [
|
|
2342
|
-
{ name: 'Coverage testing', value: 'coverage' },
|
|
2343
|
-
{ name: 'Mutation testing', value: 'mutation' },
|
|
2344
|
-
{ name: 'Contract testing', value: 'contracts' },
|
|
2345
|
-
{ name: 'Manual review', value: 'manual_review' },
|
|
2346
|
-
{ name: 'Trust score check', value: 'trust_score' },
|
|
2347
|
-
],
|
|
2348
|
-
when: (answers) => answers.needsOverride,
|
|
2349
|
-
validate: (input) => {
|
|
2350
|
-
if (input.length === 0) return 'At least one gate must be waived';
|
|
2351
|
-
return true;
|
|
2352
|
-
},
|
|
2353
|
-
},
|
|
2354
|
-
{
|
|
2355
|
-
type: 'number',
|
|
2356
|
-
name: 'overrideExpiresDays',
|
|
2357
|
-
message: '⏰ Override expires in how many days?',
|
|
2358
|
-
default: 7,
|
|
2359
|
-
when: (answers) => answers.needsOverride,
|
|
2360
|
-
validate: (input) => {
|
|
2361
|
-
if (input < 1) return 'Must expire in at least 1 day';
|
|
2362
|
-
if (input > 30) return 'Cannot exceed 30 days';
|
|
2363
|
-
return true;
|
|
2364
|
-
},
|
|
2365
|
-
},
|
|
2366
|
-
{
|
|
2367
|
-
type: 'confirm',
|
|
2368
|
-
name: 'isExperimental',
|
|
2369
|
-
message: '🧪 Experimental/Prototype mode? (Reduced requirements for sandbox code)',
|
|
2370
|
-
default: false,
|
|
2371
|
-
},
|
|
2372
|
-
{
|
|
2373
|
-
type: 'input',
|
|
2374
|
-
name: 'experimentalRationale',
|
|
2375
|
-
message: '🔬 Experimental rationale (what are you exploring?):',
|
|
2376
|
-
when: (answers) => answers.isExperimental,
|
|
2377
|
-
validate: (input) => {
|
|
2378
|
-
if (!input.trim()) return 'Rationale is required for experimental mode';
|
|
2379
|
-
return true;
|
|
2380
|
-
},
|
|
2381
|
-
},
|
|
2382
|
-
{
|
|
2383
|
-
type: 'input',
|
|
2384
|
-
name: 'experimentalSandbox',
|
|
2385
|
-
message: '📁 Sandbox location (directory or feature flag):',
|
|
2386
|
-
default: 'experimental/',
|
|
2387
|
-
when: (answers) => answers.isExperimental,
|
|
2388
|
-
validate: (input) => {
|
|
2389
|
-
if (!input.trim()) return 'Sandbox location is required';
|
|
2390
|
-
return true;
|
|
2391
|
-
},
|
|
2392
|
-
},
|
|
2393
|
-
{
|
|
2394
|
-
type: 'number',
|
|
2395
|
-
name: 'experimentalExpiresDays',
|
|
2396
|
-
message: '⏰ Experimental code expires in how many days?',
|
|
2397
|
-
default: 14,
|
|
2398
|
-
when: (answers) => answers.isExperimental,
|
|
2399
|
-
validate: (input) => {
|
|
2400
|
-
if (input < 1) return 'Must expire in at least 1 day';
|
|
2401
|
-
if (input > 90) return 'Cannot exceed 90 days for experimental code';
|
|
2402
|
-
return true;
|
|
2403
|
-
},
|
|
2404
|
-
},
|
|
2405
|
-
{
|
|
2406
|
-
type: 'number',
|
|
2407
|
-
name: 'aiConfidence',
|
|
2408
|
-
message: '🤖 AI confidence level (1-10, 10 = very confident):',
|
|
2409
|
-
default: 7,
|
|
2410
|
-
validate: (input) => {
|
|
2411
|
-
if (input < 1 || input > 10) return 'Must be between 1 and 10';
|
|
2412
|
-
return true;
|
|
2413
|
-
},
|
|
2414
|
-
},
|
|
2415
|
-
{
|
|
2416
|
-
type: 'input',
|
|
2417
|
-
name: 'uncertaintyAreas',
|
|
2418
|
-
message: '❓ Areas of uncertainty (comma-separated):',
|
|
2419
|
-
default: '',
|
|
2420
|
-
},
|
|
2421
|
-
{
|
|
2422
|
-
type: 'input',
|
|
2423
|
-
name: 'complexityFactors',
|
|
2424
|
-
message: '🔧 Complexity factors (comma-separated):',
|
|
2425
|
-
default: '',
|
|
2426
|
-
},
|
|
2427
|
-
];
|
|
2428
|
-
|
|
2429
|
-
console.log(chalk.cyan('⏳ Gathering project requirements...'));
|
|
2430
|
-
|
|
2431
|
-
let answers;
|
|
2432
|
-
try {
|
|
2433
|
-
answers = await inquirer.prompt(questions);
|
|
2434
|
-
} catch (error) {
|
|
2435
|
-
if (error.isTtyError) {
|
|
2436
|
-
console.error(chalk.red('❌ Interactive prompts not supported in this environment'));
|
|
2437
|
-
console.error(chalk.blue('💡 Run with --non-interactive flag to use defaults'));
|
|
2438
|
-
process.exit(1);
|
|
2439
|
-
} else {
|
|
2440
|
-
console.error(chalk.red('❌ Error during interactive setup:'), error.message);
|
|
2441
|
-
process.exit(1);
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
console.log(chalk.green('✅ Project requirements gathered successfully!'));
|
|
2446
|
-
|
|
2447
|
-
// Show summary before generating spec
|
|
2448
|
-
console.log(chalk.bold('\n📋 Configuration Summary:'));
|
|
2449
|
-
console.log(` ${chalk.cyan('Project')}: ${answers.projectTitle} (${answers.projectId})`);
|
|
2450
|
-
console.log(
|
|
2451
|
-
` ${chalk.cyan('Mode')}: ${answers.projectMode} | ${chalk.cyan('Tier')}: ${answers.riskTier}`
|
|
2452
|
-
);
|
|
2453
|
-
console.log(` ${chalk.cyan('Budget')}: ${answers.maxFiles} files, ${answers.maxLoc} lines`);
|
|
2454
|
-
console.log(` ${chalk.cyan('Data Migration')}: ${answers.dataMigration ? 'Yes' : 'No'}`);
|
|
2455
|
-
console.log(` ${chalk.cyan('Rollback SLO')}: ${answers.rollbackSlo}`);
|
|
2456
|
-
|
|
2457
|
-
// Generate working spec
|
|
2458
|
-
const workingSpecContent = generateWorkingSpec(answers);
|
|
2459
|
-
|
|
2460
|
-
// Validate the generated spec
|
|
2461
|
-
validateGeneratedSpec(workingSpecContent, answers);
|
|
2462
|
-
|
|
2463
|
-
// Save the working spec
|
|
2464
|
-
await fs.writeFile('.caws/working-spec.yaml', workingSpecContent);
|
|
2465
|
-
|
|
2466
|
-
console.log(chalk.green('✅ Working spec generated and validated'));
|
|
2467
|
-
|
|
2468
|
-
// Finalize project with provenance and git initialization
|
|
2469
|
-
await finalizeProject(projectName, options, answers);
|
|
2470
|
-
|
|
2471
|
-
continueToSuccess();
|
|
2472
|
-
}
|
|
2473
|
-
} catch (error) {
|
|
2474
|
-
console.error(chalk.red('❌ Error during project initialization:'), error.message);
|
|
2475
|
-
|
|
2476
|
-
// Cleanup on error
|
|
2477
|
-
if (fs.existsSync(projectName)) {
|
|
2478
|
-
console.log(chalk.cyan('🧹 Cleaning up failed initialization...'));
|
|
2479
|
-
try {
|
|
2480
|
-
await fs.remove(projectName);
|
|
2481
|
-
console.log(chalk.green('✅ Cleanup completed'));
|
|
2482
|
-
} catch (cleanupError) {
|
|
2483
|
-
console.warn(
|
|
2484
|
-
chalk.yellow('⚠️ Could not clean up:'),
|
|
2485
|
-
cleanupError?.message || cleanupError
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
process.exit(1);
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
// Generate provenance manifest and git initialization (for both modes)
|
|
2495
|
-
async function finalizeProject(projectName, options, answers) {
|
|
2496
|
-
try {
|
|
2497
|
-
// Detect and configure language support
|
|
2498
|
-
if (languageSupport) {
|
|
2499
|
-
console.log(chalk.cyan('🔍 Detecting project language...'));
|
|
2500
|
-
const detectedLanguage = languageSupport.detectProjectLanguage();
|
|
2501
|
-
|
|
2502
|
-
if (detectedLanguage !== 'unknown') {
|
|
2503
|
-
console.log(chalk.green(`✅ Detected language: ${detectedLanguage}`));
|
|
2504
|
-
|
|
2505
|
-
// Generate language-specific configuration
|
|
2506
|
-
try {
|
|
2507
|
-
const langConfig = languageSupport.generateLanguageConfig(
|
|
2508
|
-
detectedLanguage,
|
|
2509
|
-
'.caws/language-config.json'
|
|
2510
|
-
);
|
|
2511
|
-
|
|
2512
|
-
console.log(chalk.green('✅ Generated language-specific configuration'));
|
|
2513
|
-
console.log(` Language: ${langConfig.name}`);
|
|
2514
|
-
console.log(` Tier: ${langConfig.tier}`);
|
|
2515
|
-
console.log(
|
|
2516
|
-
` Thresholds: Branch ≥${langConfig.thresholds.min_branch * 100}%, Mutation ≥${langConfig.thresholds.min_mutation * 100}%`
|
|
2517
|
-
);
|
|
2518
|
-
} catch (langError) {
|
|
2519
|
-
console.warn(chalk.yellow('⚠️ Could not generate language config:'), langError.message);
|
|
2520
|
-
}
|
|
2521
|
-
} else {
|
|
2522
|
-
console.log(
|
|
2523
|
-
chalk.blue('ℹ️ Could not detect project language - using default configuration')
|
|
2524
|
-
);
|
|
2525
|
-
}
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
// Generate provenance manifest
|
|
2529
|
-
console.log(chalk.cyan('📦 Generating provenance manifest...'));
|
|
2530
|
-
|
|
2531
|
-
const provenanceData = {
|
|
2532
|
-
agent: 'caws-cli',
|
|
2533
|
-
model: 'cli-interactive',
|
|
2534
|
-
modelHash: CLI_VERSION,
|
|
2535
|
-
toolAllowlist: [
|
|
2536
|
-
'node',
|
|
2537
|
-
'npm',
|
|
2538
|
-
'git',
|
|
2539
|
-
'fs-extra',
|
|
2540
|
-
'inquirer',
|
|
2541
|
-
'commander',
|
|
2542
|
-
'js-yaml',
|
|
2543
|
-
'ajv',
|
|
2544
|
-
'chalk',
|
|
2545
|
-
],
|
|
2546
|
-
prompts: Object.keys(answers),
|
|
2547
|
-
commit: null, // Will be set after git init
|
|
2548
|
-
artifacts: ['.caws/working-spec.yaml'],
|
|
2549
|
-
results: {
|
|
2550
|
-
project_id: answers.projectId,
|
|
2551
|
-
project_title: answers.projectTitle,
|
|
2552
|
-
risk_tier: answers.riskTier,
|
|
2553
|
-
mode: answers.projectMode,
|
|
2554
|
-
change_budget: {
|
|
2555
|
-
max_files: answers.maxFiles,
|
|
2556
|
-
max_loc: answers.maxLoc,
|
|
2557
|
-
},
|
|
2558
|
-
},
|
|
2559
|
-
approvals: [],
|
|
2560
|
-
};
|
|
2561
|
-
|
|
2562
|
-
// Generate provenance if tools are available
|
|
2563
|
-
const tools = loadProvenanceTools();
|
|
2564
|
-
if (
|
|
2565
|
-
tools &&
|
|
2566
|
-
typeof tools.generateProvenance === 'function' &&
|
|
2567
|
-
typeof tools.saveProvenance === 'function'
|
|
2568
|
-
) {
|
|
2569
|
-
const provenance = tools.generateProvenance(provenanceData);
|
|
2570
|
-
await tools.saveProvenance(provenance, '.agent/provenance.json');
|
|
2571
|
-
console.log(chalk.green('✅ Provenance manifest generated'));
|
|
2572
|
-
} else {
|
|
2573
|
-
console.log(
|
|
2574
|
-
chalk.yellow('⚠️ Provenance tools not available - skipping manifest generation')
|
|
2575
|
-
);
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
// Initialize git repository
|
|
2579
|
-
if (options.git) {
|
|
2580
|
-
try {
|
|
2581
|
-
console.log(chalk.cyan('🔧 Initializing git repository...'));
|
|
2582
|
-
|
|
2583
|
-
// Check if git is available
|
|
2584
|
-
try {
|
|
2585
|
-
require('child_process').execSync('git --version', { stdio: 'ignore' });
|
|
2586
|
-
} catch (error) {
|
|
2587
|
-
console.warn(chalk.yellow('⚠️ Git not found. Skipping git initialization.'));
|
|
2588
|
-
console.warn(chalk.blue('💡 Install git to enable automatic repository setup.'));
|
|
2589
|
-
return;
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
require('child_process').execSync('git init', { stdio: 'inherit' });
|
|
2593
|
-
require('child_process').execSync('git add .', { stdio: 'inherit' });
|
|
2594
|
-
require('child_process').execSync('git commit -m "Initial CAWS project setup"', {
|
|
2595
|
-
stdio: 'inherit',
|
|
2596
|
-
});
|
|
2597
|
-
console.log(chalk.green('✅ Git repository initialized'));
|
|
2598
|
-
|
|
2599
|
-
// Update provenance with commit hash
|
|
2600
|
-
const commitHash = require('child_process')
|
|
2601
|
-
.execSync('git rev-parse HEAD', { encoding: 'utf8' })
|
|
2602
|
-
.trim();
|
|
2603
|
-
const currentProvenance = JSON.parse(fs.readFileSync('.agent/provenance.json', 'utf8'));
|
|
2604
|
-
currentProvenance.commit = commitHash;
|
|
2605
|
-
currentProvenance.hash = require('crypto')
|
|
2606
|
-
.createHash('sha256')
|
|
2607
|
-
.update(JSON.stringify(currentProvenance, Object.keys(currentProvenance).sort()))
|
|
2608
|
-
.digest('hex');
|
|
2609
|
-
await fs.writeFile('.agent/provenance.json', JSON.stringify(currentProvenance, null, 2));
|
|
2610
|
-
|
|
2611
|
-
console.log(chalk.green('✅ Provenance updated with commit hash'));
|
|
2612
|
-
} catch (error) {
|
|
2613
|
-
console.warn(
|
|
2614
|
-
chalk.yellow('⚠️ Failed to initialize git repository:'),
|
|
2615
|
-
error?.message || String(error)
|
|
2616
|
-
);
|
|
2617
|
-
console.warn(chalk.blue('💡 You can initialize git manually later with:'));
|
|
2618
|
-
console.warn(" git init && git add . && git commit -m 'Initial CAWS project setup'");
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
} catch (error) {
|
|
2622
|
-
console.error(
|
|
2623
|
-
chalk.red('❌ Error during project finalization:'),
|
|
2624
|
-
error?.message || String(error)
|
|
2625
|
-
);
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
function continueToSuccess() {
|
|
2630
|
-
const isCurrentDir =
|
|
2631
|
-
process.cwd() ===
|
|
2632
|
-
path.resolve(process.argv[3] === '.' ? process.cwd() : process.argv[3] || 'caws-project');
|
|
2633
|
-
|
|
2634
|
-
console.log(chalk.green('\n🎉 CAWS project initialized successfully!'));
|
|
2635
|
-
|
|
2636
|
-
if (isCurrentDir) {
|
|
2637
|
-
console.log(
|
|
2638
|
-
`📁 ${chalk.cyan('Initialized in current directory')}: ${path.resolve(process.cwd())}`
|
|
2639
|
-
);
|
|
2640
|
-
console.log(chalk.gray(' (CAWS files added to your existing project)'));
|
|
2641
|
-
} else {
|
|
2642
|
-
console.log(`📁 ${chalk.cyan('Project location')}: ${path.resolve(process.cwd())}`);
|
|
2643
|
-
console.log(chalk.gray(' (New subdirectory created with CAWS structure)'));
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
console.log(chalk.bold('\nNext steps:'));
|
|
2647
|
-
console.log('1. Customize .caws/working-spec.yaml');
|
|
2648
|
-
console.log('2. Review added CAWS tools and documentation');
|
|
2649
|
-
if (!isCurrentDir) {
|
|
2650
|
-
console.log('3. Move CAWS files to your main project if needed');
|
|
2651
|
-
}
|
|
2652
|
-
console.log('4. npm install (if using Node.js)');
|
|
2653
|
-
console.log('5. Set up your CI/CD pipeline');
|
|
2654
|
-
console.log(chalk.blue('\nFor help: caws --help'));
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
/**
|
|
2658
|
-
* Scaffold existing project with CAWS components
|
|
2659
|
-
*/
|
|
2660
|
-
async function scaffoldProject(options) {
|
|
2661
|
-
const currentDir = process.cwd();
|
|
2662
|
-
const projectName = path.basename(currentDir);
|
|
2663
|
-
|
|
2664
|
-
try {
|
|
2665
|
-
// Detect existing CAWS setup FIRST before any logging
|
|
2666
|
-
const setup = detectCAWSSetup(currentDir);
|
|
2667
|
-
|
|
2668
|
-
// Check for CAWS setup immediately and exit with helpful message if not found
|
|
2669
|
-
if (!setup.hasCAWSDir) {
|
|
2670
|
-
console.log(chalk.red('❌ CAWS not initialized in this project'));
|
|
2671
|
-
console.log(chalk.blue('\n💡 To get started:'));
|
|
2672
|
-
console.log(` 1. Initialize CAWS: ${chalk.cyan('caws init <project-name>')}`);
|
|
2673
|
-
console.log(` 2. Or initialize in current directory: ${chalk.cyan('caws init .')}`);
|
|
2674
|
-
console.log(chalk.blue('\n📚 For more help:'));
|
|
2675
|
-
console.log(` ${chalk.cyan('caws --help')}`);
|
|
2676
|
-
process.exit(1);
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
console.log(chalk.cyan(`🔧 Enhancing existing CAWS project: ${projectName}`));
|
|
2680
|
-
|
|
2681
|
-
// Preserve the original template directory from global cawsSetup
|
|
2682
|
-
// (needed because detectCAWSSetup from within a new project won't find the template)
|
|
2683
|
-
if (cawsSetup?.templateDir && !setup.templateDir) {
|
|
2684
|
-
setup.templateDir = cawsSetup.templateDir;
|
|
2685
|
-
setup.hasTemplateDir = true;
|
|
2686
|
-
} else if (!setup.templateDir) {
|
|
2687
|
-
// Try to find template directory using absolute paths that work in CI
|
|
2688
|
-
const possiblePaths = [
|
|
2689
|
-
'/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
|
|
2690
|
-
'/workspace/packages/caws-template',
|
|
2691
|
-
'/caws/packages/caws-template',
|
|
2692
|
-
path.resolve(process.cwd(), '../../../packages/caws-template'),
|
|
2693
|
-
path.resolve(process.cwd(), '../../packages/caws-template'),
|
|
2694
|
-
path.resolve(process.cwd(), '../packages/caws-template'),
|
|
2695
|
-
];
|
|
2696
|
-
|
|
2697
|
-
for (const testPath of possiblePaths) {
|
|
2698
|
-
if (fs.existsSync(testPath)) {
|
|
2699
|
-
setup.templateDir = testPath;
|
|
2700
|
-
setup.hasTemplateDir = true;
|
|
2701
|
-
break;
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
|
|
2705
|
-
if (!setup.templateDir) {
|
|
2706
|
-
console.log(chalk.red(`❌ No template directory available!`));
|
|
2707
|
-
console.log(chalk.blue('💡 To fix this issue:'));
|
|
2708
|
-
console.log(` 1. Ensure caws-template package is installed`);
|
|
2709
|
-
console.log(` 2. Run from the monorepo root directory`);
|
|
2710
|
-
console.log(` 3. Check that CAWS CLI was installed correctly`);
|
|
2711
|
-
console.log(chalk.blue('\n📚 For installation help:'));
|
|
2712
|
-
console.log(` ${chalk.cyan('npm install -g @paths.design/caws-cli')}`);
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2716
|
-
// Override global cawsSetup with current context for scaffold operations
|
|
2717
|
-
cawsSetup = setup;
|
|
2718
|
-
|
|
2719
|
-
if (!setup.hasCAWSDir) {
|
|
2720
|
-
console.error(chalk.red('❌ No .caws directory found'));
|
|
2721
|
-
console.error(chalk.blue('💡 Run "caws init <project-name>" first to create a CAWS project'));
|
|
2722
|
-
process.exit(1);
|
|
2723
|
-
}
|
|
2724
|
-
|
|
2725
|
-
// Adapt behavior based on setup type
|
|
2726
|
-
if (setup.isEnhanced) {
|
|
2727
|
-
console.log(chalk.green('🎯 Enhanced CAWS detected - adding automated publishing'));
|
|
2728
|
-
} else if (setup.isAdvanced) {
|
|
2729
|
-
console.log(chalk.blue('🔧 Advanced CAWS detected - adding missing capabilities'));
|
|
2730
|
-
} else {
|
|
2731
|
-
console.log(chalk.blue('📋 Basic CAWS detected - enhancing with additional tools'));
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2734
|
-
// Generate provenance for scaffolding operation
|
|
2735
|
-
const scaffoldProvenance = {
|
|
2736
|
-
agent: 'caws-cli',
|
|
2737
|
-
model: 'cli-scaffold',
|
|
2738
|
-
modelHash: CLI_VERSION,
|
|
2739
|
-
toolAllowlist: ['node', 'fs-extra'],
|
|
2740
|
-
prompts: ['scaffold', options.force ? 'force' : 'normal'],
|
|
2741
|
-
commit: null,
|
|
2742
|
-
artifacts: [],
|
|
2743
|
-
results: {
|
|
2744
|
-
operation: 'scaffold',
|
|
2745
|
-
force_mode: options.force,
|
|
2746
|
-
target_directory: currentDir,
|
|
2747
|
-
},
|
|
2748
|
-
approvals: [],
|
|
2749
|
-
timestamp: new Date().toISOString(),
|
|
2750
|
-
version: CLI_VERSION,
|
|
2751
|
-
};
|
|
2752
|
-
|
|
2753
|
-
// Calculate hash after object is fully defined
|
|
2754
|
-
scaffoldProvenance.hash = require('crypto')
|
|
2755
|
-
.createHash('sha256')
|
|
2756
|
-
.update(JSON.stringify(scaffoldProvenance))
|
|
2757
|
-
.digest('hex');
|
|
2758
|
-
|
|
2759
|
-
// Determine what enhancements to add based on setup type and options
|
|
2760
|
-
const enhancements = [];
|
|
2761
|
-
|
|
2762
|
-
// Add CAWS tools directory structure (matches test expectations)
|
|
2763
|
-
enhancements.push({
|
|
2764
|
-
name: 'apps/tools/caws',
|
|
2765
|
-
description: 'CAWS tools directory',
|
|
2766
|
-
required: true,
|
|
2767
|
-
});
|
|
2768
|
-
|
|
2769
|
-
// Add codemods if requested or not minimal
|
|
2770
|
-
if (options.withCodemods || (!options.minimal && !options.withCodemods)) {
|
|
2771
|
-
enhancements.push({
|
|
2772
|
-
name: 'codemod',
|
|
2773
|
-
description: 'Codemod transformation scripts',
|
|
2774
|
-
required: true,
|
|
2775
|
-
});
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
|
-
// Also add automated publishing for enhanced setups
|
|
2779
|
-
if (setup.isEnhanced) {
|
|
2780
|
-
enhancements.push({
|
|
2781
|
-
name: '.github/workflows/release.yml',
|
|
2782
|
-
description: 'GitHub Actions workflow for automated publishing',
|
|
2783
|
-
required: true,
|
|
2784
|
-
});
|
|
2785
|
-
|
|
2786
|
-
enhancements.push({
|
|
2787
|
-
name: '.releaserc.json',
|
|
2788
|
-
description: 'semantic-release configuration',
|
|
2789
|
-
required: true,
|
|
2790
|
-
});
|
|
2791
|
-
}
|
|
2792
|
-
|
|
2793
|
-
// Add commit conventions for setups that don't have them
|
|
2794
|
-
if (!setup.hasTemplates || !fs.existsSync(path.join(currentDir, 'COMMIT_CONVENTIONS.md'))) {
|
|
2795
|
-
enhancements.push({
|
|
2796
|
-
name: 'COMMIT_CONVENTIONS.md',
|
|
2797
|
-
description: 'Commit message guidelines',
|
|
2798
|
-
required: false,
|
|
2799
|
-
});
|
|
2800
|
-
}
|
|
2801
|
-
|
|
2802
|
-
// Add OIDC setup guide if requested or not minimal
|
|
2803
|
-
if (
|
|
2804
|
-
(options.withOidc || (!options.minimal && !options.withOidc)) &&
|
|
2805
|
-
(!setup.isEnhanced || !fs.existsSync(path.join(currentDir, 'OIDC_SETUP.md')))
|
|
2806
|
-
) {
|
|
2807
|
-
enhancements.push({
|
|
2808
|
-
name: 'OIDC_SETUP.md',
|
|
2809
|
-
description: 'OIDC trusted publisher setup guide',
|
|
2810
|
-
required: false,
|
|
2811
|
-
});
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
// For enhanced setups, preserve existing tools
|
|
2815
|
-
if (setup.isEnhanced) {
|
|
2816
|
-
console.log(chalk.blue('ℹ️ Preserving existing sophisticated CAWS tools'));
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
|
-
let addedCount = 0;
|
|
2820
|
-
let skippedCount = 0;
|
|
2821
|
-
const addedFiles = [];
|
|
2822
|
-
|
|
2823
|
-
for (const enhancement of enhancements) {
|
|
2824
|
-
if (!setup?.templateDir) {
|
|
2825
|
-
console.warn(
|
|
2826
|
-
chalk.yellow(`⚠️ Template directory not available for enhancement: ${enhancement.name}`)
|
|
2827
|
-
);
|
|
2828
|
-
continue;
|
|
2829
|
-
}
|
|
2830
|
-
const sourcePath = path.join(setup.templateDir, enhancement.name);
|
|
2831
|
-
const destPath = path.join(currentDir, enhancement.name);
|
|
2832
|
-
|
|
2833
|
-
if (!fs.existsSync(destPath)) {
|
|
2834
|
-
if (fs.existsSync(sourcePath)) {
|
|
2835
|
-
try {
|
|
2836
|
-
await fs.copy(sourcePath, destPath);
|
|
2837
|
-
console.log(chalk.green(`✅ Added ${enhancement.description}`));
|
|
2838
|
-
addedCount++;
|
|
2839
|
-
addedFiles.push(enhancement.name);
|
|
2840
|
-
} catch (copyError) {
|
|
2841
|
-
console.warn(chalk.yellow(`⚠️ Failed to add ${enhancement.name}:`), copyError.message);
|
|
2842
|
-
}
|
|
2843
|
-
} else {
|
|
2844
|
-
// If source doesn't exist in template, create the directory structure
|
|
2845
|
-
try {
|
|
2846
|
-
await fs.ensureDir(destPath);
|
|
2847
|
-
console.log(chalk.green(`✅ Created ${enhancement.description}`));
|
|
2848
|
-
addedCount++;
|
|
2849
|
-
addedFiles.push(enhancement.name);
|
|
2850
|
-
} catch (createError) {
|
|
2851
|
-
console.warn(
|
|
2852
|
-
chalk.yellow(`⚠️ Failed to create ${enhancement.name}:`),
|
|
2853
|
-
createError.message
|
|
2854
|
-
);
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
} else {
|
|
2858
|
-
if (options.force) {
|
|
2859
|
-
try {
|
|
2860
|
-
await fs.remove(destPath);
|
|
2861
|
-
if (fs.existsSync(sourcePath)) {
|
|
2862
|
-
await fs.copy(sourcePath, destPath);
|
|
2863
|
-
} else {
|
|
2864
|
-
await fs.ensureDir(destPath);
|
|
2865
|
-
}
|
|
2866
|
-
console.log(chalk.blue(`🔄 Updated ${enhancement.description}`));
|
|
2867
|
-
addedCount++;
|
|
2868
|
-
addedFiles.push(enhancement.name);
|
|
2869
|
-
} catch (overwriteError) {
|
|
2870
|
-
console.warn(
|
|
2871
|
-
chalk.yellow(`⚠️ Failed to update ${enhancement.name}:`),
|
|
2872
|
-
overwriteError.message
|
|
2873
|
-
);
|
|
2874
|
-
}
|
|
2875
|
-
} else {
|
|
2876
|
-
console.log(`⏭️ Skipped ${enhancement.name} (already exists)`);
|
|
2877
|
-
skippedCount++;
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
|
-
// Update provenance with results
|
|
2883
|
-
scaffoldProvenance.artifacts = addedFiles;
|
|
2884
|
-
scaffoldProvenance.results.files_added = addedCount;
|
|
2885
|
-
scaffoldProvenance.results.files_skipped = skippedCount;
|
|
2886
|
-
|
|
2887
|
-
// Show summary
|
|
2888
|
-
console.log(chalk.green(`\n🎉 Enhancement completed!`));
|
|
2889
|
-
console.log(chalk.bold(`📊 Summary: ${addedCount} added, ${skippedCount} skipped`));
|
|
2890
|
-
|
|
2891
|
-
if (addedCount > 0) {
|
|
2892
|
-
console.log(chalk.bold('\n📝 Next steps:'));
|
|
2893
|
-
console.log('1. Review the added files');
|
|
2894
|
-
|
|
2895
|
-
// Check if OIDC was added
|
|
2896
|
-
const oidcAdded = addedFiles.some((file) => file.includes('OIDC_SETUP'));
|
|
2897
|
-
if (oidcAdded) {
|
|
2898
|
-
console.log('2. Set up OIDC trusted publisher (see OIDC_SETUP.md)');
|
|
2899
|
-
console.log('3. Push to trigger automated publishing');
|
|
2900
|
-
console.log('4. Your existing CAWS tools remain unchanged');
|
|
2901
|
-
} else {
|
|
2902
|
-
console.log('2. Customize your working spec in .caws/working-spec.yaml');
|
|
2903
|
-
console.log('3. Run validation: caws validate --suggestions');
|
|
2904
|
-
console.log('4. Your existing CAWS tools remain unchanged');
|
|
2905
|
-
}
|
|
2906
|
-
}
|
|
2907
|
-
|
|
2908
|
-
if (setup.isEnhanced) {
|
|
2909
|
-
console.log(
|
|
2910
|
-
chalk.blue('\n🎯 Your enhanced CAWS setup has been improved with automated publishing!')
|
|
2911
|
-
);
|
|
2912
|
-
}
|
|
18
|
+
// Import configuration and utilities
|
|
19
|
+
const {
|
|
20
|
+
CLI_VERSION,
|
|
21
|
+
initializeGlobalSetup,
|
|
22
|
+
loadProvenanceTools,
|
|
23
|
+
initializeLanguageSupport,
|
|
24
|
+
} = require('./config');
|
|
25
|
+
|
|
26
|
+
// Import command handlers
|
|
27
|
+
const { initProject } = require('./commands/init');
|
|
28
|
+
const { validateCommand } = require('./commands/validate');
|
|
29
|
+
const { burnupCommand } = require('./commands/burnup');
|
|
30
|
+
const { testAnalysisCommand } = require('./test-analysis');
|
|
31
|
+
const { provenanceCommand } = require('./commands/provenance');
|
|
32
|
+
const { executeTool } = require('./commands/tool');
|
|
33
|
+
|
|
34
|
+
// Import scaffold functionality
|
|
35
|
+
const { scaffoldProject, setScaffoldDependencies } = require('./scaffold');
|
|
36
|
+
|
|
37
|
+
// Import git hooks functionality
|
|
38
|
+
const { scaffoldGitHooks, removeGitHooks, checkGitHooksStatus } = require('./scaffold/git-hooks');
|
|
39
|
+
|
|
40
|
+
// Import validation functionality
|
|
41
|
+
// eslint-disable-next-line no-unused-vars
|
|
42
|
+
const { validateWorkingSpecWithSuggestions } = require('./validation/spec-validation');
|
|
43
|
+
|
|
44
|
+
// Import finalization utilities
|
|
45
|
+
const {
|
|
46
|
+
// eslint-disable-next-line no-unused-vars
|
|
47
|
+
finalizeProject,
|
|
48
|
+
// eslint-disable-next-line no-unused-vars
|
|
49
|
+
continueToSuccess,
|
|
50
|
+
setFinalizationDependencies,
|
|
51
|
+
} = require('./utils/finalization');
|
|
52
|
+
|
|
53
|
+
// Import generators
|
|
54
|
+
const { generateWorkingSpec, validateGeneratedSpec } = require('./generators/working-spec');
|
|
55
|
+
|
|
56
|
+
// Initialize global configuration
|
|
57
|
+
const program = new Command();
|
|
2913
58
|
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
59
|
+
// Initialize global state
|
|
60
|
+
const cawsSetup = initializeGlobalSetup();
|
|
61
|
+
const languageSupport = initializeLanguageSupport();
|
|
2917
62
|
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
} else {
|
|
2924
|
-
console.log(chalk.yellow('⚠️ Provenance tools not available - skipping manifest save'));
|
|
2925
|
-
}
|
|
2926
|
-
} catch (error) {
|
|
2927
|
-
// Handle circular reference errors from Commander.js
|
|
2928
|
-
if (error.message && error.message.includes('Converting circular structure to JSON')) {
|
|
2929
|
-
console.log(
|
|
2930
|
-
chalk.yellow('⚠️ Scaffolding completed with minor issues (circular reference handled)')
|
|
2931
|
-
);
|
|
2932
|
-
console.log(chalk.green('✅ CAWS components scaffolded successfully'));
|
|
2933
|
-
} else {
|
|
2934
|
-
console.error(chalk.red('❌ Error during scaffolding:'), error.message);
|
|
2935
|
-
process.exit(1);
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
63
|
+
// Set up dependencies for modules that need them
|
|
64
|
+
setScaffoldDependencies({
|
|
65
|
+
cawsSetup,
|
|
66
|
+
loadProvenanceTools,
|
|
67
|
+
});
|
|
2939
68
|
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
// console.log(chalk.bold(`CAWS CLI v${CLI_VERSION}`));
|
|
2945
|
-
// console.log(chalk.cyan('Coding Agent Workflow System - Scaffolding Tool'));
|
|
2946
|
-
// console.log(chalk.gray('Author: @darianrosebrook'));
|
|
2947
|
-
// console.log(chalk.gray('License: MIT'));
|
|
2948
|
-
// }
|
|
69
|
+
setFinalizationDependencies({
|
|
70
|
+
languageSupport,
|
|
71
|
+
loadProvenanceTools,
|
|
72
|
+
});
|
|
2949
73
|
|
|
2950
|
-
// CLI
|
|
2951
|
-
program
|
|
2952
|
-
.name('caws')
|
|
2953
|
-
.description('CAWS - Coding Agent Workflow System CLI')
|
|
2954
|
-
.version(CLI_VERSION, '-v, --version', 'Show version information');
|
|
74
|
+
// Setup CLI program
|
|
75
|
+
program.name('caws').description('CAWS - Coding Agent Workflow System CLI').version(CLI_VERSION);
|
|
2955
76
|
|
|
77
|
+
// Init command
|
|
2956
78
|
program
|
|
2957
79
|
.command('init')
|
|
2958
|
-
.alias('i')
|
|
2959
80
|
.description('Initialize a new project with CAWS')
|
|
2960
|
-
.argument('
|
|
2961
|
-
.option('-i, --interactive', 'Run interactive setup wizard')
|
|
2962
|
-
.option('-
|
|
2963
|
-
.option('
|
|
2964
|
-
.option('--no-git', "Don't initialize git repository")
|
|
2965
|
-
.option('-t, --template <type>', 'Use project template (extension|library|api|cli|monorepo)')
|
|
81
|
+
.argument('[project-name]', 'Name of the project to create (use "." for current directory)')
|
|
82
|
+
.option('-i, --interactive', 'Run interactive setup wizard', true)
|
|
83
|
+
.option('--non-interactive', 'Skip interactive prompts (use defaults)', false)
|
|
84
|
+
.option('--template <template>', 'Use specific project template')
|
|
2966
85
|
.action(initProject);
|
|
2967
86
|
|
|
87
|
+
// Scaffold command
|
|
2968
88
|
program
|
|
2969
89
|
.command('scaffold')
|
|
2970
|
-
.alias('s')
|
|
2971
90
|
.description('Add CAWS components to existing project')
|
|
2972
|
-
.option('-f, --force', 'Overwrite existing files')
|
|
2973
|
-
.option('--
|
|
2974
|
-
.option('--with-codemods', 'Include codemod
|
|
2975
|
-
.option('--
|
|
91
|
+
.option('-f, --force', 'Overwrite existing files', false)
|
|
92
|
+
.option('--minimal', 'Only essential components', false)
|
|
93
|
+
.option('--with-codemods', 'Include codemod scripts', false)
|
|
94
|
+
.option('--with-oidc', 'Include OIDC trusted publisher setup', false)
|
|
2976
95
|
.action(scaffoldProject);
|
|
2977
96
|
|
|
97
|
+
// Validate command
|
|
2978
98
|
program
|
|
2979
99
|
.command('validate')
|
|
2980
|
-
.alias('v')
|
|
2981
100
|
.description('Validate CAWS working spec with suggestions')
|
|
2982
|
-
.argument('[spec-file]', 'Path to working spec file
|
|
2983
|
-
.option('-
|
|
2984
|
-
.option('
|
|
2985
|
-
.
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
101
|
+
.argument('[spec-file]', 'Path to working spec file (default: .caws/working-spec.yaml)')
|
|
102
|
+
.option('-q, --quiet', 'Suppress suggestions and warnings', false)
|
|
103
|
+
.option('--auto-fix', 'Automatically fix safe validation issues', false)
|
|
104
|
+
.action(validateCommand);
|
|
105
|
+
|
|
106
|
+
// Tool command
|
|
107
|
+
program
|
|
108
|
+
.command('tool')
|
|
109
|
+
.description('Execute CAWS tools programmatically')
|
|
110
|
+
.argument('<tool-id>', 'ID of the tool to execute')
|
|
111
|
+
.option('-p, --params <json>', 'Parameters as JSON string', '{}')
|
|
112
|
+
.option('-t, --timeout <ms>', 'Execution timeout in milliseconds', parseInt, 30000)
|
|
113
|
+
.action(executeTool);
|
|
114
|
+
|
|
115
|
+
// Test Analysis command
|
|
116
|
+
program
|
|
117
|
+
.command('test-analysis <subcommand> [options...]')
|
|
118
|
+
.description('Statistical analysis for budget prediction and test optimization')
|
|
119
|
+
.action(testAnalysisCommand);
|
|
120
|
+
|
|
121
|
+
// Provenance command group
|
|
122
|
+
const provenanceCmd = program
|
|
123
|
+
.command('provenance')
|
|
124
|
+
.description('Manage CAWS provenance tracking and audit trails');
|
|
125
|
+
|
|
126
|
+
// Subcommands
|
|
127
|
+
provenanceCmd
|
|
128
|
+
.command('update')
|
|
129
|
+
.description('Add new commit to provenance chain')
|
|
130
|
+
.requiredOption('-c, --commit <hash>', 'Git commit hash')
|
|
131
|
+
.option('-m, --message <msg>', 'Commit message')
|
|
132
|
+
.option('-a, --author <info>', 'Author information')
|
|
133
|
+
.option('-q, --quiet', 'Suppress output')
|
|
134
|
+
.option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
|
|
135
|
+
.action(async (options) => {
|
|
136
|
+
await provenanceCommand('update', options);
|
|
137
|
+
});
|
|
2995
138
|
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
139
|
+
provenanceCmd
|
|
140
|
+
.command('show')
|
|
141
|
+
.description('Display current provenance history')
|
|
142
|
+
.option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
|
|
143
|
+
.option('--format <type>', 'Output format: text, json, dashboard', 'text')
|
|
144
|
+
.action(async (options) => {
|
|
145
|
+
await provenanceCommand('show', options);
|
|
146
|
+
});
|
|
2999
147
|
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
148
|
+
provenanceCmd
|
|
149
|
+
.command('verify')
|
|
150
|
+
.description('Validate provenance chain integrity')
|
|
151
|
+
.option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
|
|
152
|
+
.action(async (options) => {
|
|
153
|
+
await provenanceCommand('verify', options);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
provenanceCmd
|
|
157
|
+
.command('analyze-ai')
|
|
158
|
+
.description('Analyze AI-assisted development patterns')
|
|
159
|
+
.option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
|
|
160
|
+
.action(async (options) => {
|
|
161
|
+
await provenanceCommand('analyze-ai', options);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
provenanceCmd
|
|
165
|
+
.command('init')
|
|
166
|
+
.description('Initialize provenance tracking for the project')
|
|
167
|
+
.option('-o, --output <path>', 'Output path for provenance files', '.caws/provenance')
|
|
168
|
+
.option('--cursor-api <url>', 'Cursor tracking API endpoint')
|
|
169
|
+
.option('--cursor-key <key>', 'Cursor API key')
|
|
170
|
+
.action(async (options) => {
|
|
171
|
+
await provenanceCommand('init', options);
|
|
172
|
+
});
|
|
3004
173
|
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
174
|
+
// Git hooks command
|
|
175
|
+
const hooksCmd = program
|
|
176
|
+
.command('hooks')
|
|
177
|
+
.description('Manage CAWS git hooks for provenance tracking');
|
|
178
|
+
|
|
179
|
+
hooksCmd
|
|
180
|
+
.command('install')
|
|
181
|
+
.description('Install CAWS git hooks')
|
|
182
|
+
.option('--no-provenance', 'Skip provenance tracking hooks')
|
|
183
|
+
.option('--no-validation', 'Skip validation hooks')
|
|
184
|
+
.option('--no-quality-gates', 'Skip quality gate hooks')
|
|
185
|
+
.option('--force', 'Overwrite existing hooks')
|
|
186
|
+
.option('--backup', 'Backup existing hooks before replacing')
|
|
187
|
+
.action(async (options) => {
|
|
188
|
+
const hookOptions = {
|
|
189
|
+
provenance: options.provenance !== false,
|
|
190
|
+
validation: options.validation !== false,
|
|
191
|
+
qualityGates: options.qualityGates !== false,
|
|
192
|
+
force: options.force,
|
|
193
|
+
backup: options.backup,
|
|
194
|
+
};
|
|
3010
195
|
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
196
|
+
try {
|
|
197
|
+
const result = await scaffoldGitHooks(process.cwd(), hookOptions);
|
|
198
|
+
if (result.added > 0) {
|
|
199
|
+
console.log(`✅ Successfully installed ${result.added} git hooks`);
|
|
200
|
+
if (result.skipped > 0) {
|
|
201
|
+
console.log(`⏭️ Skipped ${result.skipped} existing hooks`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
console.log('ℹ️ All hooks already configured');
|
|
3016
205
|
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error(`❌ Failed to install git hooks: ${error.message}`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
hooksCmd
|
|
213
|
+
.command('remove')
|
|
214
|
+
.description('Remove CAWS git hooks')
|
|
215
|
+
.action(async () => {
|
|
216
|
+
try {
|
|
217
|
+
await removeGitHooks(process.cwd());
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error(`❌ Failed to remove git hooks: ${error.message}`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
3017
223
|
|
|
3018
|
-
|
|
3019
|
-
|
|
224
|
+
hooksCmd
|
|
225
|
+
.command('status')
|
|
226
|
+
.description('Check git hooks status')
|
|
227
|
+
.action(async () => {
|
|
228
|
+
try {
|
|
229
|
+
await checkGitHooksStatus(process.cwd());
|
|
3020
230
|
} catch (error) {
|
|
3021
|
-
console.error(
|
|
231
|
+
console.error(`❌ Failed to check git hooks status: ${error.message}`);
|
|
3022
232
|
process.exit(1);
|
|
3023
233
|
}
|
|
3024
234
|
});
|
|
@@ -3036,7 +246,7 @@ program.exitOverride((err) => {
|
|
|
3036
246
|
process.exit(1);
|
|
3037
247
|
});
|
|
3038
248
|
|
|
3039
|
-
// Parse and run
|
|
249
|
+
// Parse and run
|
|
3040
250
|
if (require.main === module) {
|
|
3041
251
|
try {
|
|
3042
252
|
program.parse();
|