@paths.design/caws-cli 7.0.3 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/budget-derivation.d.ts.map +1 -1
  2. package/dist/commands/diagnose.d.ts.map +1 -1
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/provenance.d.ts +1 -1
  5. package/dist/commands/provenance.d.ts.map +1 -1
  6. package/dist/commands/quality-gates.d.ts +0 -46
  7. package/dist/commands/quality-gates.d.ts.map +1 -1
  8. package/dist/commands/quality-gates.js +226 -11
  9. package/dist/commands/specs.d.ts.map +1 -1
  10. package/dist/commands/specs.js +108 -13
  11. package/dist/commands/status.d.ts.map +1 -1
  12. package/dist/commands/templates.d.ts.map +1 -1
  13. package/dist/commands/tool.d.ts +1 -1
  14. package/dist/commands/tool.d.ts.map +1 -1
  15. package/dist/commands/tool.js +0 -1
  16. package/dist/commands/validate.d.ts.map +1 -1
  17. package/dist/commands/waivers.d.ts +1 -1
  18. package/dist/commands/waivers.d.ts.map +1 -1
  19. package/dist/config/index.d.ts.map +1 -1
  20. package/dist/generators/working-spec.d.ts.map +1 -1
  21. package/dist/index.js +10 -0
  22. package/dist/policy/PolicyManager.d.ts.map +1 -1
  23. package/dist/scaffold/cursor-hooks.d.ts.map +1 -1
  24. package/dist/scaffold/git-hooks.d.ts +18 -0
  25. package/dist/scaffold/git-hooks.d.ts.map +1 -1
  26. package/dist/scaffold/git-hooks.js +159 -58
  27. package/dist/scaffold/index.d.ts +1 -6
  28. package/dist/scaffold/index.d.ts.map +1 -1
  29. package/dist/scaffold/index.js +1 -1
  30. package/dist/templates/.caws/tools/README.md +1 -0
  31. package/dist/tool-loader.d.ts.map +1 -1
  32. package/dist/tool-validator.d.ts.map +1 -1
  33. package/dist/utils/async-utils.d.ts +73 -0
  34. package/dist/utils/async-utils.d.ts.map +1 -0
  35. package/dist/utils/command-wrapper.d.ts +66 -0
  36. package/dist/utils/command-wrapper.d.ts.map +1 -0
  37. package/dist/utils/detection.d.ts +7 -0
  38. package/dist/utils/detection.d.ts.map +1 -1
  39. package/dist/utils/git-lock.d.ts +13 -0
  40. package/dist/utils/git-lock.d.ts.map +1 -0
  41. package/dist/utils/git-lock.js +1 -0
  42. package/dist/utils/gitignore-updater.d.ts +39 -0
  43. package/dist/utils/gitignore-updater.d.ts.map +1 -0
  44. package/dist/utils/project-analysis.d.ts +20 -0
  45. package/dist/utils/project-analysis.d.ts.map +1 -1
  46. package/dist/utils/project-analysis.js +176 -16
  47. package/dist/utils/promise-utils.d.ts +30 -0
  48. package/dist/utils/promise-utils.d.ts.map +1 -0
  49. package/dist/utils/quality-gates.d.ts.map +1 -1
  50. package/dist/utils/quality-gates.js +7 -6
  51. package/dist/utils/spec-resolver.d.ts +1 -9
  52. package/dist/utils/spec-resolver.d.ts.map +1 -1
  53. package/dist/utils/spec-resolver.js +4 -0
  54. package/dist/utils/yaml-validation.d.ts +32 -0
  55. package/dist/utils/yaml-validation.d.ts.map +1 -0
  56. package/dist/utils/yaml-validation.js +1 -0
  57. package/dist/validation/spec-validation.d.ts.map +1 -1
  58. package/package.json +1 -1
  59. package/templates/.caws/tools/README.md +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"budget-derivation.d.ts","sourceRoot":"","sources":["../src/budget-derivation.js"],"names":[],"mappings":"AA8JA;;;;;;;;GAQG;AACH,sDALW,MAAM,YAEd;IAAyB,QAAQ,EAAzB,OAAO;CACf,OA+FF;AA4FD;;;;;;GAMG;AACH,qCAJW,MAAM,eACN,MAAM,GACJ,MAAO,IAAI,CAuCvB;AAED;;;;;;GAMG;AACH,0DAFa,OAAO,CAgDnB;AAED;;;;;GAKG;AACH,kFA8BC;AAqCD;;;;;;GAMG;AACH,6EAFa,MAAM,CA8ClB;AAtFD;;;;GAIG;AACH,uEAiBC;AAED;;;;;GAKG;AACH,4EAHW,MAAM,GACJ,OAAO,CAKnB;AAhgBD;;;;GAIG;AACH,kDA+FC;AAED;;;GAGG;AACH,wCAsCC;AA0GD;;;;GAIG;AACH,2DAmFC"}
