@paths.design/caws-cli 5.0.1 → 6.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.
- package/README.md +15 -12
- package/dist/commands/provenance.js +27 -22
- package/dist/commands/quality-gates.js +190 -455
- package/dist/commands/specs.js +34 -35
- package/dist/commands/status.js +10 -7
- package/dist/commands/tool.js +63 -63
- package/dist/commands/waivers.js +38 -39
- package/dist/index.js +1 -0
- package/dist/policy/PolicyManager.js +60 -28
- package/dist/scaffold/git-hooks.js +89 -27
- package/dist/scaffold/index.js +25 -0
- package/dist/utils/async-utils.js +188 -0
- package/dist/utils/command-wrapper.js +200 -0
- package/dist/utils/promise-utils.js +72 -0
- package/package.json +1 -1
- package/templates/.cursor/hooks/block-dangerous.sh +8 -2
- package/templates/.cursor/rules/README.md +5 -7
- package/templates/scripts/v3/analysis/todo_analyzer.py +48 -1
- package/templates/.cursor/rules/10-authorship-and-attribution.mdc +0 -15
- package/templates/.cursor/rules/15-sophisticated-todo-detection.mdc +0 -425
- /package/templates/.cursor/rules/{11-documentation-quality-standards.mdc → 10-documentation-quality-standards.mdc} +0 -0
- /package/templates/.cursor/rules/{12-scope-management-waivers.mdc → 11-scope-management-waivers.mdc} +0 -0
- /package/templates/.cursor/rules/{13-implementation-completeness.mdc → 12-implementation-completeness.mdc} +0 -0
- /package/templates/.cursor/rules/{14-language-agnostic-standards.mdc → 13-language-agnostic-standards.mdc} +0 -0
package/dist/commands/specs.js
CHANGED
|
@@ -9,6 +9,7 @@ const path = require('path');
|
|
|
9
9
|
const yaml = require('js-yaml');
|
|
10
10
|
const chalk = require('chalk');
|
|
11
11
|
const { safeAsync, outputResult } = require('../error-handler');
|
|
12
|
+
const { question, closeReadline } = require('../utils/promise-utils');
|
|
12
13
|
const { SPEC_TYPES } = require('../constants/spec-types');
|
|
13
14
|
|
|
14
15
|
// Import suggestFeatureBreakdown from spec-resolver
|
|
@@ -488,42 +489,40 @@ async function migrateFromLegacy(options = {}) {
|
|
|
488
489
|
* @returns {Promise<string>} User's choice: 'cancel', 'rename', 'merge', 'override'
|
|
489
490
|
*/
|
|
490
491
|
async function askConflictResolution() {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
input: process.stdin,
|
|
504
|
-
output: process.stdout,
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
rl.question('> ', (answer) => {
|
|
508
|
-
rl.close();
|
|
509
|
-
|
|
510
|
-
const trimmed = answer.trim().toLowerCase();
|
|
511
|
-
|
|
512
|
-
// Handle numeric choices
|
|
513
|
-
if (trimmed === '1' || trimmed === 'cancel') {
|
|
514
|
-
resolve('cancel');
|
|
515
|
-
} else if (trimmed === '2' || trimmed === 'rename') {
|
|
516
|
-
resolve('rename');
|
|
517
|
-
} else if (trimmed === '3' || trimmed === 'merge') {
|
|
518
|
-
resolve('merge');
|
|
519
|
-
} else if (trimmed === '4' || trimmed === 'override') {
|
|
520
|
-
resolve('override');
|
|
521
|
-
} else {
|
|
522
|
-
console.log(chalk.red('❌ Invalid choice. Defaulting to cancel.'));
|
|
523
|
-
resolve('cancel');
|
|
524
|
-
}
|
|
525
|
-
});
|
|
492
|
+
const readline = require('readline');
|
|
493
|
+
|
|
494
|
+
console.log(chalk.blue('\n🔄 Conflict Resolution Options:'));
|
|
495
|
+
console.log(chalk.gray(" 1. Cancel - Don't create the spec"));
|
|
496
|
+
console.log(chalk.gray(' 2. Rename - Create with auto-generated name'));
|
|
497
|
+
console.log(chalk.gray(' 3. Merge - Merge with existing spec (not implemented)'));
|
|
498
|
+
console.log(chalk.gray(' 4. Override - Replace existing spec (use --force)'));
|
|
499
|
+
console.log(chalk.yellow('\nEnter your choice (1-4) or the option name:'));
|
|
500
|
+
|
|
501
|
+
const rl = readline.createInterface({
|
|
502
|
+
input: process.stdin,
|
|
503
|
+
output: process.stdout,
|
|
526
504
|
});
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
const answer = await question(rl, '> ');
|
|
508
|
+
const trimmed = answer.trim().toLowerCase();
|
|
509
|
+
|
|
510
|
+
// Handle numeric choices
|
|
511
|
+
if (trimmed === '1' || trimmed === 'cancel') {
|
|
512
|
+
return 'cancel';
|
|
513
|
+
} else if (trimmed === '2' || trimmed === 'rename') {
|
|
514
|
+
return 'rename';
|
|
515
|
+
} else if (trimmed === '3' || trimmed === 'merge') {
|
|
516
|
+
return 'merge';
|
|
517
|
+
} else if (trimmed === '4' || trimmed === 'override') {
|
|
518
|
+
return 'override';
|
|
519
|
+
} else {
|
|
520
|
+
console.log(chalk.red('❌ Invalid choice. Defaulting to cancel.'));
|
|
521
|
+
return 'cancel';
|
|
522
|
+
}
|
|
523
|
+
} finally {
|
|
524
|
+
await closeReadline(rl);
|
|
525
|
+
}
|
|
527
526
|
}
|
|
528
527
|
|
|
529
528
|
/**
|
package/dist/commands/status.js
CHANGED
|
@@ -9,6 +9,7 @@ const path = require('path');
|
|
|
9
9
|
const yaml = require('js-yaml');
|
|
10
10
|
const chalk = require('chalk');
|
|
11
11
|
const { safeAsync, outputResult } = require('../error-handler');
|
|
12
|
+
const { parallel } = require('../utils/async-utils');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Load working specification (legacy single file approach)
|
|
@@ -758,13 +759,15 @@ async function statusCommand(options = {}) {
|
|
|
758
759
|
const modes = require('../config/modes');
|
|
759
760
|
const currentMode = await modes.getCurrentMode();
|
|
760
761
|
|
|
761
|
-
// Load all status data
|
|
762
|
-
const spec = await
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
762
|
+
// Load all status data in parallel for better performance
|
|
763
|
+
const [spec, specs, hooks, provenance, waivers, gates] = await parallel([
|
|
764
|
+
() => loadWorkingSpec(options.spec || '.caws/working-spec.yaml'),
|
|
765
|
+
() => loadSpecsFromMultiSpec(),
|
|
766
|
+
() => checkGitHooks(),
|
|
767
|
+
() => loadProvenanceChain(),
|
|
768
|
+
() => loadWaiverStatus(),
|
|
769
|
+
() => checkQualityGates(),
|
|
770
|
+
]);
|
|
768
771
|
|
|
769
772
|
// Display status (visual mode if requested)
|
|
770
773
|
if (options.visual || options.json) {
|
package/dist/commands/tool.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const
|
|
8
|
+
const { commandWrapper, Output } = require('../utils/command-wrapper');
|
|
9
9
|
|
|
10
10
|
// Import tool system
|
|
11
11
|
const ToolLoader = require('../tool-loader');
|
|
@@ -32,16 +32,19 @@ async function initializeToolSystem() {
|
|
|
32
32
|
// Set up event listeners for tool system
|
|
33
33
|
toolLoader.on('discovery:complete', ({ tools: _tools, count }) => {
|
|
34
34
|
if (count > 0) {
|
|
35
|
-
|
|
35
|
+
Output.info(`Discovered ${count} tools`);
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
toolLoader.on('tool:loaded', ({ id, metadata }) => {
|
|
40
|
-
|
|
40
|
+
// Only log in verbose mode or when not using JSON output
|
|
41
|
+
if (!process.env.CAWS_OUTPUT_FORMAT || process.env.CAWS_OUTPUT_FORMAT !== 'json') {
|
|
42
|
+
console.log(` ✓ Loaded tool: ${metadata.name} (${id})`);
|
|
43
|
+
}
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
toolLoader.on('tool:error', ({ id, error }) => {
|
|
44
|
-
|
|
47
|
+
Output.warning(`Failed to load tool ${id}: ${error}`);
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
// Auto-discover tools on initialization
|
|
@@ -49,8 +52,10 @@ async function initializeToolSystem() {
|
|
|
49
52
|
|
|
50
53
|
return toolLoader;
|
|
51
54
|
} catch (error) {
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
Output.warning(
|
|
56
|
+
`Tool system initialization failed: ${error.message}`,
|
|
57
|
+
'Continuing without dynamic tools'
|
|
58
|
+
);
|
|
54
59
|
return null;
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -61,75 +66,70 @@ async function initializeToolSystem() {
|
|
|
61
66
|
* @param {Object} options - Command options
|
|
62
67
|
*/
|
|
63
68
|
async function executeTool(toolId, options) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
return commandWrapper(
|
|
70
|
+
async () => {
|
|
71
|
+
// Initialize tool system
|
|
72
|
+
const loader = await initializeToolSystem();
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
74
|
+
if (!loader) {
|
|
75
|
+
throw new Error('Tool system not available');
|
|
76
|
+
}
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
// Load all tools first
|
|
79
|
+
await loader.loadAllTools();
|
|
80
|
+
const tool = loader.getTool(toolId);
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
if (!tool) {
|
|
83
|
+
const tools = loader.getAllTools();
|
|
84
|
+
const availableTools = Array.from(tools, ([id, t]) => `${id}: ${t.metadata.name}`).join(
|
|
85
|
+
', '
|
|
86
|
+
);
|
|
87
|
+
throw new Error(`Tool '${toolId}' not found.\n` + `Available tools: ${availableTools}`);
|
|
83
88
|
}
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
90
|
+
// Validate tool before execution
|
|
91
|
+
const validation = await toolValidator.validateTool(tool);
|
|
92
|
+
if (!validation.valid) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Tool validation failed:\n` + validation.errors.map((e) => ` - ${e}`).join('\n')
|
|
95
|
+
);
|
|
96
|
+
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
// Parse parameters
|
|
99
|
+
let params = {};
|
|
100
|
+
if (options.params) {
|
|
101
|
+
try {
|
|
102
|
+
params = JSON.parse(options.params);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw new Error(`Invalid JSON parameters: ${error.message}`);
|
|
105
|
+
}
|
|
105
106
|
}
|
|
106
|
-
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
Output.progress(`Executing tool: ${tool.metadata.name}`);
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
// Execute tool
|
|
111
|
+
const result = await tool.module.execute(params, {
|
|
112
|
+
workingDirectory: process.cwd(),
|
|
113
|
+
timeout: options.timeout,
|
|
114
|
+
});
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
// Display results
|
|
117
|
+
if (result.success) {
|
|
118
|
+
Output.success('Tool execution successful', {
|
|
119
|
+
output: result.output,
|
|
120
|
+
});
|
|
121
|
+
return result;
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Tool execution failed:\n` + result.errors.map((e) => ` - ${e}`).join('\n')
|
|
125
|
+
);
|
|
121
126
|
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
127
|
-
process.exit(1);
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
commandName: `tool ${toolId}`,
|
|
130
|
+
context: { toolId, options },
|
|
128
131
|
}
|
|
129
|
-
|
|
130
|
-
console.error(chalk.red(`❌ Error executing tool ${toolId}:`), error.message);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
132
|
+
);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
module.exports = {
|
package/dist/commands/waivers.js
CHANGED
|
@@ -14,6 +14,7 @@ const yaml = require('js-yaml');
|
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const { initializeGlobalSetup } = require('../config');
|
|
16
16
|
const WaiversManager = require('../waivers-manager');
|
|
17
|
+
const { commandWrapper, Output } = require('../utils/command-wrapper');
|
|
17
18
|
|
|
18
19
|
const WAIVER_DIR = '.caws/waivers';
|
|
19
20
|
|
|
@@ -24,46 +25,44 @@ const WAIVER_DIR = '.caws/waivers';
|
|
|
24
25
|
* @param {object} options - Command options
|
|
25
26
|
*/
|
|
26
27
|
async function waiversCommand(subcommand = 'list', options = {}) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
28
|
+
return commandWrapper(
|
|
29
|
+
async () => {
|
|
30
|
+
Output.info('Detecting CAWS setup...');
|
|
31
|
+
const setup = initializeGlobalSetup();
|
|
32
|
+
|
|
33
|
+
if (setup.hasWorkingSpec) {
|
|
34
|
+
Output.success(`Detected ${setup.setupType} CAWS setup`, {
|
|
35
|
+
capabilities: setup.capabilities,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Ensure waivers directory exists
|
|
40
|
+
const waiversDir = path.join(process.cwd(), WAIVER_DIR);
|
|
41
|
+
if (!fs.existsSync(waiversDir)) {
|
|
42
|
+
fs.mkdirSync(waiversDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
switch (subcommand) {
|
|
46
|
+
case 'create':
|
|
47
|
+
return await createWaiver(options);
|
|
48
|
+
case 'list':
|
|
49
|
+
return await listWaivers(options);
|
|
50
|
+
case 'show':
|
|
51
|
+
return await showWaiver(options.id, options);
|
|
52
|
+
case 'revoke':
|
|
53
|
+
return await revokeWaiver(options.id, options);
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Unknown waiver subcommand: ${subcommand}.\n` +
|
|
57
|
+
'Available subcommands: create, list, show, revoke'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
commandName: `waivers ${subcommand}`,
|
|
63
|
+
context: { subcommand, options },
|
|
59
64
|
}
|
|
60
|
-
|
|
61
|
-
console.error(chalk.red(`\n❌ Waiver command failed: ${error.message}`));
|
|
62
|
-
if (options.verbose) {
|
|
63
|
-
console.error(error.stack);
|
|
64
|
-
}
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
65
|
+
);
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
/**
|
package/dist/index.js
CHANGED
|
@@ -114,6 +114,7 @@ program
|
|
|
114
114
|
.option('--minimal', 'Only essential components', false)
|
|
115
115
|
.option('--with-codemods', 'Include codemod scripts', false)
|
|
116
116
|
.option('--with-oidc', 'Include OIDC trusted publisher setup', false)
|
|
117
|
+
.option('--with-quality-gates', 'Install quality gates package and scripts', false)
|
|
117
118
|
.action(scaffoldProject);
|
|
118
119
|
|
|
119
120
|
// Validate command
|
|
@@ -54,51 +54,83 @@ class PolicyManager {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Load from file
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
// Load from file - check multiple locations for backward compatibility
|
|
58
|
+
const policyPaths = [
|
|
59
|
+
path.join(projectRoot, '.caws', 'policy.yaml'), // Preferred location
|
|
60
|
+
path.join(projectRoot, '.caws', 'policy', 'tier-policy.json'), // Legacy location
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
let policyPath = null;
|
|
64
|
+
let policyContent = null;
|
|
65
|
+
|
|
66
|
+
// Try each path in order
|
|
67
|
+
for (const candidatePath of policyPaths) {
|
|
68
|
+
try {
|
|
69
|
+
if (await fs.pathExists(candidatePath)) {
|
|
70
|
+
policyPath = candidatePath;
|
|
71
|
+
const content = await fs.readFile(candidatePath, 'utf-8');
|
|
72
|
+
|
|
73
|
+
// Handle JSON format (legacy)
|
|
74
|
+
if (candidatePath.endsWith('.json')) {
|
|
75
|
+
policyContent = JSON.parse(content);
|
|
76
|
+
} else {
|
|
77
|
+
// Handle YAML format (preferred)
|
|
78
|
+
policyContent = yaml.load(content);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Continue to next path if this one fails
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
63
87
|
|
|
88
|
+
if (policyPath && policyContent) {
|
|
64
89
|
// Validate policy structure
|
|
65
|
-
this.validatePolicy(
|
|
90
|
+
this.validatePolicy(policyContent);
|
|
66
91
|
|
|
67
92
|
// Update cache
|
|
68
93
|
if (this.enableCaching) {
|
|
69
94
|
this.policyCache.set(projectRoot, {
|
|
70
|
-
policy,
|
|
95
|
+
policy: policyContent,
|
|
71
96
|
cachedAt: Date.now(),
|
|
72
97
|
ttl: cacheTTL,
|
|
73
98
|
});
|
|
74
99
|
}
|
|
75
100
|
|
|
101
|
+
// Warn if using legacy location
|
|
102
|
+
if (policyPath.endsWith('.json')) {
|
|
103
|
+
console.warn(
|
|
104
|
+
'⚠️ Using legacy policy file location: .caws/policy/tier-policy.json\n' +
|
|
105
|
+
' Migrate to .caws/policy.yaml for better compatibility\n' +
|
|
106
|
+
' Run: caws init --migrate-policy'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
76
110
|
return {
|
|
77
|
-
...
|
|
111
|
+
...policyContent,
|
|
78
112
|
_cacheHit: false,
|
|
79
113
|
_loadDuration: Date.now() - startTime,
|
|
114
|
+
_policyPath: policyPath,
|
|
80
115
|
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const defaultPolicy = this.getDefaultPolicy();
|
|
85
|
-
|
|
86
|
-
if (this.enableCaching) {
|
|
87
|
-
this.policyCache.set(projectRoot, {
|
|
88
|
-
policy: defaultPolicy,
|
|
89
|
-
cachedAt: Date.now(),
|
|
90
|
-
ttl: cacheTTL,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
116
|
+
} else {
|
|
117
|
+
// Policy file doesn't exist - use default
|
|
118
|
+
const defaultPolicy = this.getDefaultPolicy();
|
|
93
119
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
};
|
|
120
|
+
if (this.enableCaching) {
|
|
121
|
+
this.policyCache.set(projectRoot, {
|
|
122
|
+
policy: defaultPolicy,
|
|
123
|
+
cachedAt: Date.now(),
|
|
124
|
+
ttl: cacheTTL,
|
|
125
|
+
});
|
|
100
126
|
}
|
|
101
|
-
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...defaultPolicy,
|
|
130
|
+
_isDefault: true,
|
|
131
|
+
_cacheHit: false,
|
|
132
|
+
_loadDuration: Date.now() - startTime,
|
|
133
|
+
};
|
|
102
134
|
}
|
|
103
135
|
} catch (error) {
|
|
104
136
|
throw new Error(`Policy load failed: ${error.message}`);
|
|
@@ -117,6 +117,7 @@ async function scaffoldGitHooks(projectDir, options = {}) {
|
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* Generate pre-commit hook content with staged file quality gates
|
|
120
|
+
* Implements fallback chain: Node script → CLI → Python scripts → Skip gracefully
|
|
120
121
|
*/
|
|
121
122
|
function generatePreCommitHook(options) {
|
|
122
123
|
const { qualityGates = true, stagedOnly = true } = options;
|
|
@@ -124,6 +125,7 @@ function generatePreCommitHook(options) {
|
|
|
124
125
|
return `#!/bin/bash
|
|
125
126
|
# CAWS Pre-commit Hook
|
|
126
127
|
# Runs validation and quality checks before commits
|
|
128
|
+
# Implements graceful fallback chain to avoid blocking commits
|
|
127
129
|
|
|
128
130
|
set -e
|
|
129
131
|
|
|
@@ -136,39 +138,87 @@ if [ ! -d ".caws" ]; then
|
|
|
136
138
|
exit 0
|
|
137
139
|
fi
|
|
138
140
|
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
# Fallback chain for quality gates:
|
|
142
|
+
# 1. Try Node.js script (if exists)
|
|
143
|
+
# 2. Try CAWS CLI
|
|
144
|
+
# 3. Try Makefile target
|
|
145
|
+
# 4. Try Python scripts
|
|
146
|
+
# 5. Skip gracefully (warn only)
|
|
147
|
+
|
|
148
|
+
QUALITY_GATES_RAN=false
|
|
149
|
+
|
|
150
|
+
# Option 1: Node.js quality gates script
|
|
151
|
+
if [ -f "scripts/quality-gates/run-quality-gates.js" ]; then
|
|
152
|
+
if command -v node >/dev/null 2>&1; then
|
|
153
|
+
echo "📁 Running Node.js quality gates script..."
|
|
154
|
+
if node scripts/quality-gates/run-quality-gates.js; then
|
|
155
|
+
echo "✅ Quality gates passed"
|
|
156
|
+
QUALITY_GATES_RAN=true
|
|
157
|
+
else
|
|
158
|
+
echo "❌ Quality gates failed - commit blocked"
|
|
159
|
+
echo "💡 Fix the violations above before committing"
|
|
160
|
+
exit 1
|
|
161
|
+
fi
|
|
162
|
+
fi
|
|
163
|
+
# Option 2: CAWS CLI validation
|
|
164
|
+
elif command -v caws >/dev/null 2>&1; then
|
|
165
|
+
echo "📋 Running CAWS CLI validation..."
|
|
166
|
+
if caws validate --quiet 2>/dev/null; then
|
|
167
|
+
echo "✅ CAWS validation passed"
|
|
168
|
+
QUALITY_GATES_RAN=true
|
|
143
169
|
else
|
|
144
|
-
echo "
|
|
145
|
-
echo "💡
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
echo "⚠️ CAWS validation failed, but allowing commit (non-blocking)"
|
|
171
|
+
echo "💡 Run 'caws validate' for details"
|
|
172
|
+
QUALITY_GATES_RAN=true
|
|
173
|
+
fi
|
|
174
|
+
# Option 3: Makefile target
|
|
175
|
+
elif [ -f "Makefile" ] && grep -q "caws-validate\\|caws-gates" Makefile; then
|
|
176
|
+
echo "🔧 Running Makefile quality gates..."
|
|
177
|
+
if make caws-validate >/dev/null 2>&1 || make caws-gates >/dev/null 2>&1; then
|
|
178
|
+
echo "✅ Makefile quality gates passed"
|
|
179
|
+
QUALITY_GATES_RAN=true
|
|
180
|
+
else
|
|
181
|
+
echo "⚠️ Makefile quality gates failed, but allowing commit (non-blocking)"
|
|
182
|
+
QUALITY_GATES_RAN=true
|
|
183
|
+
fi
|
|
184
|
+
# Option 4: Python scripts
|
|
185
|
+
elif [ -f "scripts/simple_gates.py" ] && command -v python3 >/dev/null 2>&1; then
|
|
186
|
+
echo "🐍 Running Python quality gates script..."
|
|
187
|
+
if python3 scripts/simple_gates.py all --tier 2 --profile backend-api >/dev/null 2>&1; then
|
|
188
|
+
echo "✅ Python quality gates passed"
|
|
189
|
+
QUALITY_GATES_RAN=true
|
|
190
|
+
else
|
|
191
|
+
echo "⚠️ Python quality gates failed, but allowing commit (non-blocking)"
|
|
192
|
+
QUALITY_GATES_RAN=true
|
|
148
193
|
fi
|
|
194
|
+
# Option 5: Skip gracefully
|
|
149
195
|
else
|
|
150
|
-
echo "⚠️
|
|
151
|
-
echo "💡
|
|
152
|
-
|
|
196
|
+
echo "⚠️ Quality gates not available - skipping"
|
|
197
|
+
echo "💡 Available options:"
|
|
198
|
+
echo " • Install: npm install -g @paths.design/caws-cli"
|
|
199
|
+
echo " • Use Python: python3 scripts/simple_gates.py"
|
|
200
|
+
echo " • Use Makefile: make caws-gates"
|
|
201
|
+
QUALITY_GATES_RAN=true
|
|
153
202
|
fi
|
|
154
203
|
|
|
155
|
-
# Run hidden TODO analysis on staged files only
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if python3 scripts/v3/analysis/todo_analyzer.py
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
204
|
+
# Run hidden TODO analysis on staged files only (if Python available)
|
|
205
|
+
if [ "$QUALITY_GATES_RAN" = true ]; then
|
|
206
|
+
echo "🔍 Checking for hidden TODOs in staged files..."
|
|
207
|
+
if command -v python3 >/dev/null 2>&1 && [ -f "scripts/v3/analysis/todo_analyzer.py" ]; then
|
|
208
|
+
if python3 scripts/v3/analysis/todo_analyzer.py --staged-only --ci-mode --min-confidence 0.8 >/dev/null 2>&1; then
|
|
209
|
+
echo "✅ No critical hidden TODOs found in staged files"
|
|
210
|
+
else
|
|
211
|
+
echo "❌ Critical hidden TODOs detected in staged files - commit blocked"
|
|
212
|
+
echo "💡 Fix stub implementations and placeholder code before committing"
|
|
213
|
+
echo "📖 See docs/PLACEHOLDER-DETECTION-GUIDE.md for classification"
|
|
214
|
+
echo ""
|
|
215
|
+
echo "🔍 Running detailed analysis on staged files..."
|
|
216
|
+
python3 scripts/v3/analysis/todo_analyzer.py --staged-only --min-confidence 0.8
|
|
217
|
+
exit 1
|
|
218
|
+
fi
|
|
219
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
220
|
+
echo "⚠️ Python3 found but TODO analyzer not available - skipping"
|
|
168
221
|
fi
|
|
169
|
-
else
|
|
170
|
-
echo "⚠️ Python3 not found - skipping hidden TODO analysis"
|
|
171
|
-
echo "💡 Install Python3 to enable automatic TODO checking"
|
|
172
222
|
fi
|
|
173
223
|
|
|
174
224
|
echo "✅ All quality checks passed - proceeding with commit"
|
|
@@ -280,6 +330,18 @@ if [ -f "package.json" ]; then
|
|
|
280
330
|
# Don't fail on warnings, just warn
|
|
281
331
|
fi
|
|
282
332
|
fi
|
|
333
|
+
elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
|
|
334
|
+
# Python project security checks
|
|
335
|
+
if command -v pip-audit >/dev/null 2>&1; then
|
|
336
|
+
echo "🔍 Checking Python vulnerabilities..."
|
|
337
|
+
pip-audit --desc 2>/dev/null || echo "⚠️ Install pip-audit for vulnerability checks: pip install pip-audit"
|
|
338
|
+
fi
|
|
339
|
+
elif [ -f "Cargo.toml" ]; then
|
|
340
|
+
# Rust project security checks
|
|
341
|
+
if command -v cargo-audit >/dev/null 2>&1; then
|
|
342
|
+
echo "🔍 Checking Rust vulnerabilities..."
|
|
343
|
+
cargo audit 2>/dev/null || echo "⚠️ Install cargo-audit for vulnerability checks: cargo install cargo-audit"
|
|
344
|
+
fi
|
|
283
345
|
fi
|
|
284
346
|
|
|
285
347
|
echo "🎉 Pre-push checks completed!"
|