@paths.design/caws-cli 7.0.3 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/commands/quality-gates.js +147 -9
  2. package/dist/commands/specs.js +108 -13
  3. package/dist/commands/tool.js +0 -1
  4. package/dist/scaffold/git-hooks.js +159 -58
  5. package/dist/scaffold/index.js +1 -1
  6. package/dist/templates/.caws/tools/README.md +1 -0
  7. package/dist/utils/git-lock.js +1 -0
  8. package/dist/utils/project-analysis.js +176 -16
  9. package/dist/utils/quality-gates.js +7 -6
  10. package/dist/utils/spec-resolver.js +4 -0
  11. package/dist/utils/yaml-validation.js +1 -0
  12. package/package.json +1 -1
  13. package/templates/.caws/tools/README.md +1 -0
  14. package/dist/budget-derivation.d.ts +0 -74
  15. package/dist/budget-derivation.d.ts.map +0 -1
  16. package/dist/cicd-optimizer.d.ts +0 -142
  17. package/dist/cicd-optimizer.d.ts.map +0 -1
  18. package/dist/commands/archive.d.ts +0 -50
  19. package/dist/commands/archive.d.ts.map +0 -1
  20. package/dist/commands/burnup.d.ts +0 -6
  21. package/dist/commands/burnup.d.ts.map +0 -1
  22. package/dist/commands/diagnose.d.ts +0 -52
  23. package/dist/commands/diagnose.d.ts.map +0 -1
  24. package/dist/commands/evaluate.d.ts +0 -8
  25. package/dist/commands/evaluate.d.ts.map +0 -1
  26. package/dist/commands/init.d.ts +0 -5
  27. package/dist/commands/init.d.ts.map +0 -1
  28. package/dist/commands/iterate.d.ts +0 -8
  29. package/dist/commands/iterate.d.ts.map +0 -1
  30. package/dist/commands/mode.d.ts +0 -24
  31. package/dist/commands/mode.d.ts.map +0 -1
  32. package/dist/commands/plan.d.ts +0 -49
  33. package/dist/commands/plan.d.ts.map +0 -1
  34. package/dist/commands/provenance.d.ts +0 -32
  35. package/dist/commands/provenance.d.ts.map +0 -1
  36. package/dist/commands/quality-gates.d.ts +0 -52
  37. package/dist/commands/quality-gates.d.ts.map +0 -1
  38. package/dist/commands/quality-monitor.d.ts +0 -17
  39. package/dist/commands/quality-monitor.d.ts.map +0 -1
  40. package/dist/commands/specs.d.ts +0 -71
  41. package/dist/commands/specs.d.ts.map +0 -1
  42. package/dist/commands/status.d.ts +0 -44
  43. package/dist/commands/status.d.ts.map +0 -1
  44. package/dist/commands/templates.d.ts +0 -74
  45. package/dist/commands/templates.d.ts.map +0 -1
  46. package/dist/commands/tool.d.ts +0 -13
  47. package/dist/commands/tool.d.ts.map +0 -1
  48. package/dist/commands/troubleshoot.d.ts +0 -8
  49. package/dist/commands/troubleshoot.d.ts.map +0 -1
  50. package/dist/commands/tutorial.d.ts +0 -55
  51. package/dist/commands/tutorial.d.ts.map +0 -1
  52. package/dist/commands/validate.d.ts +0 -15
  53. package/dist/commands/validate.d.ts.map +0 -1
  54. package/dist/commands/waivers.d.ts +0 -8
  55. package/dist/commands/waivers.d.ts.map +0 -1
  56. package/dist/commands/workflow.d.ts +0 -85
  57. package/dist/commands/workflow.d.ts.map +0 -1
  58. package/dist/config/index.d.ts +0 -29
  59. package/dist/config/index.d.ts.map +0 -1
  60. package/dist/config/modes.d.ts +0 -225
  61. package/dist/config/modes.d.ts.map +0 -1
  62. package/dist/constants/spec-types.d.ts +0 -41
  63. package/dist/constants/spec-types.d.ts.map +0 -1
  64. package/dist/error-handler.d.ts +0 -164
  65. package/dist/error-handler.d.ts.map +0 -1
  66. package/dist/generators/jest-config.d.ts +0 -32
  67. package/dist/generators/jest-config.d.ts.map +0 -1
  68. package/dist/generators/working-spec.d.ts +0 -13
  69. package/dist/generators/working-spec.d.ts.map +0 -1
  70. package/dist/index-new.d.ts +0 -5
  71. package/dist/index-new.d.ts.map +0 -1
  72. package/dist/index-new.js +0 -317
  73. package/dist/index.d.ts +0 -5
  74. package/dist/index.d.ts.map +0 -1
  75. package/dist/index.js.backup +0 -4711
  76. package/dist/minimal-cli.d.ts +0 -3
  77. package/dist/minimal-cli.d.ts.map +0 -1
  78. package/dist/policy/PolicyManager.d.ts +0 -104
  79. package/dist/policy/PolicyManager.d.ts.map +0 -1
  80. package/dist/scaffold/cursor-hooks.d.ts +0 -7
  81. package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
  82. package/dist/scaffold/git-hooks.d.ts +0 -20
  83. package/dist/scaffold/git-hooks.d.ts.map +0 -1
  84. package/dist/scaffold/index.d.ts +0 -20
  85. package/dist/scaffold/index.d.ts.map +0 -1
  86. package/dist/spec/SpecFileManager.d.ts +0 -146
  87. package/dist/spec/SpecFileManager.d.ts.map +0 -1
  88. package/dist/test-analysis.d.ts +0 -182
  89. package/dist/test-analysis.d.ts.map +0 -1
  90. package/dist/tool-interface.d.ts +0 -236
  91. package/dist/tool-interface.d.ts.map +0 -1
  92. package/dist/tool-loader.d.ts +0 -77
  93. package/dist/tool-loader.d.ts.map +0 -1
  94. package/dist/tool-validator.d.ts +0 -72
  95. package/dist/tool-validator.d.ts.map +0 -1
  96. package/dist/utils/detection.d.ts +0 -7
  97. package/dist/utils/detection.d.ts.map +0 -1
  98. package/dist/utils/finalization.d.ts +0 -17
  99. package/dist/utils/finalization.d.ts.map +0 -1
  100. package/dist/utils/project-analysis.d.ts +0 -14
  101. package/dist/utils/project-analysis.d.ts.map +0 -1
  102. package/dist/utils/quality-gates.d.ts +0 -49
  103. package/dist/utils/quality-gates.d.ts.map +0 -1
  104. package/dist/utils/spec-resolver.d.ts +0 -88
  105. package/dist/utils/spec-resolver.d.ts.map +0 -1
  106. package/dist/utils/typescript-detector.d.ts +0 -63
  107. package/dist/utils/typescript-detector.d.ts.map +0 -1
  108. package/dist/validation/spec-validation.d.ts +0 -43
  109. package/dist/validation/spec-validation.d.ts.map +0 -1
  110. package/dist/waivers-manager.d.ts +0 -167
  111. package/dist/waivers-manager.d.ts.map +0 -1