1
+ {"version":3,"file":"budget-derivation.d.ts","sourceRoot":"","sources":["../src/budget-derivation.js"],"names":[],"mappings":"AA8JA;;;;;;;;GAQG;AACH,sDALW,MAAM,YAEd;IAAyB,QAAQ,EAAzB,OAAO;CACf,OAgGF;AA4FD;;;;;;GAMG;AACH,qCAJW,MAAM,eACN,MAAM,GACJ,MAAO,IAAI,CAuCvB;AAED;;;;;;GAMG;AACH,0DAFa,OAAO,CAgDnB;AAED;;;;;GAKG;AACH,kFA8BC;AAqCD;;;;;;GAMG;AACH,6EAFa,MAAM,CA8ClB;AAtFD;;;;GAIG;AACH,uEAiBC;AAED;;;;;GAKG;AACH,4EAHW,MAAM,GACJ,OAAO,CAKnB;AAjgBD;;;;GAIG;AACH,kDA+FC;AAED;;;GAGG;AACH,wCAsCC;AA2GD;;;;GAIG;AACH,2DAmFC"}
@@ -1 +1 @@
1
- {"version":3,"file":"diagnose.d.ts","sourceRoot":"","sources":["../../src/commands/diagnose.js"],"names":[],"mappings":"AAubA;;;GAGG;AACH,8DAiDC;AApND;;;GAGG;AACH,gCAFa,OAAO,KAAQ,CAkD3B;AAED;;;GAGG;AACH,wCAFW,KAAQ,QA4ClB;AAED;;;;GAIG;AACH,wCAHW,KAAQ,GACN,OAAO,KAAQ,CAoD3B;AAtaD;;;GAGG;AACH,oCAFa,OAAO,KAAQ,CA2C3B;AAED;;;GAGG;AACH,iCAFa,OAAO,KAAQ,CAiB3B;AAED;;;GAGG;AACH,iCAFa,OAAO,KAAQ,CA0C3B;AAED;;;GAGG;AACH,yCAFa,OAAO,KAAQ,CAgE3B;AAED;;;GAGG;AACH,kCAFa,OAAO,KAAQ,CAoC3B;AAED;;;GAGG;AACH,kCAFa,OAAO,KAAQ,CAuC3B"}
1
+ {"version":3,"file":"diagnose.d.ts","sourceRoot":"","sources":["../../src/commands/diagnose.js"],"names":[],"mappings":"AA0bA;;;GAGG;AACH,8DAoDC;AAvND;;;GAGG;AACH,gCAFa,OAAO,KAAQ,CAkD3B;AAED;;;GAGG;AACH,wCAFW,KAAQ,QA4ClB;AAED;;;;GAIG;AACH,wCAHW,KAAQ,GACN,OAAO,KAAQ,CAoD3B;AAzaD;;;GAGG;AACH,oCAFa,OAAO,KAAQ,CA2C3B;AAED;;;GAGG;AACH,iCAFa,OAAO,KAAQ,CAiB3B;AAED;;;GAGG;AACH,iCAFa,OAAO,KAAQ,CA0C3B;AAED;;;GAGG;AACH,yCAFa,OAAO,KAAQ,CAgE3B;AAED;;;GAGG;AACH,kCAFa,OAAO,KAAQ,CAoC3B;AAED;;;GAGG;AACH,kCAFa,OAAO,KAAQ,CA0C3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.js"],"names":[],"mappings":"AAoBA;;GAEG;AACH,2EAseC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.js"],"names":[],"mappings":"AAqBA;;GAEG;AACH,2EAwiBC"}
@@ -3,7 +3,7 @@
3
3
  * @param {string} subcommand - The subcommand to execute
4
4
  * @param {Object} options - Command options
5
5
  */
