@paths.design/caws-cli 8.0.0 → 8.1.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 (149) hide show
  1. package/dist/budget-derivation.d.ts +74 -0
  2. package/dist/budget-derivation.d.ts.map +1 -0
  3. package/dist/cicd-optimizer.d.ts +142 -0
  4. package/dist/cicd-optimizer.d.ts.map +1 -0
  5. package/dist/commands/archive.d.ts +51 -0
  6. package/dist/commands/archive.d.ts.map +1 -0
  7. package/dist/commands/archive.js +114 -6
  8. package/dist/commands/burnup.d.ts +6 -0
  9. package/dist/commands/burnup.d.ts.map +1 -0
  10. package/dist/commands/burnup.js +109 -10
  11. package/dist/commands/diagnose.d.ts +52 -0
  12. package/dist/commands/diagnose.d.ts.map +1 -0
  13. package/dist/commands/diagnose.js +1 -1
  14. package/dist/commands/evaluate.d.ts +8 -0
  15. package/dist/commands/evaluate.d.ts.map +1 -0
  16. package/dist/commands/init.d.ts +5 -0
  17. package/dist/commands/init.d.ts.map +1 -0
  18. package/dist/commands/iterate.d.ts +8 -0
  19. package/dist/commands/iterate.d.ts.map +1 -0
  20. package/dist/commands/mode.d.ts +24 -0
  21. package/dist/commands/mode.d.ts.map +1 -0
  22. package/dist/commands/mode.js +24 -14
  23. package/dist/commands/plan.d.ts +49 -0
  24. package/dist/commands/plan.d.ts.map +1 -0
  25. package/dist/commands/provenance.d.ts +32 -0
  26. package/dist/commands/provenance.d.ts.map +1 -0
  27. package/dist/commands/provenance.js +216 -93
  28. package/dist/commands/quality-gates.d.ts +6 -0
  29. package/dist/commands/quality-gates.d.ts.map +1 -0
  30. package/dist/commands/quality-gates.js +82 -3
  31. package/dist/commands/quality-monitor.d.ts +17 -0
  32. package/dist/commands/quality-monitor.d.ts.map +1 -0
  33. package/dist/commands/specs.d.ts +71 -0
  34. package/dist/commands/specs.d.ts.map +1 -0
  35. package/dist/commands/specs.js +184 -6
  36. package/dist/commands/status.d.ts +44 -0
  37. package/dist/commands/status.d.ts.map +1 -0
  38. package/dist/commands/status.js +134 -10
  39. package/dist/commands/templates.d.ts +74 -0
  40. package/dist/commands/templates.d.ts.map +1 -0
  41. package/dist/commands/templates.js +2 -2
  42. package/dist/commands/tool.d.ts +13 -0
  43. package/dist/commands/tool.d.ts.map +1 -0
  44. package/dist/commands/troubleshoot.d.ts +8 -0
  45. package/dist/commands/troubleshoot.d.ts.map +1 -0
  46. package/dist/commands/tutorial.d.ts +55 -0
  47. package/dist/commands/tutorial.d.ts.map +1 -0
  48. package/dist/commands/validate.d.ts +15 -0
  49. package/dist/commands/validate.d.ts.map +1 -0
  50. package/dist/commands/waivers.d.ts +8 -0
  51. package/dist/commands/waivers.d.ts.map +1 -0
  52. package/dist/commands/workflow.d.ts +85 -0
  53. package/dist/commands/workflow.d.ts.map +1 -0
  54. package/dist/config/index.d.ts +29 -0
  55. package/dist/config/index.d.ts.map +1 -0
  56. package/dist/config/modes.d.ts +225 -0
  57. package/dist/config/modes.d.ts.map +1 -0
  58. package/dist/constants/spec-types.d.ts +41 -0
  59. package/dist/constants/spec-types.d.ts.map +1 -0
  60. package/dist/error-handler.d.ts +164 -0
  61. package/dist/error-handler.d.ts.map +1 -0
  62. package/dist/error-handler.js +6 -98
  63. package/dist/generators/jest-config-generator.js +242 -0
  64. package/dist/generators/jest-config.d.ts +32 -0
  65. package/dist/generators/jest-config.d.ts.map +1 -0
  66. package/dist/generators/working-spec.d.ts +13 -0
  67. package/dist/generators/working-spec.d.ts.map +1 -0
  68. package/dist/index-new.d.ts +5 -0
  69. package/dist/index-new.d.ts.map +1 -0
  70. package/dist/index-new.js +317 -0
  71. package/dist/index.d.ts +5 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +14 -7
  74. package/dist/index.js.backup +4711 -0
  75. package/dist/minimal-cli.d.ts +3 -0
  76. package/dist/minimal-cli.d.ts.map +1 -0
  77. package/dist/minimal-cli.js +3 -1
  78. package/dist/policy/PolicyManager.d.ts +104 -0
  79. package/dist/policy/PolicyManager.d.ts.map +1 -0
  80. package/dist/scaffold/claude-hooks.js +316 -0
  81. package/dist/scaffold/cursor-hooks.d.ts +7 -0
  82. package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
  83. package/dist/scaffold/git-hooks.d.ts +38 -0
  84. package/dist/scaffold/git-hooks.d.ts.map +1 -0
  85. package/dist/scaffold/index.d.ts +15 -0
  86. package/dist/scaffold/index.d.ts.map +1 -0
  87. package/dist/scaffold/index.js +18 -0
  88. package/dist/spec/SpecFileManager.d.ts +146 -0
  89. package/dist/spec/SpecFileManager.d.ts.map +1 -0
  90. package/dist/templates/.claude/README.md +190 -0
  91. package/dist/templates/.claude/hooks/audit.sh +96 -0
  92. package/dist/templates/.claude/hooks/block-dangerous.sh +90 -0
  93. package/dist/templates/.claude/hooks/naming-check.sh +97 -0
  94. package/dist/templates/.claude/hooks/quality-check.sh +68 -0
  95. package/dist/templates/.claude/hooks/scan-secrets.sh +85 -0
  96. package/dist/templates/.claude/hooks/scope-guard.sh +105 -0
  97. package/dist/templates/.claude/hooks/validate-spec.sh +76 -0
  98. package/dist/templates/.claude/settings.json +95 -0
  99. package/dist/test-analysis.d.ts +182 -0
  100. package/dist/test-analysis.d.ts.map +1 -0
  101. package/dist/test-analysis.js +203 -10
  102. package/dist/tool-interface.d.ts +236 -0
  103. package/dist/tool-interface.d.ts.map +1 -0
  104. package/dist/tool-loader.d.ts +77 -0
  105. package/dist/tool-loader.d.ts.map +1 -0
  106. package/dist/tool-validator.d.ts +72 -0
  107. package/dist/tool-validator.d.ts.map +1 -0
  108. package/dist/utils/async-utils.d.ts +73 -0
  109. package/dist/utils/async-utils.d.ts.map +1 -0
  110. package/dist/utils/command-wrapper.d.ts +66 -0
  111. package/dist/utils/command-wrapper.d.ts.map +1 -0
  112. package/dist/utils/detection.d.ts +14 -0
  113. package/dist/utils/detection.d.ts.map +1 -0
  114. package/dist/utils/error-categories.js +210 -0
  115. package/dist/utils/finalization.d.ts +17 -0
  116. package/dist/utils/finalization.d.ts.map +1 -0
  117. package/dist/utils/git-lock.d.ts +13 -0
  118. package/dist/utils/git-lock.d.ts.map +1 -0
  119. package/dist/utils/gitignore-updater.d.ts +39 -0
  120. package/dist/utils/gitignore-updater.d.ts.map +1 -0
  121. package/dist/utils/project-analysis.d.ts +34 -0
  122. package/dist/utils/project-analysis.d.ts.map +1 -0
  123. package/dist/utils/promise-utils.d.ts +30 -0
  124. package/dist/utils/promise-utils.d.ts.map +1 -0
  125. package/dist/utils/quality-gates-utils.js +402 -0
  126. package/dist/utils/quality-gates.d.ts +49 -0
  127. package/dist/utils/quality-gates.d.ts.map +1 -0
  128. package/dist/utils/spec-resolver.d.ts +80 -0
  129. package/dist/utils/spec-resolver.d.ts.map +1 -0
  130. package/dist/utils/typescript-detector.d.ts +63 -0
  131. package/dist/utils/typescript-detector.d.ts.map +1 -0
  132. package/dist/utils/typescript-detector.js +36 -90
  133. package/dist/utils/yaml-validation.d.ts +32 -0
  134. package/dist/utils/yaml-validation.d.ts.map +1 -0
  135. package/dist/validation/spec-validation.d.ts +43 -0
  136. package/dist/validation/spec-validation.d.ts.map +1 -0
  137. package/dist/validation/spec-validation.js +59 -6
  138. package/dist/waivers-manager.d.ts +167 -0
  139. package/dist/waivers-manager.d.ts.map +1 -0
  140. package/package.json +5 -3
  141. package/templates/.claude/README.md +190 -0
  142. package/templates/.claude/hooks/audit.sh +96 -0
  143. package/templates/.claude/hooks/block-dangerous.sh +90 -0
  144. package/templates/.claude/hooks/naming-check.sh +97 -0
  145. package/templates/.claude/hooks/quality-check.sh +68 -0
  146. package/templates/.claude/hooks/scan-secrets.sh +85 -0
  147. package/templates/.claude/hooks/scope-guard.sh +105 -0
  148. package/templates/.claude/hooks/validate-spec.sh +76 -0
  149. package/templates/.claude/settings.json +95 -0