@@ -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,98 @@ 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
+
216
+ Output.progress('Executing quality gates via npx...');
217
+ Output.info(`Command: ${npxArgs.join(' ')}`);
218
+
219
+ // Execute via npx
220
+ const { execSync: execSyncNpx } = require('child_process');
221
+ execSyncNpx(npxArgs.join(' '), {
222
+ stdio: 'inherit',
223
+ cwd: projectRoot,
224
+ env: {
225
+ ...process.env,
226
+ CAWS_CLI_INTEGRATION: 'true',
227
+ CAWS_CLI_VERSION: require(path.join(cliPackageDir, 'package.json')).version,
228
+ },
229
+ });
230
+
231
+ Output.success('Quality gates completed successfully');
232
+ return;
233
+ } catch (npxError) {
234
+ // npx not available or failed - continue to error message
235
+ }
236
+ }
237
+
238
+ // If still no runner found, provide helpful error with language-agnostic suggestions
239
+ if (!qualityGatesRunner) {
240
+ // Check if Node.js/npx is available (language-agnostic check)
241
+ let hasNodeJs = false;
242
+ try {
243
+ const { execSync } = require('child_process');
244
+ execSync('command -v node', { encoding: 'utf8', stdio: 'ignore' });
245
+ execSync('command -v npx', { encoding: 'utf8', stdio: 'ignore' });
246
+ hasNodeJs = true;
247
+ } catch (e) {
248
+ // Node.js/npx not available
249
+ }
250
+
251
+ const suggestions = [];
252
+
253
+ if (hasNodeJs) {
254
+ // Node.js available - suggest npx (works for any language, no installation)
255
+ suggestions.push(
256
+ 'Use npx (no installation required): npx --yes @paths.design/quality-gates'
257
+ );
258
+ suggestions.push('Install globally: npm install -g @paths.design/quality-gates');
259
+ suggestions.push('Install locally: npm install --save-dev @paths.design/quality-gates');
260
+ } else {
261
+ // Node.js not available - suggest installation or alternatives
262
+ suggestions.push('Install Node.js to use quality gates: https://nodejs.org/');
263
+ suggestions.push(
264
+ 'Then use: npx --yes @paths.design/quality-gates (no installation required)'
265
+ );
266
+ suggestions.push('Or install globally: npm install -g @paths.design/quality-gates');
267
+ }
268
+
269
+ // Language-agnostic fallback options (if they exist)
270
+ const pythonScript = path.join(projectRoot, 'scripts', 'simple_gates.py');
271
+ const makefile = path.join(projectRoot, 'Makefile');
272
+
273
+ if (fs.existsSync(pythonScript)) {
274
+ suggestions.push(`Use project script: python3 ${pythonScript} all --tier 2`);
275
+ }
276
+ if (fs.existsSync(makefile)) {
277
+ suggestions.push('Use Makefile target: make caws-gates');
278
+ }
279
+
280
+ suggestions.push('Run from CAWS monorepo root (if developing CAWS itself)');
143
281
 
144
282
  throw new Error(
145
283
  'Quality gates runner not found.\n\n' +
@@ -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',
@@ -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