6
- export function provenanceCommand(subcommand: string, options: any): Promise<void>;
6
+ export function provenanceCommand(subcommand: string, options: any): Promise<any>;
7
7
  /**
8
8
  * Update provenance with new commit information
9
9
  * @param {Object} options - Command options
@@ -1 +1 @@
1
- {"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/commands/provenance.js"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,8CAHW,MAAM,+BA2BhB;AAED;;;GAGG;AACH,8DA4EC;AAED;;;GAGG;AACH,4DAyEC;AAED;;;GAGG;AACH,8DA8CC;AA6iBD;;;GAGG;AACH,4DAmEC;AAzcD;;;GAGG;AACH,0DAwEC"}
1
+ {"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/commands/provenance.js"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,8CAHW,MAAM,8BA+BhB;AAED;;;GAGG;AACH,8DA4EC;AAED;;;GAGG;AACH,4DAyEC;AAED;;;GAGG;AACH,8DA8CC;AA6iBD;;;GAGG;AACH,4DAmEC;AAzcD;;;GAGG;AACH,0DAwEC"}
@@ -1,49 +1,3 @@
1
- /**
2
- * Quality Gates Configuration
3
- */
4
- /**
5
- * Update provenance with quality gates results
6
- * @param {Object} results - Quality gates results
7
- * @param {boolean} crisisMode - Whether in crisis mode
8
- * @param {string[]} stagedFiles - Array of staged files
9
- */
10
- /**
11
- * Detect agent type for provenance tracking
12
- * @returns {string} Agent type identifier
13
- */
14
- /**
15
- * Check if a waiver applies to the given gate
16
- * @param {string} gate - Gate name to check
17
- * @returns {Object} Waiver check result
18
- */
19
- /**
20
- * Detect if project is in crisis response mode
21
- * @returns {boolean} True if in crisis mode
22
- */
23
- /**
24
- * Get staged files from git
25
- * @returns {string[]} Array of staged file paths
26
- */
27
- /**
28
- * Check for god objects in staged Rust files with waiver and crisis mode support
29
- * @param {string[]} stagedFiles - Array of staged file paths
30
- * @param {boolean} crisisMode - Whether in crisis response mode
31
- * @returns {Object} God object analysis results
32
- */
33
- /**
34
- * Check for hidden TODOs in staged files with waiver and crisis mode support
35
- * @param {string[]} stagedFiles - Array of staged file paths
36
- * @param {boolean} crisisMode - Whether in crisis response mode
37
- * @returns {Object} TODO analysis results
38
- */
39
- /**
40
- * Check for human override in working spec
41
- * @returns {Object} Human override check result
42
- */
43
- /**
44
- * Get CAWS tier from working spec
45
- * @returns {number|null} CAWS tier (1, 2, or 3) or null if not found
46
- */
47
1
  /**
48
2
  * Run comprehensive quality gates on staged files
49
3
  * @param {Object} options - Command options
@@ -1 +1 @@
1
- {"version":3,"file":"quality-gates.d.ts","sourceRoot":"","sources":["../../src/commands/quality-gates.js"],"names":[],"mappings":"AAqBA;;GAEG;AAkBH;;;;;GAKG;AAGH;;;GAGG;AA6BH;;;;GAIG;AAiCH;;;GAGG;AAoCH;;;GAGG;AAeH;;;;;GAKG;AAmFH;;;;;GAKG;AAqEH;;;GAGG;AA0BH;;;GAGG;AAaH;;;GAGG;AACH,iEAmGC"}
1
+ {"version":3,"file":"quality-gates.d.ts","sourceRoot":"","sources":["../../src/commands/quality-gates.js"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,iEA4ZC"}
@@ -44,14 +44,66 @@ async function qualityGatesCommand(options = {}) {
44
44
  const packagesDir = path.dirname(cliPackageDir);
45
45
  const monorepoRunner = path.join(packagesDir, 'quality-gates', 'run-quality-gates.mjs');
46
46
 
47
- // Option 2: Check VS Code extension bundled (if running from extension context)
47
+ // Option 2: Check globally installed CLI for bundled quality gates
48
+ let globalCliPath = null;
49
+ try {
50
+ const { execSync } = require('child_process');
51
+ const whichCaws = execSync('which caws', { encoding: 'utf8', stdio: 'pipe' }).trim();
52
+ if (whichCaws) {
53
+ // Resolve symlink to actual path
54
+ const realPath = fs.realpathSync(whichCaws);
55
+ const globalCliDir = path.dirname(realPath);
56
+ // Check for bundled quality gates in global CLI installation
57
+ const possibleBundledPaths = [
58
+ path.join(
59
+ globalCliDir,
60
+ '..',
61
+ 'lib',
62
+ 'node_modules',
63
+ '@paths.design',
64
+ 'caws-cli',
65
+ 'node_modules',
66
+ '@paths.design',
67
+ 'quality-gates',
68
+ 'run-quality-gates.mjs'
69
+ ),
70
+ path.join(
71
+ globalCliDir,
72
+ '..',
73
+ 'lib',
74
+ 'node_modules',
75
+ '@paths.design',
76
+ 'quality-gates',
77
+ 'run-quality-gates.mjs'
78
+ ),
79
+ path.join(
80
+ globalCliDir,
81
+ '..',
82
+ 'node_modules',
83
+ '@paths.design',
84
+ 'quality-gates',
85
+ 'run-quality-gates.mjs'
86
+ ),
87
+ ];
88
+ for (const bundledPath of possibleBundledPaths) {
89
+ if (fs.existsSync(bundledPath)) {
90
+ globalCliPath = bundledPath;
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ } catch (e) {
96
+ // Ignore errors finding global CLI
97
+ }
98
+
99
+ // Option 3: Check VS Code extension bundled (if running from extension context)
48
100
  const vscodeExtensionPath =
49
101
  process.env.VSCODE_EXTENSION_PATH || process.env.VSCODE_EXTENSION_DIR;
50
102
  const bundledRunner = vscodeExtensionPath
51
103
  ? path.join(vscodeExtensionPath, 'bundled', 'quality-gates', 'run-quality-gates.mjs')
52
104
  : null;
53
105
 
54
- // Option 3: Check node_modules for quality-gates package (prioritize published package)
106
+ // Option 4: Check node_modules for quality-gates package (prioritize published package)
55
107
  const nodeModulesPaths = [
56
108
  // Published npm package (priority)
57
109
  path.join(
@@ -69,6 +121,8 @@ async function qualityGatesCommand(options = {}) {
69
121
  // Try all possible paths in order
70
122
  if (fs.existsSync(monorepoRunner)) {
71
123
  qualityGatesRunner = monorepoRunner;
124
+ } else if (globalCliPath) {
125
+ qualityGatesRunner = globalCliPath;
72
126
  } else if (bundledRunner && fs.existsSync(bundledRunner)) {
73
127
  qualityGatesRunner = bundledRunner;
74
128
  } else {
@@ -132,14 +186,104 @@ async function qualityGatesCommand(options = {}) {
132
186
  }
133
187
  }
134
188
 
135
- // If still no runner found, provide helpful error
189
+ // Option 5: Try npx (no installation required) - works if Node.js is available
136
190
  if (!qualityGatesRunner) {
137
- const suggestions = [
138
- 'Install quality gates package: npm install -g @paths.design/quality-gates',
139
- 'Use Python script (if available): python3 scripts/simple_gates.py all --tier 2 --profile backend-api',
140
- 'Use Makefile target (if available): make caws-gates',
141
- 'Run from CAWS monorepo root',
142
- ];
191
+ try {
192
+ const { execSync } = require('child_process');
193
+ // Check if npx is available
194
+ execSync('command -v npx', { encoding: 'utf8', stdio: 'ignore' });
195
+
196
+ Output.info('Using npx to run quality gates (no installation required)...');
197
+
198
+ // Build npx command - the package exposes 'caws-quality-gates' bin command
199
+ // Use npx to download and run without installing
200
+ const npxArgs = ['npx', '--yes', '@paths.design/quality-gates'];
201
+
202
+ // Map CLI options to runner options
203
+ if (options.ci) {
204
+ npxArgs.push('--ci');
205
+ }
206
+ if (options.json) {
207
+ npxArgs.push('--json');
208
+ }
209
+ if (options.gates && options.gates.trim()) {
210
+ npxArgs.push('--gates', options.gates.trim());
211
+ }
212
+ if (options.fix) {
213
+ npxArgs.push('--fix');
214
+ }
215
+ // Handle context options: --all-files takes precedence, then --context
216
+ if (options.allFiles) {
217
+ npxArgs.push('--context=ci');
218
+ } else if (options.context && options.context !== 'commit') {
219
+ npxArgs.push(`--context=${options.context}`);
220
+ }
221
+
222
+ Output.progress('Executing quality gates via npx...');
223
+ Output.info(`Command: ${npxArgs.join(' ')}`);
224
+
225
+ // Execute via npx
226
+ const { execSync: execSyncNpx } = require('child_process');
227
+ execSyncNpx(npxArgs.join(' '), {
228
+ stdio: 'inherit',
229
+ cwd: projectRoot,
230
+ env: {
231
+ ...process.env,
232
+ CAWS_CLI_INTEGRATION: 'true',
233
+ CAWS_CLI_VERSION: require(path.join(cliPackageDir, 'package.json')).version,
234
+ },
235
+ });
236
+
237
+ Output.success('Quality gates completed successfully');
238
+ return;
239
+ } catch (npxError) {
240
+ // npx not available or failed - continue to error message
241
+ }
242
+ }
243
+
244
+ // If still no runner found, provide helpful error with language-agnostic suggestions
245
+ if (!qualityGatesRunner) {
246
+ // Check if Node.js/npx is available (language-agnostic check)
247
+ let hasNodeJs = false;
248
+ try {
249
+ const { execSync } = require('child_process');
250
+ execSync('command -v node', { encoding: 'utf8', stdio: 'ignore' });
251
+ execSync('command -v npx', { encoding: 'utf8', stdio: 'ignore' });
252
+ hasNodeJs = true;
253
+ } catch (e) {
254
+ // Node.js/npx not available
255
+ }
256
+
257
+ const suggestions = [];
258
+
259
+ if (hasNodeJs) {
260
+ // Node.js available - suggest npx (works for any language, no installation)
261
+ suggestions.push(
262
+ 'Use npx (no installation required): npx --yes @paths.design/quality-gates'
263
+ );
264
+ suggestions.push('Install globally: npm install -g @paths.design/quality-gates');
265
+ suggestions.push('Install locally: npm install --save-dev @paths.design/quality-gates');
266
+ } else {
267
+ // Node.js not available - suggest installation or alternatives
268
+ suggestions.push('Install Node.js to use quality gates: https://nodejs.org/');
269
+ suggestions.push(
270
+ 'Then use: npx --yes @paths.design/quality-gates (no installation required)'
271
+ );
272
+ suggestions.push('Or install globally: npm install -g @paths.design/quality-gates');
273
+ }
274
+
275
+ // Language-agnostic fallback options (if they exist)
276
+ const pythonScript = path.join(projectRoot, 'scripts', 'simple_gates.py');
277
+ const makefile = path.join(projectRoot, 'Makefile');
278
+
279
+ if (fs.existsSync(pythonScript)) {
280
+ suggestions.push(`Use project script: python3 ${pythonScript} all --tier 2`);
281
+ }
282
+ if (fs.existsSync(makefile)) {
283
+ suggestions.push('Use Makefile target: make caws-gates');
284
+ }
285
+
286
+ suggestions.push('Run from CAWS monorepo root (if developing CAWS itself)');
143
287
 
144
288
  throw new Error(
145
289
  'Quality gates runner not found.\n\n' +
@@ -172,6 +316,13 @@ async function qualityGatesCommand(options = {}) {
172
316
  args.push('--fix');
173
317
  }
174
318
 
319
+ // Handle context options: --all-files takes precedence, then --context
320
+ if (options.allFiles) {
321
+ args.push('--context=ci');
322
+ } else if (options.context && options.context !== 'commit') {
323
+ args.push(`--context=${options.context}`);
324
+ }
325
+
175
326
  // Add CAWS-specific environment variables for integration
176
327
  const env = {
177
328
  ...process.env,
@@ -198,7 +349,47 @@ async function qualityGatesCommand(options = {}) {
198
349
  const timeoutMs = options.timeout || (options.ci ? 30 * 60 * 1000 : 10 * 60 * 1000);
199
350
 
200
351
  const completionPromise = new Promise((resolve, reject) => {
352
+ let resolved = false;
353
+
354
+ // Handle process termination signals
355
+ const signalHandlers = {
356
+ SIGINT: () => {
357
+ if (!resolved && !child.killed && child.pid) {
358
+ child.kill('SIGINT');
359
+ }
360
+ },
361
+ SIGTERM: () => {
362
+ if (!resolved && !child.killed && child.pid) {
363
+ child.kill('SIGTERM');
364
+ }
365
+ },
366
+ };
367
+
368
+ process.on('SIGINT', signalHandlers.SIGINT);
369
+ process.on('SIGTERM', signalHandlers.SIGTERM);
370
+
371
+ const cleanup = () => {
372
+ if (resolved) return;
373
+ resolved = true;
374
+
375
+ // Remove signal handlers
376
+ try {
377
+ process.removeListener('SIGINT', signalHandlers.SIGINT);
378
+ process.removeListener('SIGTERM', signalHandlers.SIGTERM);
379
+ } catch (e) {
380
+ // Ignore errors removing listeners
381
+ }
382
+
383
+ // Remove event listeners to prevent memory leaks
384
+ try {
385
+ child.removeAllListeners();
386
+ } catch (e) {
387
+ // Ignore errors removing listeners
388
+ }
389
+ };
390
+
201
391
  child.on('close', (code) => {
392
+ cleanup();
202
393
  if (code === 0) {
203
394
  resolve();
204
395
  } else {
@@ -207,12 +398,36 @@ async function qualityGatesCommand(options = {}) {
207
398
  });
208
399
 
209
400
  child.on('error', (error) => {
401
+ cleanup();
210
402
  reject(new Error(`Failed to execute quality gates runner: ${error.message}`));
211
403
  });
212
404
  });
213
405
 
214
- await withTimeout(completionPromise, timeoutMs, 'Quality gates execution timed out');
215
- Output.success('Quality gates completed successfully');
406
+ try {
407
+ await withTimeout(completionPromise, timeoutMs, 'Quality gates execution timed out');
408
+ Output.success('Quality gates completed successfully');
409
+ } catch (error) {
410
+ // Ensure child process is killed on timeout or error
411
+ try {
412
+ if (!child.killed && child.pid) {
413
+ child.kill('SIGTERM');
414
+ // Give it a moment to exit gracefully, then force kill
415
+ // eslint-disable-next-line no-undef
416
+ setTimeout(() => {
417
+ if (!child.killed && child.pid) {
418
+ try {
419
+ child.kill('SIGKILL');
420
+ } catch (e) {
421
+ // Ignore errors killing process
422
+ }
423
+ }
424
+ }, 1000);
425
+ }
426
+ } catch (killError) {
427
+ // Ignore errors killing process
428
+ }
429
+ throw error;
430
+ }
216
431
  },
217
432
  {
218
433
  commandName: 'quality-gates',
@@ -1 +1 @@
1
- {"version":3,"file":"specs.d.ts","sourceRoot":"","sources":["../../src/commands/specs.js"],"names":[],"mappings":"AAghBA;;;;GAIG;AACH,qCAHW,MAAM,+BA2LhB;AAvrBD;;;GAGG;AACH,qCAFa,OAAO,KAAQ,CAqB3B;AAED;;;;GAIG;AACH,kDAFa,OAAO,CAAC,IAAI,CAAC,CAMzB;AAED;;;GAGG;AACH,iCAFa,OAAO,OAAO,CAmC1B;AAED;;;;;GAKG;AACH,+BAJW,MAAM,kBAEJ,OAAO,KAAQ,CA8H3B;AAED;;;;GAIG;AACH,6BAHW,MAAM,GACJ,OAAO,CAAC,MAAO,IAAI,CAAC,CAiBhC;AAED;;;;;GAKG;AACH,+BAJW,MAAM,kBAEJ,OAAO,CAAC,OAAO,CAAC,CA6B5B;AAED;;;;GAIG;AACH,+BAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAmB5B;AAED;;;GAGG;AACH,sDA2CC;AAED;;;GAGG;AACH,oDAoCC;AAsFD;;;GAGG;AACH,yCAFa,OAAO,CAAC,MAAM,CAAC,CAuC3B;AA9fD;;GAEG;AACH,wBAAkB,aAAa,CAAC;AAChC,6BAAuB,2BAA2B,CAAC"}
1
+ {"version":3,"file":"specs.d.ts","sourceRoot":"","sources":["../../src/commands/specs.js"],"names":[],"mappings":"AAipBA;;;;GAIG;AACH,qCAHW,MAAM,+BA+LhB;AA3zBD;;;GAGG;AACH,qCAFa,OAAO,KAAQ,CAqB3B;AAED;;;;GAIG;AACH,kDAFa,OAAO,CAAC,IAAI,CAAC,CAMzB;AAED;;;GAGG;AACH,iCAFa,OAAO,OAAO,CAmC1B;AAED;;;;;GAKG;AACH,+BAJW,MAAM,kBAEJ,OAAO,KAAQ,CAkO3B;AAED;;;;GAIG;AACH,6BAHW,MAAM,GACJ,OAAO,CAAC,MAAO,IAAI,CAAC,CAiBhC;AAED;;;;;GAKG;AACH,+BAJW,MAAM,kBAEJ,OAAO,CAAC,OAAO,CAAC,CA6B5B;AAED;;;;GAIG;AACH,+BAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAmB5B;AAED;;;GAGG;AACH,sDA2CC;AAED;;;GAGG;AACH,oDAoCC;AAoHD;;;GAGG;AACH,yCAFa,OAAO,CAAC,MAAM,CAAC,CAqC3B;AA9nBD;;GAEG;AACH,wBAAkB,aAAa,CAAC;AAChC,6BAAuB,2BAA2B,CAAC"}
@@ -154,9 +154,13 @@ async function createSpec(id, options = {}) {
154
154
  console.log(chalk.blue('ā„¹ļø Spec creation canceled.'));
155
155
  return null;
156
156
  } else if (answer === 'rename') {
157
- // Generate new name with timestamp suffix
158
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
159
- const newId = `${id}-${timestamp}`;
157
+ // Generate new name with valid PREFIX-NUMBER format
158
+ // Extract prefix from existing ID or use default
159
+ const prefixMatch = id.match(/^([A-Z]+)-\d+$/);
160
+ const prefix = prefixMatch ? prefixMatch[1] : 'FEAT';
161
+ // Generate sequential number based on timestamp
162
+ const number = Date.now().toString().slice(-6); // Last 6 digits of timestamp
163
+ const newId = `${prefix}-${number}`;
160
164
  console.log(chalk.blue(`šŸ“ Creating spec with new name: ${newId}`));
161
165
  return await createSpec(newId, { ...options, interactive: false });
162
166
  } else if (answer === 'merge') {
@@ -183,9 +187,10 @@ async function createSpec(id, options = {}) {
183
187
  // Ensure specs directory exists
184
188
  await fs.ensureDir(SPECS_DIR);
185
189
 
186
- // Generate spec content
187
- const specContent = {
188
- id,
190
+ // Generate spec content with all required fields
191
+ // Merge template carefully to preserve required fields and structure
192
+ const defaultSpec = {
193
+ id, // Always use the provided id parameter
189
194
  type,
190
195
  title,
191
196
  status: 'draft',
@@ -193,8 +198,64 @@ async function createSpec(id, options = {}) {
193
198
  mode,
194
199
  created_at: new Date().toISOString(),
195
200
  updated_at: new Date().toISOString(),
196
- acceptance_criteria: [],
201
+ // Required fields for validation
202
+ blast_radius: {
203
+ modules: [],
204
+ data_migration: false,
205
+ },
206
+ operational_rollback_slo: '5m',
207
+ scope: {
208
+ in: ['src/', 'tests/'],
209
+ out: ['node_modules/', 'dist/', 'build/'],
210
+ },
211
+ invariants: ['System maintains data consistency'],
212
+ acceptance: [], // Note: validation expects 'acceptance', not 'acceptance_criteria'
213
+ acceptance_criteria: [], // Keep for backward compatibility
214
+ non_functional: {
215
+ a11y: [],
216
+ perf: {},
217
+ security: [],
218
+ },
219
+ contracts: [],
220
+ };
221
+
222
+ // Merge template, but preserve required structure
223
+ // Map template.criteria to acceptance if present
224
+ const templateAcceptance = template?.criteria || template?.acceptance;
225
+
226
+ const specContent = {
227
+ ...defaultSpec,
197
228
  ...(template || {}),
229
+ // Always preserve these critical fields
230
+ id, // Never allow template to override id
231
+ // Map criteria to acceptance if template uses criteria
232
+ acceptance: templateAcceptance || defaultSpec.acceptance,
233
+ acceptance_criteria: templateAcceptance || defaultSpec.acceptance_criteria,
234
+ // Deep merge scope if template provides it
235
+ scope: template?.scope
236
+ ? {
237
+ in: template.scope.in || defaultSpec.scope.in,
238
+ out: template.scope.out || defaultSpec.scope.out,
239
+ }
240
+ : defaultSpec.scope,
241
+ // Deep merge blast_radius if template provides it
242
+ blast_radius: template?.blast_radius
243
+ ? {
244
+ modules: template.blast_radius.modules || defaultSpec.blast_radius.modules,
245
+ data_migration:
246
+ template.blast_radius.data_migration !== undefined
247
+ ? template.blast_radius.data_migration
248
+ : defaultSpec.blast_radius.data_migration,
249
+ }
250
+ : defaultSpec.blast_radius,
251
+ // Deep merge non_functional if template provides it
252
+ non_functional: template?.non_functional
253
+ ? {
254
+ a11y: template.non_functional.a11y || defaultSpec.non_functional.a11y,
255
+ perf: template.non_functional.perf || defaultSpec.non_functional.perf,
256
+ security: template.non_functional.security || defaultSpec.non_functional.security,
257
+ }
258
+ : defaultSpec.non_functional,
198
259
  };
199
260
 
200
261
  // Create file path
@@ -442,9 +503,10 @@ function displaySpecDetails(spec) {
442
503
  /**
443
504
  * Migrate from legacy working-spec.yaml to feature-specific specs
444
505
  * @param {Object} options - Migration options
506
+ * @param {Function} [createSpecFn] - Function to create specs (for testing)
445
507
  * @returns {Promise<Object>} Migration result
446
508
  */
