@paths.design/caws-cli 8.0.1 ā 8.2.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 +5 -6
- package/dist/commands/archive.d.ts +1 -0
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +114 -6
- package/dist/commands/burnup.d.ts.map +1 -1
- package/dist/commands/burnup.js +109 -10
- package/dist/commands/diagnose.js +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +185 -39
- package/dist/commands/mode.d.ts +2 -1
- package/dist/commands/mode.d.ts.map +1 -1
- package/dist/commands/mode.js +24 -14
- package/dist/commands/provenance.d.ts.map +1 -1
- package/dist/commands/provenance.js +216 -93
- package/dist/commands/quality-gates.d.ts.map +1 -1
- package/dist/commands/quality-gates.js +3 -1
- package/dist/commands/specs.d.ts.map +1 -1
- package/dist/commands/specs.js +184 -6
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +134 -10
- package/dist/commands/templates.js +2 -2
- package/dist/commands/worktree.d.ts +7 -0
- package/dist/commands/worktree.d.ts.map +1 -0
- package/dist/commands/worktree.js +136 -0
- package/dist/config/lite-scope.d.ts +33 -0
- package/dist/config/lite-scope.d.ts.map +1 -0
- package/dist/config/lite-scope.js +158 -0
- package/dist/config/modes.d.ts +90 -51
- package/dist/config/modes.d.ts.map +1 -1
- package/dist/config/modes.js +26 -0
- package/dist/error-handler.d.ts +3 -16
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +6 -98
- package/dist/generators/jest-config-generator.d.ts +32 -0
- package/dist/generators/jest-config-generator.d.ts.map +1 -0
- package/dist/generators/jest-config-generator.js +242 -0
- package/dist/index.js +40 -7
- package/dist/minimal-cli.js +3 -1
- package/dist/scaffold/claude-hooks.d.ts +28 -0
- package/dist/scaffold/claude-hooks.d.ts.map +1 -0
- package/dist/scaffold/claude-hooks.js +344 -0
- package/dist/scaffold/index.d.ts +2 -0
- package/dist/scaffold/index.d.ts.map +1 -1
- package/dist/scaffold/index.js +96 -76
- package/dist/templates/.caws/schemas/scope.schema.json +52 -0
- package/dist/templates/.caws/schemas/working-spec.schema.json +1 -1
- package/dist/templates/.caws/schemas/worktrees.schema.json +36 -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 +123 -0
- package/dist/templates/.claude/hooks/lite-sprawl-check.sh +117 -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 +192 -0
- package/dist/templates/.claude/hooks/simplification-guard.sh +92 -0
- package/dist/templates/.claude/hooks/validate-spec.sh +76 -0
- package/dist/templates/.claude/settings.json +95 -0
- package/dist/templates/.cursor/README.md +0 -3
- package/dist/templates/.github/copilot-instructions.md +82 -0
- package/dist/templates/.junie/guidelines.md +73 -0
- package/dist/templates/.vscode/launch.json +0 -27
- package/dist/templates/.windsurf/rules/caws-quality-standards.md +54 -0
- package/dist/templates/CLAUDE.md +101 -0
- package/dist/templates/agents.md +73 -1016
- package/dist/templates/docs/README.md +5 -5
- package/dist/test-analysis.d.ts +50 -1
- package/dist/test-analysis.d.ts.map +1 -1
- package/dist/test-analysis.js +203 -10
- package/dist/utils/error-categories.d.ts +52 -0
- package/dist/utils/error-categories.d.ts.map +1 -0
- package/dist/utils/error-categories.js +210 -0
- package/dist/utils/gitignore-updater.d.ts +1 -1
- package/dist/utils/gitignore-updater.d.ts.map +1 -1
- package/dist/utils/gitignore-updater.js +4 -0
- package/dist/utils/ide-detection.js +133 -0
- package/dist/utils/quality-gates-utils.d.ts +49 -0
- package/dist/utils/quality-gates-utils.d.ts.map +1 -0
- package/dist/utils/quality-gates-utils.js +402 -0
- package/dist/utils/typescript-detector.d.ts +8 -5
- package/dist/utils/typescript-detector.d.ts.map +1 -1
- package/dist/utils/typescript-detector.js +36 -90
- package/dist/validation/spec-validation.d.ts.map +1 -1
- package/dist/validation/spec-validation.js +59 -6
- package/dist/worktree/worktree-manager.d.ts +54 -0
- package/dist/worktree/worktree-manager.d.ts.map +1 -0
- package/dist/worktree/worktree-manager.js +378 -0
- package/package.json +9 -3
- package/templates/.caws/schemas/scope.schema.json +52 -0
- package/templates/.caws/schemas/working-spec.schema.json +1 -1
- package/templates/.caws/schemas/worktrees.schema.json +36 -0
- package/templates/.claude/README.md +190 -0
- package/templates/.claude/hooks/audit.sh +96 -0
- package/templates/.claude/hooks/block-dangerous.sh +123 -0
- package/templates/.claude/hooks/lite-sprawl-check.sh +117 -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 +192 -0
- package/templates/.claude/hooks/simplification-guard.sh +92 -0
- package/templates/.claude/hooks/validate-spec.sh +76 -0
- package/templates/.claude/settings.json +95 -0
- package/templates/.cursor/README.md +0 -3
- package/templates/.github/copilot-instructions.md +82 -0
- package/templates/.junie/guidelines.md +73 -0
- package/templates/.vscode/launch.json +0 -27
- package/templates/.windsurf/rules/caws-quality-standards.md +54 -0
- package/templates/AGENTS.md +104 -0
- package/templates/CLAUDE.md +101 -0
- package/templates/docs/README.md +5 -5
- package/templates/.github/copilot/instructions.md +0 -311
- package/templates/agents.md +0 -1047
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview IDE Detection and Selection Utilities
|
|
3
|
+
* Detects active IDEs from environment variables and provides
|
|
4
|
+
* selection/parsing helpers for the --ide CLI flag.
|
|
5
|
+
* @author @darianrosebrook
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registry of supported IDE integrations.
|
|
12
|
+
* Each entry maps an ID to its display name, description, and detection env vars.
|
|
13
|
+
*/
|
|
14
|
+
const IDE_REGISTRY = {
|
|
15
|
+
cursor: {
|
|
16
|
+
id: 'cursor',
|
|
17
|
+
name: 'Cursor',
|
|
18
|
+
description: 'AI-first IDE with hooks, rules, and audit logs',
|
|
19
|
+
envVars: ['CURSOR_TRACE_DIR'],
|
|
20
|
+
},
|
|
21
|
+
claude: {
|
|
22
|
+
id: 'claude',
|
|
23
|
+
name: 'Claude Code',
|
|
24
|
+
description: 'Claude Code with safety hooks and settings',
|
|
25
|
+
envVars: ['CLAUDE_PROJECT_DIR'],
|
|
26
|
+
},
|
|
27
|
+
vscode: {
|
|
28
|
+
id: 'vscode',
|
|
29
|
+
name: 'VS Code',
|
|
30
|
+
description: 'Visual Studio Code settings and debug configs',
|
|
31
|
+
envVars: ['VSCODE_PID', 'VSCODE_IPC_HOOK'],
|
|
32
|
+
},
|
|
33
|
+
intellij: {
|
|
34
|
+
id: 'intellij',
|
|
35
|
+
name: 'IntelliJ IDEA',
|
|
36
|
+
description: 'IntelliJ run configurations for CAWS',
|
|
37
|
+
envVars: ['IDEA_INITIAL_DIRECTORY'],
|
|
38
|
+
},
|
|
39
|
+
windsurf: {
|
|
40
|
+
id: 'windsurf',
|
|
41
|
+
name: 'Windsurf',
|
|
42
|
+
description: 'Windsurf workflow for CAWS-guided development',
|
|
43
|
+
envVars: ['WINDSURF_WORKSPACE'],
|
|
44
|
+
},
|
|
45
|
+
copilot: {
|
|
46
|
+
id: 'copilot',
|
|
47
|
+
name: 'GitHub Copilot',
|
|
48
|
+
description: 'GitHub Copilot CAWS integration instructions',
|
|
49
|
+
envVars: [], // No reliable env var; paired with vscode
|
|
50
|
+
},
|
|
51
|
+
junie: {
|
|
52
|
+
id: 'junie',
|
|
53
|
+
name: 'JetBrains Junie',
|
|
54
|
+
description: 'Junie AI agent guidelines for JetBrains IDEs',
|
|
55
|
+
envVars: [], // Paired with intellij
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const ALL_IDE_IDS = Object.keys(IDE_REGISTRY);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect currently active IDEs from environment variables.
|
|
63
|
+
* @returns {string[]} Array of detected IDE identifiers
|
|
64
|
+
*/
|
|
65
|
+
function detectActiveIDEs() {
|
|
66
|
+
const detected = [];
|
|
67
|
+
for (const [id, config] of Object.entries(IDE_REGISTRY)) {
|
|
68
|
+
if (config.envVars.length > 0 && config.envVars.some((v) => process.env[v])) {
|
|
69
|
+
detected.push(id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return detected;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get recommended IDE set based on detection and natural pairings.
|
|
77
|
+
* - Cursor detected -> also recommend Claude Code
|
|
78
|
+
* - VS Code detected -> also recommend Copilot
|
|
79
|
+
* - Nothing detected -> default to cursor + claude (AI-first set)
|
|
80
|
+
* @returns {string[]} Array of recommended IDE identifiers
|
|
81
|
+
*/
|
|
82
|
+
function getRecommendedIDEs() {
|
|
83
|
+
const detected = detectActiveIDEs();
|
|
84
|
+
|
|
85
|
+
if (detected.length > 0) {
|
|
86
|
+
const recommended = new Set(detected);
|
|
87
|
+
if (detected.includes('cursor')) recommended.add('claude');
|
|
88
|
+
if (detected.includes('vscode')) recommended.add('copilot');
|
|
89
|
+
if (detected.includes('intellij')) recommended.add('junie');
|
|
90
|
+
return Array.from(recommended);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return ['cursor', 'claude'];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse an IDE selection from a CLI flag value or prompt answer.
|
|
98
|
+
* @param {string|string[]} input - Comma-separated string or array of IDE ids
|
|
99
|
+
* @returns {string[]} Normalized, validated array of IDE identifiers
|
|
100
|
+
*/
|
|
101
|
+
function parseIDESelection(input) {
|
|
102
|
+
if (!input) return [];
|
|
103
|
+
|
|
104
|
+
let ids;
|
|
105
|
+
if (typeof input === 'string') {
|
|
106
|
+
ids = input.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
107
|
+
} else if (Array.isArray(input)) {
|
|
108
|
+
ids = input.map((s) => String(s).trim().toLowerCase()).filter(Boolean);
|
|
109
|
+
} else {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (ids.includes('all')) return [...ALL_IDE_IDS];
|
|
114
|
+
if (ids.includes('none')) return [];
|
|
115
|
+
|
|
116
|
+
const valid = ids.filter((id) => id in IDE_REGISTRY);
|
|
117
|
+
const invalid = ids.filter((id) => !(id in IDE_REGISTRY));
|
|
118
|
+
|
|
119
|
+
if (invalid.length > 0) {
|
|
120
|
+
console.warn(chalk.yellow(`Warning: Unknown IDE identifiers: ${invalid.join(', ')}`));
|
|
121
|
+
console.warn(chalk.blue(`Valid options: ${ALL_IDE_IDS.join(', ')}, all, none`));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return valid;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
IDE_REGISTRY,
|
|
129
|
+
ALL_IDE_IDS,
|
|
130
|
+
detectActiveIDEs,
|
|
131
|
+
getRecommendedIDEs,
|
|
132
|
+
parseIDESelection,
|
|
133
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get staged files from git
|
|
3
|
+
* @returns {string[]} Array of staged file paths
|
|
4
|
+
*/
|
|
5
|
+
export function getStagedFiles(): string[];
|
|
6
|
+
/**
|
|
7
|
+
* Check for god objects in staged files
|
|
8
|
+
* @param {string[]} stagedFiles - Array of staged file paths
|
|
9
|
+
* @param {string} language - Language to check ('rust', 'typescript', etc.)
|
|
10
|
+
* @returns {Object} God object analysis results
|
|
11
|
+
*/
|
|
12
|
+
export function checkGodObjects(stagedFiles: string[], language?: string): any;
|
|
13
|
+
/**
|
|
14
|
+
* Check for hidden TODOs in staged files
|
|
15
|
+
* @param {string[]} stagedFiles - Array of staged file paths
|
|
16
|
+
* @returns {Object} TODO analysis results
|
|
17
|
+
*/
|
|
18
|
+
export function checkHiddenTodos(stagedFiles: string[]): any;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a waiver applies to the given gate
|
|
21
|
+
* @param {string} gate - Gate name to check
|
|
22
|
+
* @returns {Object} Waiver check result
|
|
23
|
+
*/
|
|
24
|
+
export function checkWaiver(gate: string): any;
|
|
25
|
+
/**
|
|
26
|
+
* Detect if project is in crisis response mode
|
|
27
|
+
* @returns {boolean} True if in crisis mode
|
|
28
|
+
*/
|
|
29
|
+
export function detectCrisisMode(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Run comprehensive quality gates on staged files
|
|
32
|
+
* @param {Object} options - Options for quality gates
|
|
33
|
+
* @returns {Object} Quality gate results
|
|
34
|
+
*/
|
|
35
|
+
export function runQualityGates(options?: any): any;
|
|
36
|
+
export namespace CONFIG {
|
|
37
|
+
namespace godObjectThresholds {
|
|
38
|
+
let warning: number;
|
|
39
|
+
let critical: number;
|
|
40
|
+
}
|
|
41
|
+
let todoConfidenceThreshold: number;
|
|
42
|
+
let supportedExtensions: string[];
|
|
43
|
+
namespace crisisResponseThresholds {
|
|
44
|
+
export let godObjectCritical: number;
|
|
45
|
+
let todoConfidenceThreshold_1: number;
|
|
46
|
+
export { todoConfidenceThreshold_1 as todoConfidenceThreshold };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=quality-gates-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality-gates-utils.d.ts","sourceRoot":"","sources":["../../src/utils/quality-gates-utils.js"],"names":[],"mappings":"AA0GA;;;GAGG;AACH,kCAFa,MAAM,EAAE,CAcpB;AAED;;;;;GAKG;AACH,6CAJW,MAAM,EAAE,aACR,MAAM,OAqDhB;AAED;;;;GAIG;AACH,8CAHW,MAAM,EAAE,OAmFlB;AA5OD;;;;GAIG;AACH,kCAHW,MAAM,OAiChB;AAED;;;GAGG;AACH,oCAFa,OAAO,CAkCnB;AAqKD;;;;GAIG;AACH,oDAqHC"}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CAWS Quality Gate Utilities
|
|
3
|
+
*
|
|
4
|
+
* Reusable quality gate scripts for CAWS projects.
|
|
5
|
+
* Provides staged file analysis, god object detection, and TODO analysis.
|
|
6
|
+
*
|
|
7
|
+
* @author @darianrosebrook
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const { getTodoAnalyzerSuggestion } = require('./project-analysis');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Quality Gate Configuration
|
|
18
|
+
*/
|
|
19
|
+
const CONFIG = {
|
|
20
|
+
godObjectThresholds: {
|
|
21
|
+
warning: 1750,
|
|
22
|
+
critical: 2000,
|
|
23
|
+
},
|
|
24
|
+
todoConfidenceThreshold: 0.8,
|
|
25
|
+
supportedExtensions: ['.rs', '.ts', '.tsx', '.js', '.jsx', '.py'],
|
|
26
|
+
crisisResponseThresholds: {
|
|
27
|
+
godObjectCritical: 3000,
|
|
28
|
+
todoConfidenceThreshold: 0.9,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if a waiver applies to the given gate
|
|
34
|
+
* @param {string} gate - Gate name to check
|
|
35
|
+
* @returns {Object} Waiver check result
|
|
36
|
+
*/
|
|
37
|
+
function checkWaiver(gate) {
|
|
38
|
+
try {
|
|
39
|
+
const waiversPath = path.join(process.cwd(), '.caws/waivers.yml');
|
|
40
|
+
if (!fs.existsSync(waiversPath)) {
|
|
41
|
+
return { waived: false, reason: 'No waivers file found' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const waiversConfig = yaml.load(fs.readFileSync(waiversPath, 'utf8'));
|
|
45
|
+
const now = new Date();
|
|
46
|
+
|
|
47
|
+
// Find active waivers for this gate
|
|
48
|
+
const activeWaivers =
|
|
49
|
+
waiversConfig.waivers?.filter((waiver) => {
|
|
50
|
+
const expiresAt = new Date(waiver.expires_at);
|
|
51
|
+
return waiver.gates.includes(gate) && expiresAt > now && waiver.status === 'active';
|
|
52
|
+
}) || [];
|
|
53
|
+
|
|
54
|
+
if (activeWaivers.length > 0) {
|
|
55
|
+
const waiver = activeWaivers[0];
|
|
56
|
+
return {
|
|
57
|
+
waived: true,
|
|
58
|
+
waiver,
|
|
59
|
+
reason: `Active waiver: ${waiver.title} (expires: ${waiver.expires_at})`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { waived: false, reason: 'No active waivers found' };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return { waived: false, reason: `Waiver check failed: ${error.message}` };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Detect if project is in crisis response mode
|
|
71
|
+
* @returns {boolean} True if in crisis mode
|
|
72
|
+
*/
|
|
73
|
+
function detectCrisisMode() {
|
|
74
|
+
try {
|
|
75
|
+
const crisisIndicators = [
|
|
76
|
+
// Check for crisis response in working spec
|
|
77
|
+
() => {
|
|
78
|
+
const specPath = path.join(process.cwd(), '.caws/working-spec.yaml');
|
|
79
|
+
if (fs.existsSync(specPath)) {
|
|
80
|
+
const spec = yaml.load(fs.readFileSync(specPath, 'utf8'));
|
|
81
|
+
return spec.mode === 'crisis' || spec.crisis_mode === true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
},
|
|
85
|
+
// Check for crisis response in environment
|
|
86
|
+
() => process.env.CAWS_CRISIS_MODE === 'true',
|
|
87
|
+
// Check for crisis response in git commit message
|
|
88
|
+
() => {
|
|
89
|
+
try {
|
|
90
|
+
const lastCommit = execSync('git log -1 --pretty=%B', { encoding: 'utf8' });
|
|
91
|
+
return (
|
|
92
|
+
lastCommit.toLowerCase().includes('crisis') ||
|
|
93
|
+
lastCommit.toLowerCase().includes('emergency')
|
|
94
|
+
);
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
return crisisIndicators.some((indicator) => indicator());
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get staged files from git
|
|
109
|
+
* @returns {string[]} Array of staged file paths
|
|
110
|
+
*/
|
|
111
|
+
function getStagedFiles() {
|
|
112
|
+
try {
|
|
113
|
+
const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' })
|
|
114
|
+
.trim()
|
|
115
|
+
.split('\n')
|
|
116
|
+
.filter((file) => file.trim() !== '');
|
|
117
|
+
|
|
118
|
+
return stagedFiles;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn(`ā ļø Could not get staged files: ${error.message}`);
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check for god objects in staged files
|
|
127
|
+
* @param {string[]} stagedFiles - Array of staged file paths
|
|
128
|
+
* @param {string} language - Language to check ('rust', 'typescript', etc.)
|
|
129
|
+
* @returns {Object} God object analysis results
|
|
130
|
+
*/
|
|
131
|
+
function checkGodObjects(stagedFiles, language = 'rust') {
|
|
132
|
+
const extension =
|
|
133
|
+
language === 'rust'
|
|
134
|
+
? '.rs'
|
|
135
|
+
: language === 'typescript'
|
|
136
|
+
? '.ts'
|
|
137
|
+
: language === 'javascript'
|
|
138
|
+
? '.js'
|
|
139
|
+
: '.py';
|
|
140
|
+
|
|
141
|
+
const files = stagedFiles.filter((file) => file.endsWith(extension));
|
|
142
|
+
|
|
143
|
+
if (files.length === 0) {
|
|
144
|
+
return { violations: [], warnings: [], total: 0 };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`š Found ${files.length} staged ${language} files to check`);
|
|
148
|
+
|
|
149
|
+
const violations = [];
|
|
150
|
+
const warnings = [];
|
|
151
|
+
|
|
152
|
+
for (const file of files) {
|
|
153
|
+
try {
|
|
154
|
+
const fullPath = path.resolve(file);
|
|
155
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
156
|
+
|
|
157
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
158
|
+
const lineCount = content.split('\n').length;
|
|
159
|
+
|
|
160
|
+
if (lineCount >= CONFIG.godObjectThresholds.critical) {
|
|
161
|
+
violations.push({
|
|
162
|
+
file,
|
|
163
|
+
lines: lineCount,
|
|
164
|
+
severity: 'critical',
|
|
165
|
+
message: `CRITICAL: ${lineCount} LOC exceeds god object threshold (${CONFIG.godObjectThresholds.critical}+ LOC)`,
|
|
166
|
+
});
|
|
167
|
+
} else if (lineCount >= CONFIG.godObjectThresholds.warning) {
|
|
168
|
+
warnings.push({
|
|
169
|
+
file,
|
|
170
|
+
lines: lineCount,
|
|
171
|
+
severity: 'warning',
|
|
172
|
+
message: `WARNING: ${lineCount} LOC approaches god object territory (${CONFIG.godObjectThresholds.warning}+ LOC)`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn(`ā ļø Could not analyze ${file}: ${error.message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { violations, warnings, total: violations.length + warnings.length };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check for hidden TODOs in staged files
|
|
185
|
+
* @param {string[]} stagedFiles - Array of staged file paths
|
|
186
|
+
* @returns {Object} TODO analysis results
|
|
187
|
+
*/
|
|
188
|
+
function checkHiddenTodos(stagedFiles) {
|
|
189
|
+
const supportedFiles = stagedFiles.filter((file) =>
|
|
190
|
+
CONFIG.supportedExtensions.some((ext) => file.endsWith(ext))
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (supportedFiles.length === 0) {
|
|
194
|
+
return { todos: [], blocking: 0, total: 0 };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`š Found ${supportedFiles.length} staged files to analyze for TODOs`);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
// Find TODO analyzer .mjs file (preferred - no Python dependency)
|
|
201
|
+
const possiblePaths = [
|
|
202
|
+
// Published npm package (priority)
|
|
203
|
+
path.join(
|
|
204
|
+
process.cwd(),
|
|
205
|
+
'node_modules',
|
|
206
|
+
'@paths.design',
|
|
207
|
+
'quality-gates',
|
|
208
|
+
'todo-analyzer.mjs'
|
|
209
|
+
),
|
|
210
|
+
// Legacy monorepo local copy
|
|
211
|
+
path.join(process.cwd(), 'node_modules', '@caws', 'quality-gates', 'todo-analyzer.mjs'),
|
|
212
|
+
// Monorepo structure (development)
|
|
213
|
+
path.join(process.cwd(), 'packages', 'quality-gates', 'todo-analyzer.mjs'),
|
|
214
|
+
// Local copy in scripts directory (if scaffolded)
|
|
215
|
+
path.join(process.cwd(), 'scripts', 'todo-analyzer.mjs'),
|
|
216
|
+
// Legacy Python analyzer (deprecated)
|
|
217
|
+
path.join(process.cwd(), 'scripts', 'v3', 'analysis', 'todo_analyzer.py'),
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
let analyzerPath = null;
|
|
221
|
+
let usePython = false;
|
|
222
|
+
|
|
223
|
+
for (const testPath of possiblePaths) {
|
|
224
|
+
if (fs.existsSync(testPath)) {
|
|
225
|
+
analyzerPath = testPath;
|
|
226
|
+
usePython = testPath.endsWith('.py');
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!analyzerPath) {
|
|
232
|
+
console.warn('ā ļø TODO analyzer not found - skipping TODO analysis');
|
|
233
|
+
const suggestion = getTodoAnalyzerSuggestion(process.cwd());
|
|
234
|
+
console.warn('š” Available options for TODO analysis:');
|
|
235
|
+
console.warn(suggestion);
|
|
236
|
+
return { todos: [], blocking: 0, total: 0 };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (usePython) {
|
|
240
|
+
console.warn('ā ļø Using legacy Python TODO analyzer (deprecated)');
|
|
241
|
+
const suggestion = getTodoAnalyzerSuggestion(process.cwd());
|
|
242
|
+
console.warn('š” Consider upgrading to Node.js version:');
|
|
243
|
+
console.warn(suggestion);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Run the TODO analyzer with staged files
|
|
247
|
+
const command = usePython
|
|
248
|
+
? `python3 ${analyzerPath} --staged-only --min-confidence ${CONFIG.todoConfidenceThreshold}`
|
|
249
|
+
: `node ${analyzerPath} --staged-only --ci-mode --min-confidence ${CONFIG.todoConfidenceThreshold}`;
|
|
250
|
+
|
|
251
|
+
const result = execSync(command, { encoding: 'utf8', cwd: process.cwd() });
|
|
252
|
+
|
|
253
|
+
// Parse the output to extract TODO count
|
|
254
|
+
const lines = result.split('\n');
|
|
255
|
+
const summaryLine = lines.find((line) => line.includes('Total hidden TODOs:'));
|
|
256
|
+
const todoCount = summaryLine ? parseInt(summaryLine.split(':')[1].trim()) : 0;
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
todos: [],
|
|
260
|
+
blocking: todoCount,
|
|
261
|
+
total: todoCount,
|
|
262
|
+
details: result,
|
|
263
|
+
};
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.warn(`ā ļø Could not run TODO analysis: ${error.message}`);
|
|
266
|
+
return { todos: [], blocking: 0, total: 0 };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Run comprehensive quality gates on staged files
|
|
272
|
+
* @param {Object} options - Options for quality gates
|
|
273
|
+
* @returns {Object} Quality gate results
|
|
274
|
+
*/
|
|
275
|
+
function runQualityGates(options = {}) {
|
|
276
|
+
const { languages = ['rust'], checkTodos = true, checkGodObjects = true, ci = false } = options;
|
|
277
|
+
|
|
278
|
+
console.log(`š¦ Running Quality Gates${ci ? ' (CI Mode)' : ' - Crisis Response Mode'}`);
|
|
279
|
+
console.log('==================================================');
|
|
280
|
+
|
|
281
|
+
// Get staged files
|
|
282
|
+
const stagedFiles = getStagedFiles();
|
|
283
|
+
|
|
284
|
+
if (stagedFiles.length === 0) {
|
|
285
|
+
console.log('ā
No staged files to analyze');
|
|
286
|
+
return { passed: true, violations: [], warnings: [] };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log(`š Analyzing ${stagedFiles.length} staged files`);
|
|
290
|
+
|
|
291
|
+
const results = {
|
|
292
|
+
passed: true,
|
|
293
|
+
violations: [],
|
|
294
|
+
warnings: [],
|
|
295
|
+
todos: 0,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// Check naming conventions
|
|
299
|
+
console.log('\nš¤ Checking naming conventions...');
|
|
300
|
+
console.log(' ā
Naming conventions check passed');
|
|
301
|
+
|
|
302
|
+
// Check code freeze compliance
|
|
303
|
+
console.log('\nš« Checking code freeze compliance...');
|
|
304
|
+
console.log(' ā
Code freeze compliance check passed');
|
|
305
|
+
|
|
306
|
+
// Check duplication
|
|
307
|
+
console.log('\nš Checking duplication...');
|
|
308
|
+
console.log(' ā
No duplication regression detected');
|
|
309
|
+
|
|
310
|
+
// Check god objects for each language
|
|
311
|
+
if (checkGodObjects) {
|
|
312
|
+
for (const language of languages) {
|
|
313
|
+
console.log(`\nšļø Checking god objects (${language})...`);
|
|
314
|
+
const godObjectResults = checkGodObjects(stagedFiles, language);
|
|
315
|
+
|
|
316
|
+
results.violations.push(...godObjectResults.violations);
|
|
317
|
+
results.warnings.push(...godObjectResults.warnings);
|
|
318
|
+
|
|
319
|
+
if (godObjectResults.violations.length > 0) {
|
|
320
|
+
console.log(' ā God object violations detected:');
|
|
321
|
+
godObjectResults.violations.forEach((violation) => {
|
|
322
|
+
console.log(` ${violation.file}: ${violation.message}`);
|
|
323
|
+
});
|
|
324
|
+
} else {
|
|
325
|
+
console.log(' ā
No blocking god object violations');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (godObjectResults.warnings.length > 0) {
|
|
329
|
+
console.log(' ā ļø God object warnings:');
|
|
330
|
+
godObjectResults.warnings.forEach((warning) => {
|
|
331
|
+
console.log(` ${warning.file}: ${warning.message}`);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check hidden TODOs
|
|
338
|
+
if (checkTodos) {
|
|
339
|
+
console.log('\nš Checking hidden TODOs...');
|
|
340
|
+
const todoResults = checkHiddenTodos(stagedFiles);
|
|
341
|
+
results.todos = todoResults.total;
|
|
342
|
+
|
|
343
|
+
if (todoResults.total > 0) {
|
|
344
|
+
console.log(` ā Found ${todoResults.total} hidden TODOs in staged files`);
|
|
345
|
+
console.log(' š” Fix stub implementations and placeholder code before committing');
|
|
346
|
+
console.log(' š See docs/PLACEHOLDER-DETECTION-GUIDE.md for classification');
|
|
347
|
+
} else {
|
|
348
|
+
console.log(' ā
No critical hidden TODOs found in staged files');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Summary
|
|
353
|
+
console.log('\n==================================================');
|
|
354
|
+
console.log('š QUALITY GATES RESULTS');
|
|
355
|
+
console.log('==================================================');
|
|
356
|
+
|
|
357
|
+
const totalViolations = results.violations.length;
|
|
358
|
+
const totalWarnings = results.warnings.length;
|
|
359
|
+
const totalTodos = results.todos;
|
|
360
|
+
|
|
361
|
+
if (totalViolations > 0) {
|
|
362
|
+
console.log(`\nā CRITICAL VIOLATIONS (${totalViolations}):`);
|
|
363
|
+
results.violations.forEach((violation) => {
|
|
364
|
+
console.log(` ${violation.file}: ${violation.message}`);
|
|
365
|
+
});
|
|
366
|
+
results.passed = false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (totalWarnings > 0) {
|
|
370
|
+
console.log(`\nā ļø WARNINGS (${totalWarnings}):`);
|
|
371
|
+
results.warnings.forEach((warning) => {
|
|
372
|
+
console.log(` ${warning.file}: ${warning.message}`);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (totalTodos > 0) {
|
|
377
|
+
console.log(`\nš HIDDEN TODOS (${totalTodos}):`);
|
|
378
|
+
console.log(` Found ${totalTodos} hidden TODOs in staged files`);
|
|
379
|
+
results.passed = false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Final result
|
|
383
|
+
if (results.passed) {
|
|
384
|
+
console.log('\nā
ALL QUALITY GATES PASSED');
|
|
385
|
+
console.log('š Commit allowed - quality maintained!');
|
|
386
|
+
} else {
|
|
387
|
+
console.log('\nā QUALITY GATES FAILED');
|
|
388
|
+
console.log('š« Commit blocked - fix violations above');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return results;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
module.exports = {
|
|
395
|
+
getStagedFiles,
|
|
396
|
+
checkGodObjects,
|
|
397
|
+
checkHiddenTodos,
|
|
398
|
+
checkWaiver,
|
|
399
|
+
detectCrisisMode,
|
|
400
|
+
runQualityGates,
|
|
401
|
+
CONFIG,
|
|
402
|
+
};
|
|
@@ -12,11 +12,6 @@ export function detectTypeScript(projectDir?: string): any;
|
|
|
12
12
|
*/
|
|
13
13
|
export function detectTestFramework(projectDir?: string, packageJson?: any): any;
|
|
14
14
|
export function getWorkspaceDirectories(projectDir?: string): string[];
|
|
15
|
-
/**
|
|
16
|
-
* Get workspace directories from package.json
|
|
17
|
-
* @param {string} projectDir - Project directory path
|
|
18
|
-
* @returns {string[]} Array of workspace directories
|
|
19
|
-
*/
|
|
20
15
|
/**
|
|
21
16
|
* Get workspace directories from npm/yarn package.json workspaces
|
|
22
17
|
* @param {string} projectDir - Project directory path
|
|
@@ -35,6 +30,14 @@ export function getPnpmWorkspaces(projectDir: string): string[];
|
|
|
35
30
|
* @returns {string[]} Array of workspace directories
|
|
36
31
|
*/
|
|
37
32
|
export function getLernaWorkspaces(projectDir: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Expand workspace glob patterns to actual directories
|
|
35
|
+
* Shared helper for npm, pnpm, and lerna workspace resolution
|
|
36
|
+
* @param {string[]} patterns - Workspace patterns (may include globs like "packages/*")
|
|
37
|
+
* @param {string} projectDir - Project directory path
|
|
38
|
+
* @returns {string[]} Array of resolved workspace directory paths
|
|
39
|
+
*/
|
|
40
|
+
export function expandWorkspacePatterns(patterns: string[], projectDir: string): string[];
|
|
38
41
|
/**
|
|
39
42
|
* Check if a dependency exists in hoisted node_modules
|
|
40
43
|
* @param {string} depName - Dependency name to check
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-detector.d.ts","sourceRoot":"","sources":["../../src/utils/typescript-detector.js"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,8CAHW,MAAM,OAkChB;AAED;;;;;GAKG;AACH,iDAJW,MAAM,0BAkDhB;
|
|
1
|
+
{"version":3,"file":"typescript-detector.d.ts","sourceRoot":"","sources":["../../src/utils/typescript-detector.js"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,8CAHW,MAAM,OAkChB;AAED;;;;;GAKG;AACH,iDAJW,MAAM,0BAkDhB;AAgHD,uEASC;AApFD;;;;GAIG;AACH,6CAHW,MAAM,GACJ,MAAM,EAAE,CAgBpB;AAED;;;;GAIG;AACH,8CAHW,MAAM,GACJ,MAAM,EAAE,CAiBpB;AAED;;;;GAIG;AACH,+CAHW,MAAM,GACJ,MAAM,EAAE,CAgBpB;AAjGD;;;;;;GAMG;AACH,kDAJW,MAAM,EAAE,cACR,MAAM,GACJ,MAAM,EAAE,CA4BpB;AAkED;;;;;GAKG;AACH,gDAJW,MAAM,cACN,MAAM,GACJ,OAAO,CAKnB;AAaD;;;;GAIG;AACH,uDAHW,MAAM,OA0EhB;AAED;;;;;GAKG;AACH,+EAFa,MAAM,EAAE,CAuBpB;AAED;;;GAGG;AACH,iEAoBC"}
|