@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.
- package/dist/budget-derivation.d.ts +74 -0
- package/dist/budget-derivation.d.ts.map +1 -0
- package/dist/cicd-optimizer.d.ts +142 -0
- package/dist/cicd-optimizer.d.ts.map +1 -0
- package/dist/commands/archive.d.ts +51 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +114 -6
- package/dist/commands/burnup.d.ts +6 -0
- package/dist/commands/burnup.d.ts.map +1 -0
- package/dist/commands/burnup.js +109 -10
- package/dist/commands/diagnose.d.ts +52 -0
- package/dist/commands/diagnose.d.ts.map +1 -0
- package/dist/commands/diagnose.js +1 -1
- package/dist/commands/evaluate.d.ts +8 -0
- package/dist/commands/evaluate.d.ts.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/iterate.d.ts +8 -0
- package/dist/commands/iterate.d.ts.map +1 -0
- package/dist/commands/mode.d.ts +24 -0
- package/dist/commands/mode.d.ts.map +1 -0
- package/dist/commands/mode.js +24 -14
- package/dist/commands/plan.d.ts +49 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/provenance.d.ts +32 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +216 -93
- package/dist/commands/quality-gates.d.ts +6 -0
- package/dist/commands/quality-gates.d.ts.map +1 -0
- package/dist/commands/quality-gates.js +82 -3
- package/dist/commands/quality-monitor.d.ts +17 -0
- package/dist/commands/quality-monitor.d.ts.map +1 -0
- package/dist/commands/specs.d.ts +71 -0
- package/dist/commands/specs.d.ts.map +1 -0
- package/dist/commands/specs.js +184 -6
- package/dist/commands/status.d.ts +44 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +134 -10
- package/dist/commands/templates.d.ts +74 -0
- package/dist/commands/templates.d.ts.map +1 -0
- package/dist/commands/templates.js +2 -2
- package/dist/commands/tool.d.ts +13 -0
- package/dist/commands/tool.d.ts.map +1 -0
- package/dist/commands/troubleshoot.d.ts +8 -0
- package/dist/commands/troubleshoot.d.ts.map +1 -0
- package/dist/commands/tutorial.d.ts +55 -0
- package/dist/commands/tutorial.d.ts.map +1 -0
- package/dist/commands/validate.d.ts +15 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/waivers.d.ts +8 -0
- package/dist/commands/waivers.d.ts.map +1 -0
- package/dist/commands/workflow.d.ts +85 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/config/index.d.ts +29 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/modes.d.ts +225 -0
- package/dist/config/modes.d.ts.map +1 -0
- package/dist/constants/spec-types.d.ts +41 -0
- package/dist/constants/spec-types.d.ts.map +1 -0
- package/dist/error-handler.d.ts +164 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +6 -98
- package/dist/generators/jest-config-generator.js +242 -0
- package/dist/generators/jest-config.d.ts +32 -0
- package/dist/generators/jest-config.d.ts.map +1 -0
- package/dist/generators/working-spec.d.ts +13 -0
- package/dist/generators/working-spec.d.ts.map +1 -0
- package/dist/index-new.d.ts +5 -0
- package/dist/index-new.d.ts.map +1 -0
- package/dist/index-new.js +317 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -7
- package/dist/index.js.backup +4711 -0
- package/dist/minimal-cli.d.ts +3 -0
- package/dist/minimal-cli.d.ts.map +1 -0
- package/dist/minimal-cli.js +3 -1
- package/dist/policy/PolicyManager.d.ts +104 -0
- package/dist/policy/PolicyManager.d.ts.map +1 -0
- package/dist/scaffold/claude-hooks.js +316 -0
- package/dist/scaffold/cursor-hooks.d.ts +7 -0
- package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
- package/dist/scaffold/git-hooks.d.ts +38 -0
- package/dist/scaffold/git-hooks.d.ts.map +1 -0
- package/dist/scaffold/index.d.ts +15 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +18 -0
- package/dist/spec/SpecFileManager.d.ts +146 -0
- package/dist/spec/SpecFileManager.d.ts.map +1 -0
- package/dist/templates/.claude/README.md +190 -0
- package/dist/templates/.claude/hooks/audit.sh +96 -0
- package/dist/templates/.claude/hooks/block-dangerous.sh +90 -0
- package/dist/templates/.claude/hooks/naming-check.sh +97 -0
- package/dist/templates/.claude/hooks/quality-check.sh +68 -0
- package/dist/templates/.claude/hooks/scan-secrets.sh +85 -0
- package/dist/templates/.claude/hooks/scope-guard.sh +105 -0
- package/dist/templates/.claude/hooks/validate-spec.sh +76 -0
- package/dist/templates/.claude/settings.json +95 -0
- package/dist/test-analysis.d.ts +182 -0
- package/dist/test-analysis.d.ts.map +1 -0
- package/dist/test-analysis.js +203 -10
- package/dist/tool-interface.d.ts +236 -0
- package/dist/tool-interface.d.ts.map +1 -0
- package/dist/tool-loader.d.ts +77 -0
- package/dist/tool-loader.d.ts.map +1 -0
- package/dist/tool-validator.d.ts +72 -0
- package/dist/tool-validator.d.ts.map +1 -0
- package/dist/utils/async-utils.d.ts +73 -0
- package/dist/utils/async-utils.d.ts.map +1 -0
- package/dist/utils/command-wrapper.d.ts +66 -0
- package/dist/utils/command-wrapper.d.ts.map +1 -0
- package/dist/utils/detection.d.ts +14 -0
- package/dist/utils/detection.d.ts.map +1 -0
- package/dist/utils/error-categories.js +210 -0
- package/dist/utils/finalization.d.ts +17 -0
- package/dist/utils/finalization.d.ts.map +1 -0
- package/dist/utils/git-lock.d.ts +13 -0
- package/dist/utils/git-lock.d.ts.map +1 -0
- package/dist/utils/gitignore-updater.d.ts +39 -0
- package/dist/utils/gitignore-updater.d.ts.map +1 -0
- package/dist/utils/project-analysis.d.ts +34 -0
- package/dist/utils/project-analysis.d.ts.map +1 -0
- package/dist/utils/promise-utils.d.ts +30 -0
- package/dist/utils/promise-utils.d.ts.map +1 -0
- package/dist/utils/quality-gates-utils.js +402 -0
- package/dist/utils/quality-gates.d.ts +49 -0
- package/dist/utils/quality-gates.d.ts.map +1 -0
- package/dist/utils/spec-resolver.d.ts +80 -0
- package/dist/utils/spec-resolver.d.ts.map +1 -0
- package/dist/utils/typescript-detector.d.ts +63 -0
- package/dist/utils/typescript-detector.d.ts.map +1 -0
- package/dist/utils/typescript-detector.js +36 -90
- package/dist/utils/yaml-validation.d.ts +32 -0
- package/dist/utils/yaml-validation.d.ts.map +1 -0
- package/dist/validation/spec-validation.d.ts +43 -0
- package/dist/validation/spec-validation.d.ts.map +1 -0
- package/dist/validation/spec-validation.js +59 -6
- package/dist/waivers-manager.d.ts +167 -0
- package/dist/waivers-manager.d.ts.map +1 -0
- package/package.json +5 -3
- package/templates/.claude/README.md +190 -0
- package/templates/.claude/hooks/audit.sh +96 -0
- package/templates/.claude/hooks/block-dangerous.sh +90 -0
- package/templates/.claude/hooks/naming-check.sh +97 -0
- package/templates/.claude/hooks/quality-check.sh +68 -0
- package/templates/.claude/hooks/scan-secrets.sh +85 -0
- package/templates/.claude/hooks/scope-guard.sh +105 -0
- package/templates/.claude/hooks/validate-spec.sh +76 -0
- 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:
|
|
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
|
-
|
|
353
|
-
|
|
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"}
|
package/dist/commands/specs.js
CHANGED
|
@@ -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
|
-
|
|
168
|
-
console.log(chalk.blue('
|
|
169
|
-
return
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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"}
|
package/dist/commands/status.js
CHANGED
|
@@ -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
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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,
|