@@ -212,6 +212,12 @@ async function qualityGatesCommand(options = {}) {
212
212
  if (options.fix) {
213
213
  npxArgs.push('--fix');
214
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
+ }
215
221
 
216
222
  Output.progress('Executing quality gates via npx...');
217
223
  Output.info(`Command: ${npxArgs.join(' ')}`);
@@ -310,6 +316,13 @@ async function qualityGatesCommand(options = {}) {
310
316
  args.push('--fix');
311
317
  }
312
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
+
313
326
  // Add CAWS-specific environment variables for integration
314
327
  const env = {
315
328
  ...process.env,
@@ -326,9 +339,11 @@ async function qualityGatesCommand(options = {}) {
326
339
  Output.info(`Command: ${args.join(' ')}`);
327
340
 
328
341
  // Execute the quality gates runner with timeout
342
+ // CRITICAL: Must run from projectRoot (user's current directory) so that
343
+ // git commands resolve correctly to the user's repository, not the CLI installation
329
344
  const child = spawn(args[0], args.slice(1), {
330
345
  stdio: 'inherit',
331
- cwd: packagesDir,
346
+ cwd: projectRoot,
332
347
  env: env,
333
348
  });
334
349
 
@@ -336,7 +351,47 @@ async function qualityGatesCommand(options = {}) {
336
351
  const timeoutMs = options.timeout || (options.ci ? 30 * 60 * 1000 : 10 * 60 * 1000);
337
352
 
338
353
  const completionPromise = new Promise((resolve, reject) => {
354
+ let resolved = false;
355
+
356
+ // Handle process termination signals
357
+ const signalHandlers = {
358
+ SIGINT: () => {
359
+ if (!resolved && !child.killed && child.pid) {
360
+ child.kill('SIGINT');
361
+ }
362
+ },
363
+ SIGTERM: () => {
364
+ if (!resolved && !child.killed && child.pid) {
365
+ child.kill('SIGTERM');
366
+ }
367
+ },
368
+ };
369
+
370
+ process.on('SIGINT', signalHandlers.SIGINT);
371
+ process.on('SIGTERM', signalHandlers.SIGTERM);
372
+
373
+ const cleanup = () => {
374
+ if (resolved) return;
375
+ resolved = true;
376
+
377
+ // Remove signal handlers
378
+ try {
379
+ process.removeListener('SIGINT', signalHandlers.SIGINT);
380
+ process.removeListener('SIGTERM', signalHandlers.SIGTERM);
381
+ } catch (e) {
382
+ // Ignore errors removing listeners
383
+ }
384
+
385
+ // Remove event listeners to prevent memory leaks
386
+ try {
387
+ child.removeAllListeners();
388
+ } catch (e) {
389
+ // Ignore errors removing listeners
390
+ }
391
+ };
392
+
339
393
  child.on('close', (code) => {
394
+ cleanup();
340
395
  if (code === 0) {
341
396
  resolve();
342
397
  } else {
@@ -345,12 +400,36 @@ async function qualityGatesCommand(options = {}) {
345
400
  });
346
401
 
347
402
  child.on('error', (error) => {
403
+ cleanup();
348
404
  reject(new Error(`Failed to execute quality gates runner: ${error.message}`));
349
405
  });
350
406
  });
351
407
 
352
- await withTimeout(completionPromise, timeoutMs, 'Quality gates execution timed out');
353
- Output.success('Quality gates completed successfully');
408
+ try {
409
+ await withTimeout(completionPromise, timeoutMs, 'Quality gates execution timed out');
410
+ Output.success('Quality gates completed successfully');
411
+ } catch (error) {
412
+ // Ensure child process is killed on timeout or error
413
+ try {
414
+ if (!child.killed && child.pid) {
415
+ child.kill('SIGTERM');
416
+ // Give it a moment to exit gracefully, then force kill
417
+ // eslint-disable-next-line no-undef
418
+ setTimeout(() => {
419
+ if (!child.killed && child.pid) {
420
+ try {
421
+ child.kill('SIGKILL');
422
+ } catch (e) {
423
+ // Ignore errors killing process
424
+ }
425
+ }
426
+ }, 1000);
427
+ }
428
+ } catch (killError) {
429
+ // Ignore errors killing process
430
+ }
431
+ throw error;
432
+ }
354
433
  },
355
434
  {
356
435
  commandName: 'quality-gates',
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Quality monitor command handler
3
+ *
4
+ * @param {string} action - Type of action to monitor
5
+ * @param {object} options - Command options
6
+ */
7
+ export function qualityMonitorCommand(action: string, options?: object): Promise<void>;
8
+ /**
9
+ * Analyze quality impact of an action
10
+ *
11
+ * @param {string} action - Type of action (file_saved, code_edited, test_run)
12
+ * @param {array} files - Files affected by the action
13
+ * @param {object} context - Additional context information
14
+ * @returns {object} Quality analysis
15
+ */
16
+ export function analyzeQualityImpact(action: string, files?: any[], context?: object): object;
17
+ //# sourceMappingURL=quality-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-monitor.d.ts","sourceRoot":"","sources":["../../src/commands/quality-monitor.js"],"names":[],"mappings":"AAoIA;;;;;GAKG;AACH,8CAHW,MAAM,YACN,MAAM,iBA2HhB;AArPD;;;;;;;GAOG;AACH,6CALW,MAAM,2BAEN,MAAM,GACJ,MAAM,CA8GlB"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Specs command handler
3
+ * @param {string} action - Action to perform (list, create, show, update, delete, conflicts, migrate)
4
+ * @param {Object} options - Command options
5
+ */
6
+ export function specsCommand(action: string, options?: any): Promise<any>;
7
+ /**
8
+ * Load specs registry
9
+ * @returns {Promise<Object>} Registry data
10
+ */
11
+ export function loadSpecsRegistry(): Promise<any>;
12
+ /**
13
+ * Save specs registry
14
+ * @param {Object} registry - Registry data
15
+ * @returns {Promise<void>}
16
+ */
17
+ export function saveSpecsRegistry(registry: any): Promise<void>;
18
+ /**
19
+ * List all spec files in the specs directory
20
+ * @returns {Promise<Array>} Array of spec file info
21
+ */
22
+ export function listSpecFiles(): Promise<any[]>;
23
+ /**
24
+ * Create a new spec file
25
+ * @param {string} id - Spec identifier
26
+ * @param {Object} options - Creation options
27
+ * @returns {Promise<Object>} Created spec info
28
+ */
29
+ export function createSpec(id: string, options?: any): Promise<any>;
30
+ /**
31
+ * Load a specific spec file
32
+ * @param {string} id - Spec identifier
33
+ * @returns {Promise<Object|null>} Spec data or null
34
+ */
35
+ export function loadSpec(id: string): Promise<any | null>;
36
+ /**
37
+ * Update a spec file
38
+ * @param {string} id - Spec identifier
39
+ * @param {Object} updates - Updates to apply
40
+ * @returns {Promise<boolean>} Success status
41
+ */
42
+ export function updateSpec(id: string, updates?: any): Promise<boolean>;
43
+ /**
44
+ * Delete a spec file
45
+ * @param {string} id - Spec identifier
46
+ * @returns {Promise<boolean>} Success status
47
+ */
48
+ export function deleteSpec(id: string): Promise<boolean>;
49
+ /**
50
+ * Display specs in a formatted table
51
+ * @param {Array} specs - Array of spec objects
52
+ */
53
+ export function displaySpecsTable(specs: any[]): void;
54
+ /**
55
+ * Display detailed spec information
56
+ * @param {Object} spec - Spec object
57
+ */
58
+ export function displaySpecDetails(spec: any): void;
59
+ /**
60
+ * Ask user how to resolve spec creation conflicts
61
+ * @returns {Promise<string>} User's choice: 'cancel', 'rename', 'merge', 'override'
62
+ */
63
+ export function askConflictResolution(): Promise<string>;
64
+ /**
65
+ * Specs directory structure
66
+ */
67
+ export const SPECS_DIR: ".caws/specs";
68
+ export const SPECS_REGISTRY: ".caws/specs/registry.json";
69
+ import { SPEC_TYPES } from "../constants/spec-types";
70
+ export { SPEC_TYPES };
71
+ //# sourceMappingURL=specs.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -164,9 +164,9 @@ async function createSpec(id, options = {}) {
164
164
  console.log(chalk.blue(`📝 Creating spec with new name: ${newId}`));
165
165
  return await createSpec(newId, { ...options, interactive: false });
166
166
  } else if (answer === 'merge') {
167
- console.log(chalk.yellow('🔄 Merge functionality not yet implemented.'));
168
- console.log(chalk.blue('💡 For now, consider creating with a different name.'));
169
- return null;
167
+ // Merge new spec data with existing spec
168
+ console.log(chalk.blue('🔄 Merging with existing spec...'));
169
+ return await mergeSpec(id, options);
170
170
  } else if (answer === 'override') {
171
171
  console.log(chalk.yellow('⚠️ Overriding existing spec...'));
172
172
  }
@@ -385,6 +385,133 @@ async function updateSpec(id, updates = {}) {
385
385
  return true;
386
386
  }
387
387
 
388
+ /**
389
+ * Merge new spec data with an existing spec
390
+ * Combines acceptance criteria, updates metadata, preserves history
391
+ * @param {string} id - Spec identifier
392
+ * @param {Object} options - Options including new spec data to merge
393
+ * @returns {Promise<Object>} Merged spec
394
+ */
395
+ async function mergeSpec(id, options = {}) {
396
+ const existingSpec = await loadSpec(id);
397
+ if (!existingSpec) {
398
+ throw new Error(`Spec '${id}' not found`);
399
+ }
400
+
401
+ console.log(chalk.blue(`\n📋 Merging into existing spec: ${id}`));
402
+ console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
403
+
404
+ // Show existing spec summary
405
+ console.log(chalk.gray(`Existing spec:`));
406
+ console.log(chalk.gray(` Title: ${existingSpec.title}`));
407
+ console.log(chalk.gray(` Status: ${existingSpec.status}`));
408
+ console.log(
409
+ chalk.gray(` Acceptance Criteria: ${existingSpec.acceptance_criteria?.length || 0}`)
410
+ );
411
+ console.log('');
412
+
413
+ // Prepare merge data from options
414
+ const {
415
+ title: newTitle,
416
+ description: newDescription,
417
+ acceptance_criteria: newCriteria,
418
+ mode: newMode,
419
+ risk_tier: newRiskTier,
420
+ } = options;
421
+
422
+ const mergedSpec = { ...existingSpec };
423
+
424
+ // Track what was merged
425
+ const mergeLog = [];
426
+
427
+ // Merge title (prefer new if provided)
428
+ if (newTitle && newTitle !== existingSpec.title) {
429
+ mergedSpec.title = newTitle;
430
+ mergeLog.push(`Title updated: "${existingSpec.title}" → "${newTitle}"`);
431
+ }
432
+
433
+ // Merge description
434
+ if (newDescription) {
435
+ if (existingSpec.description) {
436
+ mergedSpec.description = `${existingSpec.description}\n\n---\n\n${newDescription}`;
437
+ mergeLog.push('Description appended');
438
+ } else {
439
+ mergedSpec.description = newDescription;
440
+ mergeLog.push('Description added');
441
+ }
442
+ }
443
+
444
+ // Merge acceptance criteria (append new ones, avoid duplicates)
445
+ if (newCriteria && Array.isArray(newCriteria) && newCriteria.length > 0) {
446
+ const existingCriteria = existingSpec.acceptance_criteria || [];
447
+ const existingIds = new Set(existingCriteria.map((c) => c.id));
448
+
449
+ const criteriaToAdd = newCriteria.filter((c) => !existingIds.has(c.id));
450
+ if (criteriaToAdd.length > 0) {
451
+ mergedSpec.acceptance_criteria = [...existingCriteria, ...criteriaToAdd];
452
+ mergeLog.push(`Added ${criteriaToAdd.length} new acceptance criteria`);
453
+ }
454
+
455
+ // Also update the 'acceptance' array if it exists
456
+ if (existingSpec.acceptance) {
457
+ const existingAcceptIds = new Set(existingSpec.acceptance.map((a) => a.id));
458
+ const acceptToAdd = newCriteria.filter((c) => !existingAcceptIds.has(c.id));
459
+ if (acceptToAdd.length > 0) {
460
+ mergedSpec.acceptance = [...existingSpec.acceptance, ...acceptToAdd];
461
+ }
462
+ }
463
+ }
464
+
465
+ // Merge mode (prefer higher tier if both provided)
466
+ if (newMode && newMode !== existingSpec.mode) {
467
+ // Mode priority: crisis > standard > minimal
468
+ const modePriority = { minimal: 1, standard: 2, crisis: 3 };
469
+ if ((modePriority[newMode] || 0) > (modePriority[existingSpec.mode] || 0)) {
470
+ mergedSpec.mode = newMode;
471
+ mergeLog.push(`Mode upgraded: ${existingSpec.mode} → ${newMode}`);
472
+ }
473
+ }
474
+
475
+ // Merge risk tier (prefer higher risk if both provided)
476
+ if (newRiskTier && newRiskTier !== existingSpec.risk_tier) {
477
+ // Risk priority: T1 > T2 > T3
478
+ const riskPriority = { T3: 1, T2: 2, T1: 3, 3: 1, 2: 2, 1: 3 };
479
+ if ((riskPriority[newRiskTier] || 0) > (riskPriority[existingSpec.risk_tier] || 0)) {
480
+ mergedSpec.risk_tier = newRiskTier;
481
+ mergeLog.push(`Risk tier updated: ${existingSpec.risk_tier} → ${newRiskTier}`);
482
+ }
483
+ }
484
+
485
+ // Update metadata
486
+ mergedSpec.updated_at = new Date().toISOString();
487
+
488
+ // Add merge history entry
489
+ if (!mergedSpec.history) {
490
+ mergedSpec.history = [];
491
+ }
492
+ mergedSpec.history.push({
493
+ action: 'merge',
494
+ timestamp: new Date().toISOString(),
495
+ changes: mergeLog,
496
+ });
497
+
498
+ // Save merged spec
499
+ await updateSpec(id, mergedSpec);
500
+
501
+ // Display merge results
502
+ console.log(chalk.green('✅ Merge completed:'));
503
+ if (mergeLog.length > 0) {
504
+ mergeLog.forEach((change) => {
505
+ console.log(chalk.gray(` • ${change}`));
506
+ });
507
+ } else {
508
+ console.log(chalk.gray(' • No changes needed (specs were identical)'));
509
+ }
510
+ console.log('');
511
+
512
+ return mergedSpec;
513
+ }
514
+
388
515
  /**
389
516
  * Delete a spec file
390
517
  * @param {string} id - Spec identifier
@@ -544,9 +671,12 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
544
671
  let selectedFeatures = features;
545
672
 
546
673
  if (options.interactive) {
547
- // For now, just use all suggested features
548
- // In a full implementation, this would prompt for selection
549
- console.log(chalk.blue('\n📋 Using all suggested features for migration'));
674
+ selectedFeatures = await selectFeaturesInteractively(features);
675
+ if (selectedFeatures.length === 0) {
676
+ console.log(chalk.yellow('⚠️ No features selected. Migration cancelled.'));
677
+ return { migrated: 0, total: features.length, createdSpecs: [], legacySpec: legacySpec.id };
678
+ }
679
+ console.log(chalk.blue(`\n📋 Migrating ${selectedFeatures.length} selected features`));
550
680
  }
551
681
 
552
682
  if (options.features && options.features.length > 0) {
@@ -614,6 +744,54 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
614
744
  };
615
745
  }
616
746
 
747
+ /**
748
+ * Interactive feature selection for migration
749
+ * @param {Array} features - Array of suggested features
750
+ * @returns {Promise<Array>} Selected features
751
+ */
752
+ async function selectFeaturesInteractively(features) {
753
+ const readline = require('readline');
754
+ const rl = readline.createInterface({
755
+ input: process.stdin,
756
+ output: process.stdout,
757
+ });
758
+
759
+ console.log(chalk.cyan('\n📋 Select features to migrate:\n'));
760
+ features.forEach((f, i) => {
761
+ const scope = f.scope?.in?.join(', ') || 'N/A';
762
+ console.log(` ${chalk.yellow(i + 1)}. ${chalk.bold(f.id || f.name)} - ${f.title || f.description}`);
763
+ console.log(chalk.gray(` Scope: ${scope}`));
764
+ });
765
+ console.log(chalk.cyan(`\nEnter numbers separated by commas, or 'all' for all features:`));
766
+ console.log(chalk.gray(`Example: 1,3,5 or all`));
767
+
768
+ try {
769
+ const answer = await question(rl, '> ');
770
+ const trimmed = answer.trim().toLowerCase();
771
+
772
+ if (trimmed === 'all' || trimmed === '*') {
773
+ return features;
774
+ }
775
+
776
+ if (trimmed === '' || trimmed === 'none' || trimmed === 'q' || trimmed === 'quit') {
777
+ return [];
778
+ }
779
+
780
+ // Parse comma-separated numbers
781
+ const indices = trimmed
782
+ .split(',')
783
+ .map(n => parseInt(n.trim(), 10) - 1)
784
+ .filter(i => !isNaN(i) && i >= 0 && i < features.length);
785
+
786
+ // Remove duplicates and sort
787
+ const uniqueIndices = [...new Set(indices)].sort((a, b) => a - b);
788
+
789
+ return features.filter((_, i) => uniqueIndices.includes(i));
790
+ } finally {
791
+ await closeReadline(rl);
792
+ }
793
+ }
794
+
617
795
  /**
618
796
  * Ask user how to resolve spec creation conflicts
619
797
  * @returns {Promise<string>} User's choice: 'cancel', 'rename', 'merge', 'override'
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Status command handler
3
+ * @param {Object} options - Command options
4
+ */
5
+ export function statusCommand(options?: any): Promise<any>;
6
+ /**
7
+ * Load working specification (legacy single file approach)
8
+ * @param {string} specPath - Path to working spec
9
+ * @returns {Promise<Object|null>} Parsed spec or null
10
+ */
11
+ export function loadWorkingSpec(specPath?: string): Promise<any | null>;
12
+ /**
13
+ * Check Git hooks status
14
+ * @returns {Promise<Object>} Hooks status
15
+ */
16
+ export function checkGitHooks(): Promise<any>;
17
+ /**
18
+ * Load provenance chain
19
+ * @returns {Promise<Object>} Provenance status
20
+ */
21
+ export function loadProvenanceChain(): Promise<any>;
22
+ /**
23
+ * Load waiver status
24
+ * @returns {Promise<Object>} Waiver status
25
+ */
26
+ export function loadWaiverStatus(): Promise<any>;
27
+ /**
28
+ * Check quality gates status (simplified)
29
+ * @returns {Promise<Object>} Quality gates status
30
+ */
31
+ export function checkQualityGates(): Promise<any>;
32
+ /**
33
+ * Display project status
34
+ * @param {Object} data - Status data
35
+ */
36
+ export function displayStatus(data: any): void;
37
+ /**
38
+ * Generate actionable suggestions based on status and mode
39
+ * @param {Object} data - Status data
40
+ * @param {string} currentMode - Current CAWS mode
41
+ * @returns {string[]} Array of suggestions
42
+ */
43
+ export function generateSuggestions(data: any, currentMode: string): string[];
44
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.js"],"names":[],"mappings":"AA02BA;;;GAGG;AACH,2DA+IC;AA/+BD;;;;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,CAO3B;AA8HD;;;GAGG;AACH,+CAgGC;AAED;;;;;GAKG;AACH,4DAHW,MAAM,GACJ,MAAM,EAAE,CAoCpB"}
@@ -8,6 +8,7 @@ const fs = require('fs-extra');
8
8
  const path = require('path');
9
9
  const yaml = require('js-yaml');
10
10
  const chalk = require('chalk');
11
+ // child_process removed - execSync no longer used directly
11
12
  const { safeAsync, outputResult } = require('../error-handler');
12
13
  const { parallel } = require('../utils/async-utils');
13
14
 
@@ -181,14 +182,114 @@ async function loadWaiverStatus() {
181
182
  * @returns {Promise<Object>} Quality gates status
182
183
  */
183
184
  async function checkQualityGates() {
184
- // For now, return a placeholder
185
- // Quality gates are available via CLI or MCP
186
185
  return {
187
186
  checked: false,
188
187
  message: 'Run: caws quality-gates or use MCP tool caws_quality_gates_run for full gate status',
189
188
  };
190
189
  }
191
190
 
191
+ /**
192
+ * Get test coverage from coverage reports
193
+ * Looks for common coverage report locations and formats
194
+ * @returns {Promise<Object>} Coverage data with percentage and details
195
+ */
196
+ async function getTestCoverage() {
197
+ const coverageLocations = [
198
+ // Jest/Istanbul coverage
199
+ { path: 'coverage/coverage-summary.json', type: 'istanbul' },
200
+ { path: 'coverage/lcov-report/index.html', type: 'lcov-html' },
201
+ { path: 'coverage/coverage-final.json', type: 'istanbul-final' },
202
+ // NYC coverage
203
+ { path: '.nyc_output/coverage-summary.json', type: 'nyc' },
204
+ // C8 coverage
205
+ { path: 'coverage/c8/coverage-summary.json', type: 'c8' },
206
+ ];
207
+
208
+ try {
209
+ for (const loc of coverageLocations) {
210
+ const coveragePath = path.join(process.cwd(), loc.path);
211
+ if (await fs.pathExists(coveragePath)) {
212
+ if (loc.type === 'istanbul' || loc.type === 'nyc' || loc.type === 'c8') {
213
+ const content = await fs.readFile(coveragePath, 'utf8');
214
+ const data = JSON.parse(content);
215
+
216
+ // Istanbul/NYC format has a "total" key with coverage percentages
217
+ if (data.total) {
218
+ const lines = data.total.lines?.pct || 0;
219
+ const statements = data.total.statements?.pct || 0;
220
+ const branches = data.total.branches?.pct || 0;
221
+ const functions = data.total.functions?.pct || 0;
222
+
223
+ return {
224
+ available: true,
225
+ percentage: Math.round((lines + statements + branches + functions) / 4),
226
+ lines: Math.round(lines),
227
+ statements: Math.round(statements),
228
+ branches: Math.round(branches),
229
+ functions: Math.round(functions),
230
+ source: loc.path,
231
+ };
232
+ }
233
+ } else if (loc.type === 'istanbul-final') {
234
+ // coverage-final.json has per-file data, need to aggregate
235
+ const content = await fs.readFile(coveragePath, 'utf8');
236
+ const data = JSON.parse(content);
237
+
238
+ let totalStatements = 0;
239
+ let coveredStatements = 0;
240
+
241
+ for (const file of Object.values(data)) {
242
+ const s = file.s || {};
243
+ for (const count of Object.values(s)) {
244
+ totalStatements++;
245
+ if (count > 0) coveredStatements++;
246
+ }
247
+ }
248
+
249
+ if (totalStatements > 0) {
250
+ const percentage = Math.round((coveredStatements / totalStatements) * 100);
251
+ return {
252
+ available: true,
253
+ percentage,
254
+ source: loc.path,
255
+ };
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ // Try running test coverage command if no reports found
262
+ try {
263
+ // Check if package.json has a coverage script
264
+ const pkgPath = path.join(process.cwd(), 'package.json');
265
+ if (await fs.pathExists(pkgPath)) {
266
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
267
+ if (pkg.scripts && (pkg.scripts.coverage || pkg.scripts['test:coverage'])) {
268
+ return {
269
+ available: false,
270
+ percentage: null,
271
+ message: 'Run npm run coverage to generate coverage report',
272
+ };
273
+ }
274
+ }
275
+ } catch {
276
+ // Ignore package.json errors
277
+ }
278
+
279
+ return {
280
+ available: false,
281
+ percentage: null,
282
+ message: 'No coverage report found',
283
+ };
284
+ } catch (error) {
285
+ return {
286
+ available: false,
287
+ percentage: null,
288
+ message: `Error reading coverage: ${error.message}`,
289
+ };
290
+ }
291
+ }
292
+
192
293
  /**
193
294
  * Get time since last update
194
295
  * @param {string} timestamp - ISO timestamp
@@ -388,7 +489,7 @@ function getProgressColor(percentage) {
388
489
  * @param {Object} data - Status data
389
490
  * @param {string} currentMode - Current CAWS mode
390
491
  */
391
- function displayVisualStatus(data, currentMode) {
492
+ async function displayVisualStatus(data, currentMode) {
392
493
  const { spec, specs, hooks, provenance, waivers, gates } = data;
393
494
  const modes = require('../config/modes');
394
495
  const tierConfig = modes.getTier(currentMode);
@@ -497,12 +598,35 @@ function displayVisualStatus(data, currentMode) {
497
598
  );
498
599
  }
499
600
 
500
- // Test Coverage (placeholder for now)
501
- console.log(
502
- chalk.gray(
503
- ` Test Coverage: ${chalk.blue('Calculating...')} ${createProgressBar(0, 100)} 0%`
504
- )
505
- );
601
+ // Test Coverage
602
+ const coverage = await getTestCoverage();
603
+ if (coverage.available && coverage.percentage !== null) {
604
+ const coverageColor =
605
+ coverage.percentage >= 80
606
+ ? chalk.green
607
+ : coverage.percentage >= 50
608
+ ? chalk.yellow
609
+ : chalk.red;
610
+ const coverageBar = createProgressBar(coverage.percentage, 100);
611
+ console.log(
612
+ chalk.gray(
613
+ ` Test Coverage: ${coverageColor(`${coverage.percentage}%`)} ${coverageBar}`
614
+ )
615
+ );
616
+ if (coverage.lines !== undefined) {
617
+ console.log(
618
+ chalk.gray(
619
+ ` Lines: ${coverage.lines}% | Branches: ${coverage.branches}% | Functions: ${coverage.functions}%`
620
+ )
621
+ );
622
+ }
623
+ } else {
624
+ console.log(
625
+ chalk.gray(
626
+ ` Test Coverage: ${chalk.yellow('N/A')} ${createProgressBar(0, 100)} ${chalk.gray(coverage.message || 'No report')}`
627
+ )
628
+ );
629
+ }
506
630
 
507
631
  // Risk Tier Indicator
508
632
  const riskColor =
@@ -839,7 +963,7 @@ async function statusCommand(options = {}) {
839
963
  console.log(JSON.stringify(result, null, 2));
840
964
  } else {
841
965
  // Visual output
842
- displayVisualStatus(
966
+ await displayVisualStatus(
843
967
  {
844
968
  spec,
845
969
  specs,