447
- async function migrateFromLegacy(options = {}) {
509
+ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
448
510
  const fs = require('fs-extra');
449
511
  const path = require('path');
450
512
  const yaml = require('js-yaml');
@@ -461,6 +523,14 @@ async function migrateFromLegacy(options = {}) {
461
523
  const legacyContent = await fs.readFile(legacyPath, 'utf8');
462
524
  const legacySpec = yaml.load(legacyContent);
463
525
 
526
+ if (!legacySpec) {
527
+ throw new Error('Legacy working-spec.yaml is empty or invalid');
528
+ }
529
+
530
+ if (!legacySpec.acceptance || !Array.isArray(legacySpec.acceptance)) {
531
+ throw new Error('Legacy working-spec.yaml must have an acceptance array');
532
+ }
533
+
464
534
  // Suggest feature breakdown based on acceptance criteria
465
535
  const features = suggestFeatureBreakdown(legacySpec);
466
536
 
@@ -480,15 +550,32 @@ async function migrateFromLegacy(options = {}) {
480
550
  }
481
551
 
482
552
  if (options.features && options.features.length > 0) {
553
+ // Filter by original feature IDs (before transformation)
483
554
  selectedFeatures = features.filter((f) => options.features.includes(f.id));
484
- console.log(chalk.blue(`\nšŸ“‹ Migrating selected features: ${options.features.join(', ')}`));
555
+ if (selectedFeatures.length === 0) {
556
+ const errorMsg = `No features found matching: ${options.features.join(', ')}. Available features: ${features.map((f) => f.id).join(', ')}`;
557
+ console.log(chalk.yellow(`āš ļø ${errorMsg}`));
558
+ throw new Error(errorMsg);
559
+ } else {
560
+ console.log(chalk.blue(`\nšŸ“‹ Migrating selected features: ${options.features.join(', ')}`));
561
+ }
485
562
  }
486
563
 
487
564
  // Create each feature spec
488
565
  const createdSpecs = [];
566
+ let featureCounter = 1;
489
567
  for (const feature of selectedFeatures) {
490
568
  try {
491
- await createSpec(feature.id, {
569
+ // Transform feature ID to proper format (PREFIX-NUMBER) if needed
570
+ let specId = feature.id;
571
+ if (!/^[A-Z]+-\d+$/.test(specId)) {
572
+ // Convert 'auth' -> 'FEAT-001', 'payment' -> 'FEAT-002', etc.
573
+ const prefix = specId.toUpperCase().replace(/[^A-Z0-9]/g, '');
574
+ specId = `${prefix || 'FEAT'}-${String(featureCounter).padStart(3, '0')}`;
575
+ featureCounter++;
576
+ }
577
+
578
+ await createSpecFn(specId, {
492
579
  type: 'feature',
493
580
  title: feature.title,
494
581
  risk_tier: 'T3', // Default tier
@@ -496,10 +583,14 @@ async function migrateFromLegacy(options = {}) {
496
583
  template: feature,
497
584
  });
498
585
 
499
- createdSpecs.push(feature.id);
500
- console.log(chalk.green(` āœ… Created spec: ${feature.id}`));
586
+ createdSpecs.push(specId);
587
+ console.log(chalk.green(` āœ… Created spec: ${specId}`));
501
588
  } catch (error) {
589
+ // Log full error details for debugging
502
590
  console.log(chalk.red(` āŒ Failed to create spec ${feature.id}: ${error.message}`));
591
+ if (process.env.DEBUG_MIGRATION) {
592
+ console.log(chalk.gray(` Error details: ${error.stack}`));
593
+ }
503
594
  }
504
595
  }
505
596
 
@@ -628,7 +719,11 @@ async function specsCommand(action, options = {}) {
628
719
  }
629
720
 
630
721
  case 'migrate': {
631
- const result = await migrateFromLegacy(options);
722
+ // Allow tests to inject createSpec function
723
+ const createSpecFn = options._createSpecFn || createSpec;
724
+ const migrationOptions = { ...options };
725
+ delete migrationOptions._createSpecFn; // Remove test-only option
726
+ const result = await migrateFromLegacy(migrationOptions, createSpecFn);
632
727
 
633
728
  return outputResult({
634
729
  command: 'specs migrate',
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.js"],"names":[],"mappings":"AA6uBA;;;GAGG;AACH,2DA6IC;AAl3BD;;;;GAIG;AACH,2CAHW,MAAM,GACJ,OAAO,CAAC,MAAO,IAAI,CAAC,CAahC;AAgBD;;;GAGG;AACH,iCAFa,OAAO,KAAQ,CAgC3B;AAED;;;GAGG;AACH,uCAFa,OAAO,KAAQ,CA+B3B;AAED;;;GAGG;AACH,oCAFa,OAAO,KAAQ,CA0D3B;AAED;;;GAGG;AACH,qCAFa,OAAO,KAAQ,CAS3B;AAwBD;;;GAGG;AACH,+CAgGC;AAED;;;;;GAKG;AACH,4DAHW,MAAM,GACJ,MAAM,EAAE,CAoCpB"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.js"],"names":[],"mappings":"AA8uBA;;;GAGG;AACH,2DA+IC;AAp3BD;;;;GAIG;AACH,2CAHW,MAAM,GACJ,OAAO,CAAC,MAAO,IAAI,CAAC,CAahC;AAgBD;;;GAGG;AACH,iCAFa,OAAO,KAAQ,CAgC3B;AAED;;;GAGG;AACH,uCAFa,OAAO,KAAQ,CA+B3B;AAED;;;GAGG;AACH,oCAFa,OAAO,KAAQ,CA0D3B;AAED;;;GAGG;AACH,qCAFa,OAAO,KAAQ,CAS3B;AAwBD;;;GAGG;AACH,+CAgGC;AAED;;;;;GAKG;AACH,4DAHW,MAAM,GACJ,MAAM,EAAE,CAoCpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/commands/templates.js"],"names":[],"mappings":"AAoMA;;;;GAIG;AACH,8CAHW,MAAM,gCA4BhB;AAvHD;;GAEG;AACH,sCAuCC;AAED;;;GAGG;AACH,6CAFW,MAAM,QAyChB;AA9GD;;;GAGG;AACH,6CAiBC;AA/FD;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDE"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/commands/templates.js"],"names":[],"mappings":"AA8MA;;;;GAIG;AACH,8CAHW,MAAM,gCA4BhB;AAvHD;;GAEG;AACH,sCAuCC;AAED;;;GAGG;AACH,6CAFW,MAAM,QAyChB;AA9GD;;;GAGG;AACH,6CAiBC;AAxGD;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDE"}
@@ -8,6 +8,6 @@ export function initializeToolSystem(): Promise<ToolLoader | null>;
8
8
  * @param {string} toolId - ID of the tool to execute
9
9
  * @param {Object} options - Command options
10
10
  */
11
- export function executeTool(toolId: string, options: any): Promise<void>;
11
+ export function executeTool(toolId: string, options: any): Promise<any>;
12
12
  import ToolLoader = require("../tool-loader");
13
13
  //# sourceMappingURL=tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/commands/tool.js"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wCAFa,OAAO,CAAC,UAAU,GAAC,IAAI,CAAC,CAoCpC;AAED;;;;GAIG;AACH,oCAHW,MAAM,+BAyEhB"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/commands/tool.js"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wCAFa,OAAO,CAAC,UAAU,GAAC,IAAI,CAAC,CAwCpC;AAED;;;;GAIG;AACH,oCAHW,MAAM,8BAoEhB"}
@@ -4,7 +4,6 @@
4
4
  * @author @darianrosebrook
5
5
  */
6
6
 
7
- const path = require('path');
8
7
  const { commandWrapper, Output } = require('../utils/command-wrapper');
9
8
 
10
9
  // Import tool system
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.js"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH,0CANW,MAAM,YAEd;IAAyB,MAAM,GAAvB,MAAM;IACY,WAAW,GAA7B,OAAO;IACW,MAAM,GAAxB,OAAO;CACjB,iBAiNA"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.js"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH,0CANW,MAAM,YAEd;IAAyB,MAAM,GAAvB,MAAM;IACY,WAAW,GAA7B,OAAO;IACW,MAAM,GAAxB,OAAO;CACjB,iBA6NA"}
@@ -4,5 +4,5 @@
4
4
  * @param {string} subcommand - create, list, show, revoke
5
5
  * @param {object} options - Command options
6
6
  */
7
- export function waiversCommand(subcommand?: string, options?: object): Promise<void>;
7
+ export function waiversCommand(subcommand?: string, options?: object): Promise<any>;
8
8
  //# sourceMappingURL=waivers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"waivers.d.ts","sourceRoot":"","sources":["../../src/commands/waivers.js"],"names":[],"mappings":"AAmBA;;;;;GAKG;AACH,4CAHW,MAAM,YACN,MAAM,iBA2ChB"}
1
+ {"version":3,"file":"waivers.d.ts","sourceRoot":"","sources":["../../src/commands/waivers.js"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,4CAHW,MAAM,YACN,MAAM,gBAyChB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.js"],"names":[],"mappings":";mCAakD,OAAO;AAOzD;;;GAGG;AACH,6CAkBC;AAED;;;GAGG;AACH,uCAFa,MAAO,IAAI,CA2CvB;AAED;;;GAGG;AACH,6CAFa,MAAO,IAAI,CAmCvB;AAED;;;GAGG;AACH,0CAEC;AAED;;;GAGG;AACH,qDAEC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.js"],"names":[],"mappings":";mCAakD,OAAO;AAOzD;;;GAGG;AACH,6CAkBC;AAED;;;GAGG;AACH,uCAFa,MAAO,IAAI,CA+CvB;AAED;;;GAGG;AACH,6CAFa,MAAO,IAAI,CAwCvB;AAED;;;GAGG;AACH,0CAEC;AAED;;;GAGG;AACH,qDAEC"}
@@ -1 +1 @@
1
- {"version":3,"file":"working-spec.d.ts","sourceRoot":"","sources":["../../src/generators/working-spec.js"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,mDAFa,MAAM,CAqJlB;AAED;;;;GAIG;AACH,mDAHW,MAAM,uBA8BhB"}
1
+ {"version":3,"file":"working-spec.d.ts","sourceRoot":"","sources":["../../src/generators/working-spec.js"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,mDAFa,MAAM,CAsLlB;AAED;;;;GAIG;AACH,mDAHW,MAAM,uBA8BhB"}