claude-code-templates 1.1.7 → 1.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 +70 -16
- package/package.json +20 -5
- package/src/file-operations.js +44 -1
- package/src/hook-scanner.js +271 -0
- package/src/index.js +18 -2
- package/src/prompts.js +67 -1
- package/src/templates.js +12 -15
- package/templates/javascript-typescript/.claude/settings.json +7 -7
package/README.md
CHANGED
|
@@ -30,8 +30,9 @@ npx claude-code-templates
|
|
|
30
30
|
The installer will:
|
|
31
31
|
1. 🔍 **Auto-detect** your project type (JavaScript, Python, etc.)
|
|
32
32
|
2. 🎯 **Ask about frameworks** (React, Django, Flask, etc.)
|
|
33
|
-
3.
|
|
34
|
-
4.
|
|
33
|
+
3. 📋 **Let you choose commands** (testing, linting, debugging)
|
|
34
|
+
4. 🔧 **Let you select automation hooks** (formatting, type checking, etc.)
|
|
35
|
+
5. ✅ **Confirm before installing**
|
|
35
36
|
|
|
36
37
|
### Step 4: Start Using Claude Code
|
|
37
38
|
```bash
|
|
@@ -110,8 +111,8 @@ create-claude-config
|
|
|
110
111
|
- **Common** - Universal configuration for any project
|
|
111
112
|
- **JavaScript/TypeScript** - Modern JS/TS development with frameworks
|
|
112
113
|
- **Python** - Python development with popular frameworks
|
|
113
|
-
- **Rust** -
|
|
114
|
-
- **Go** -
|
|
114
|
+
- **Rust** - Rust development with Cargo and clippy
|
|
115
|
+
- **Go** - Go development with modules and testing
|
|
115
116
|
|
|
116
117
|
## Supported Frameworks
|
|
117
118
|
|
|
@@ -130,22 +131,63 @@ create-claude-config
|
|
|
130
131
|
|
|
131
132
|
- 🔍 **Auto-detection** - Automatically detects your project type
|
|
132
133
|
- 🎯 **Framework-specific** - Includes framework-specific commands and configurations
|
|
134
|
+
- 🔧 **Selective automation hooks** - Choose which automation features to enable
|
|
133
135
|
- 💾 **Backup existing files** - Safely backs up existing CLAUDE.md and .claude directories
|
|
134
|
-
- ⚙️ **Customizable** - Interactive prompts for
|
|
136
|
+
- ⚙️ **Customizable** - Interactive prompts for command and hook selection
|
|
135
137
|
- 🚀 **Quick setup** - Get started with Claude Code in seconds
|
|
136
138
|
|
|
139
|
+
## 🔧 Automation Hooks
|
|
140
|
+
|
|
141
|
+
The CLI allows you to selectively enable automation hooks that enhance your development workflow:
|
|
142
|
+
|
|
143
|
+
### JavaScript/TypeScript Hooks
|
|
144
|
+
- **Console.log Detection** - Prevents committing debug statements
|
|
145
|
+
- **Prettier Auto-formatting** - Automatically formats code on save
|
|
146
|
+
- **TypeScript Type Checking** - Validates types after each edit
|
|
147
|
+
- **Import Validation** - Warns about performance-impacting wildcard imports
|
|
148
|
+
- **Auto-testing** - Runs relevant tests when files change
|
|
149
|
+
- **ESLint Integration** - Lints code before session ends
|
|
150
|
+
|
|
151
|
+
### Python Hooks
|
|
152
|
+
- **Print Statement Detection** - Warns about debug print statements
|
|
153
|
+
- **Black Auto-formatting** - Formats Python code automatically
|
|
154
|
+
- **Import Sorting** - Organizes imports with isort
|
|
155
|
+
- **Flake8 Linting** - Checks code quality standards
|
|
156
|
+
- **MyPy Type Checking** - Validates type hints
|
|
157
|
+
- **Security Auditing** - Scans for vulnerabilities with bandit
|
|
158
|
+
|
|
159
|
+
### Go Hooks
|
|
160
|
+
- **Print Statement Detection** - Warns about fmt.Print statements
|
|
161
|
+
- **Auto-formatting** - Formats code with gofmt and goimports
|
|
162
|
+
- **Go Vet Analysis** - Runs static analysis checks
|
|
163
|
+
- **Dependency Checking** - Lists and validates dependencies
|
|
164
|
+
- **Auto-testing** - Runs tests for modified files
|
|
165
|
+
|
|
166
|
+
### Rust Hooks
|
|
167
|
+
- **Print Macro Detection** - Warns about println! statements
|
|
168
|
+
- **Rustfmt Formatting** - Automatically formats Rust code
|
|
169
|
+
- **Cargo Check** - Validates code compilation
|
|
170
|
+
- **Clippy Linting** - Runs Rust's built-in linter
|
|
171
|
+
- **Security Auditing** - Checks for vulnerable dependencies
|
|
172
|
+
|
|
137
173
|
## What Gets Installed
|
|
138
174
|
|
|
139
175
|
### Core Files
|
|
140
176
|
- `CLAUDE.md` - Main configuration file for Claude Code
|
|
141
|
-
- `.claude/settings.json` - Language-specific settings
|
|
177
|
+
- `.claude/settings.json` - Language-specific settings with selected automation hooks
|
|
142
178
|
- `.claude/commands/` - Custom commands for common tasks
|
|
143
|
-
|
|
179
|
+
|
|
180
|
+
### Automation Hooks
|
|
181
|
+
Each language template includes selectable automation hooks for:
|
|
182
|
+
- **PreToolUse**: Code quality checks before actions (e.g., detecting console.log statements)
|
|
183
|
+
- **PostToolUse**: Automatic formatting and validation after edits (e.g., Prettier, ESLint, type checking)
|
|
184
|
+
- **Stop**: Final checks before session ends (e.g., linting changed files, bundle analysis)
|
|
185
|
+
- **Notification**: Logging and monitoring of Claude Code activities
|
|
144
186
|
|
|
145
187
|
### Language-Specific Commands
|
|
146
188
|
Each language template includes optimized commands for:
|
|
147
|
-
- Testing
|
|
148
|
-
- Linting and formatting
|
|
189
|
+
- Testing (Jest, pytest, Cargo test, Go test)
|
|
190
|
+
- Linting and formatting (ESLint/Prettier, Black/flake8, clippy/rustfmt, gofmt/go vet)
|
|
149
191
|
- Building and deployment
|
|
150
192
|
- Debugging
|
|
151
193
|
- Framework-specific operations
|
|
@@ -213,28 +255,40 @@ Target directory: /path/to/your/project
|
|
|
213
255
|
- Shows relevant frameworks for your language
|
|
214
256
|
- Auto-detected from dependencies
|
|
215
257
|
|
|
216
|
-
4. **
|
|
258
|
+
4. **Command Selection** 📋
|
|
217
259
|
```
|
|
218
|
-
|
|
260
|
+
📋 Select commands to include (use space to select):
|
|
219
261
|
◉ Enhanced testing commands
|
|
220
262
|
◉ Code linting and formatting
|
|
221
263
|
◯ Git hooks integration
|
|
222
264
|
◯ Debugging helpers
|
|
223
265
|
```
|
|
224
266
|
|
|
225
|
-
5. **
|
|
267
|
+
5. **Hook Selection** 🔧
|
|
268
|
+
```
|
|
269
|
+
🔧 Select automation hooks to include (use space to select):
|
|
270
|
+
◉ PreToolUse: Block files containing console.log statements
|
|
271
|
+
◉ PostToolUse: Auto-format JavaScript/TypeScript files with Prettier
|
|
272
|
+
◉ PostToolUse: Run TypeScript type checking after edits
|
|
273
|
+
◯ PostToolUse: Run tests automatically for modified files
|
|
274
|
+
◉ Stop: Run ESLint on changed files before stopping
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
6. **Final Confirmation** 🚀
|
|
226
278
|
```
|
|
227
|
-
🚀 Setup Claude Code for javascript-typescript with react? (Y/n)
|
|
279
|
+
🚀 Setup Claude Code for javascript-typescript with react (5 commands) (9 hooks)? (Y/n)
|
|
228
280
|
```
|
|
229
|
-
- Review your choices
|
|
281
|
+
- Review your choices including selected commands and hooks
|
|
230
282
|
- Type 'n' to cancel, 'y' or Enter to proceed
|
|
231
283
|
|
|
232
|
-
|
|
284
|
+
7. **Installation** 📁
|
|
233
285
|
```
|
|
234
286
|
📋 Existing CLAUDE.md backed up to CLAUDE.md.backup
|
|
235
287
|
✓ Copied javascript-typescript/CLAUDE.md → CLAUDE.md
|
|
236
|
-
✓ Copied javascript-typescript/.claude → .claude
|
|
288
|
+
✓ Copied javascript-typescript/.claude → .claude (with selected hooks)
|
|
237
289
|
✓ Copied react-specific commands → .claude/commands
|
|
290
|
+
📋 Installed 5 commands
|
|
291
|
+
🔧 Installed 9 automation hooks
|
|
238
292
|
✅ Claude Code configuration setup complete!
|
|
239
293
|
```
|
|
240
294
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI tool to setup Claude Code configurations for different programming languages",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "CLI tool to setup Claude Code configurations with selective automation hooks for different programming languages",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-claude-config": "bin/create-claude-config.js",
|
|
@@ -11,11 +11,19 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"start": "node bin/create-claude-config.js",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "./test-commands.sh",
|
|
15
|
+
"test:detailed": "./test-detailed.sh",
|
|
16
|
+
"test:react": "make test-react",
|
|
17
|
+
"test:vue": "make test-vue",
|
|
18
|
+
"test:node": "make test-node",
|
|
19
|
+
"test:all": "make test",
|
|
20
|
+
"dev:link": "npm link",
|
|
21
|
+
"dev:unlink": "npm unlink -g claude-code-templates",
|
|
22
|
+
"pretest": "npm run dev:link",
|
|
15
23
|
"sync": "node scripts/sync-templates.js",
|
|
16
24
|
"presync": "echo \"🔄 Starting template synchronization...\"",
|
|
17
25
|
"postsync": "echo \"✅ Synchronization completed. Ready to publish!\"",
|
|
18
|
-
"prepublishOnly": "npm run sync"
|
|
26
|
+
"prepublishOnly": "npm run sync && npm run test"
|
|
19
27
|
},
|
|
20
28
|
"keywords": [
|
|
21
29
|
"claude",
|
|
@@ -24,7 +32,14 @@
|
|
|
24
32
|
"configuration",
|
|
25
33
|
"template",
|
|
26
34
|
"setup",
|
|
27
|
-
"cli"
|
|
35
|
+
"cli",
|
|
36
|
+
"hooks",
|
|
37
|
+
"automation",
|
|
38
|
+
"javascript",
|
|
39
|
+
"typescript",
|
|
40
|
+
"python",
|
|
41
|
+
"rust",
|
|
42
|
+
"go"
|
|
28
43
|
],
|
|
29
44
|
"author": "Claude Code Templates",
|
|
30
45
|
"license": "MIT",
|
package/src/file-operations.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
+
const { getHooksForLanguage, filterHooksBySelection } = require('./hook-scanner');
|
|
4
5
|
|
|
5
6
|
async function copyTemplateFiles(templateConfig, targetDir) {
|
|
6
7
|
const templateDir = path.join(__dirname, '../templates');
|
|
@@ -74,6 +75,10 @@ async function copyTemplateFiles(templateConfig, targetDir) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
console.log(chalk.green(`✓ Copied base configuration and commands ${file.source} → ${file.destination}`));
|
|
78
|
+
} else if (file.source.includes('settings.json') && templateConfig.selectedHooks) {
|
|
79
|
+
// Handle settings.json with hook filtering
|
|
80
|
+
await processSettingsFile(sourcePath, destPath, templateConfig);
|
|
81
|
+
console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination} (with selected hooks)`));
|
|
77
82
|
} else {
|
|
78
83
|
// Copy regular files (CLAUDE.md, settings.json, etc.)
|
|
79
84
|
await fs.copy(sourcePath, destPath, {
|
|
@@ -111,6 +116,43 @@ async function copyTemplateFiles(templateConfig, targetDir) {
|
|
|
111
116
|
|
|
112
117
|
console.log(chalk.cyan(`📋 Installed ${templateConfig.selectedCommands.length} commands`));
|
|
113
118
|
}
|
|
119
|
+
|
|
120
|
+
// Report hook selection
|
|
121
|
+
if (templateConfig.selectedHooks && templateConfig.selectedHooks.length > 0) {
|
|
122
|
+
console.log(chalk.magenta(`🔧 Installed ${templateConfig.selectedHooks.length} automation hooks`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function processSettingsFile(sourcePath, destPath, templateConfig) {
|
|
127
|
+
try {
|
|
128
|
+
// Read the original settings file
|
|
129
|
+
const originalSettings = JSON.parse(await fs.readFile(sourcePath, 'utf8'));
|
|
130
|
+
|
|
131
|
+
// If hooks are selected, filter them
|
|
132
|
+
if (templateConfig.selectedHooks && templateConfig.selectedHooks.length > 0) {
|
|
133
|
+
const availableHooks = getHooksForLanguage(templateConfig.language);
|
|
134
|
+
const filteredSettings = filterHooksBySelection(
|
|
135
|
+
originalSettings,
|
|
136
|
+
templateConfig.selectedHooks,
|
|
137
|
+
availableHooks
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Write the filtered settings
|
|
141
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
142
|
+
await fs.writeFile(destPath, JSON.stringify(filteredSettings, null, 2));
|
|
143
|
+
} else {
|
|
144
|
+
// No hooks selected, copy original without hooks
|
|
145
|
+
const settingsWithoutHooks = { ...originalSettings };
|
|
146
|
+
delete settingsWithoutHooks.hooks;
|
|
147
|
+
|
|
148
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
149
|
+
await fs.writeFile(destPath, JSON.stringify(settingsWithoutHooks, null, 2));
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(chalk.red(`Failed to process settings file: ${error.message}`));
|
|
153
|
+
// Fallback to copying original file
|
|
154
|
+
await fs.copy(sourcePath, destPath);
|
|
155
|
+
}
|
|
114
156
|
}
|
|
115
157
|
|
|
116
158
|
async function ensureDirectoryExists(dirPath) {
|
|
@@ -137,5 +179,6 @@ async function checkWritePermissions(targetDir) {
|
|
|
137
179
|
module.exports = {
|
|
138
180
|
copyTemplateFiles,
|
|
139
181
|
ensureDirectoryExists,
|
|
140
|
-
checkWritePermissions
|
|
182
|
+
checkWritePermissions,
|
|
183
|
+
processSettingsFile
|
|
141
184
|
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts and describes hooks from a settings.json file
|
|
6
|
+
* @param {string} settingsPath - Path to the settings.json file
|
|
7
|
+
* @returns {Array} Array of hook descriptions
|
|
8
|
+
*/
|
|
9
|
+
function getHooksFromSettings(settingsPath) {
|
|
10
|
+
if (!fs.existsSync(settingsPath)) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
16
|
+
const hooks = [];
|
|
17
|
+
|
|
18
|
+
if (settings.hooks) {
|
|
19
|
+
// Process PreToolUse hooks
|
|
20
|
+
if (settings.hooks.PreToolUse) {
|
|
21
|
+
settings.hooks.PreToolUse.forEach((hookGroup, index) => {
|
|
22
|
+
hookGroup.hooks.forEach((hook, hookIndex) => {
|
|
23
|
+
const hookId = `pre-${index}-${hookIndex}`;
|
|
24
|
+
const description = getHookDescription(hook, hookGroup.matcher, 'PreToolUse');
|
|
25
|
+
hooks.push({
|
|
26
|
+
id: hookId,
|
|
27
|
+
type: 'PreToolUse',
|
|
28
|
+
matcher: hookGroup.matcher,
|
|
29
|
+
description,
|
|
30
|
+
originalHook: hook,
|
|
31
|
+
originalGroup: hookGroup,
|
|
32
|
+
checked: true // Default to checked
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Process PostToolUse hooks
|
|
39
|
+
if (settings.hooks.PostToolUse) {
|
|
40
|
+
settings.hooks.PostToolUse.forEach((hookGroup, index) => {
|
|
41
|
+
hookGroup.hooks.forEach((hook, hookIndex) => {
|
|
42
|
+
const hookId = `post-${index}-${hookIndex}`;
|
|
43
|
+
const description = getHookDescription(hook, hookGroup.matcher, 'PostToolUse');
|
|
44
|
+
hooks.push({
|
|
45
|
+
id: hookId,
|
|
46
|
+
type: 'PostToolUse',
|
|
47
|
+
matcher: hookGroup.matcher,
|
|
48
|
+
description,
|
|
49
|
+
originalHook: hook,
|
|
50
|
+
originalGroup: hookGroup,
|
|
51
|
+
checked: true // Default to checked
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Process Notification hooks
|
|
58
|
+
if (settings.hooks.Notification) {
|
|
59
|
+
settings.hooks.Notification.forEach((hookGroup, index) => {
|
|
60
|
+
hookGroup.hooks.forEach((hook, hookIndex) => {
|
|
61
|
+
const hookId = `notification-${index}-${hookIndex}`;
|
|
62
|
+
const description = getHookDescription(hook, hookGroup.matcher, 'Notification');
|
|
63
|
+
hooks.push({
|
|
64
|
+
id: hookId,
|
|
65
|
+
type: 'Notification',
|
|
66
|
+
matcher: hookGroup.matcher,
|
|
67
|
+
description,
|
|
68
|
+
originalHook: hook,
|
|
69
|
+
originalGroup: hookGroup,
|
|
70
|
+
checked: false // Default to unchecked for notifications
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Process Stop hooks
|
|
77
|
+
if (settings.hooks.Stop) {
|
|
78
|
+
settings.hooks.Stop.forEach((hookGroup, index) => {
|
|
79
|
+
hookGroup.hooks.forEach((hook, hookIndex) => {
|
|
80
|
+
const hookId = `stop-${index}-${hookIndex}`;
|
|
81
|
+
const description = getHookDescription(hook, hookGroup.matcher, 'Stop');
|
|
82
|
+
hooks.push({
|
|
83
|
+
id: hookId,
|
|
84
|
+
type: 'Stop',
|
|
85
|
+
matcher: hookGroup.matcher,
|
|
86
|
+
description,
|
|
87
|
+
originalHook: hook,
|
|
88
|
+
originalGroup: hookGroup,
|
|
89
|
+
checked: true // Default to checked
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return hooks;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(`Error parsing settings file ${settingsPath}:`, error.message);
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generates a human-readable description for a hook
|
|
105
|
+
* @param {Object} hook - The hook object
|
|
106
|
+
* @param {string} matcher - The matcher pattern
|
|
107
|
+
* @param {string} type - The hook type
|
|
108
|
+
* @returns {string} Human-readable description
|
|
109
|
+
*/
|
|
110
|
+
function getHookDescription(hook, matcher, type) {
|
|
111
|
+
const command = hook.command || '';
|
|
112
|
+
|
|
113
|
+
// Pre-defined descriptions for common hooks
|
|
114
|
+
const descriptions = {
|
|
115
|
+
// PreToolUse hooks
|
|
116
|
+
'jq -r': 'Log Bash commands to ~/.claude/bash-command-log.txt',
|
|
117
|
+
'console\\.log': 'Block files containing console.log statements',
|
|
118
|
+
'print(': 'Block Python files containing print statements',
|
|
119
|
+
'fmt.Print': 'Block Go files containing fmt.Print statements',
|
|
120
|
+
'println!': 'Block Rust files containing println! statements',
|
|
121
|
+
'npm audit': 'Check for vulnerable dependencies when modifying package.json',
|
|
122
|
+
'pip-audit': 'Check for vulnerable Python dependencies',
|
|
123
|
+
'cargo audit': 'Check for vulnerable Rust dependencies',
|
|
124
|
+
'go list': 'Check Go dependencies',
|
|
125
|
+
|
|
126
|
+
// PostToolUse hooks
|
|
127
|
+
'prettier --write': 'Auto-format JavaScript/TypeScript files with Prettier',
|
|
128
|
+
'black': 'Auto-format Python files with Black',
|
|
129
|
+
'isort': 'Sort Python imports with isort',
|
|
130
|
+
'gofmt': 'Auto-format Go files with gofmt',
|
|
131
|
+
'goimports': 'Auto-format Go imports with goimports',
|
|
132
|
+
'rustfmt': 'Auto-format Rust files with rustfmt',
|
|
133
|
+
'tsc --noEmit': 'Run TypeScript type checking after edits',
|
|
134
|
+
'flake8': 'Run Python code quality checks with flake8',
|
|
135
|
+
'mypy': 'Run Python type checking with mypy',
|
|
136
|
+
'go vet': 'Run Go code analysis with go vet',
|
|
137
|
+
'cargo check': 'Run Rust code checking with cargo check',
|
|
138
|
+
'cargo clippy': 'Run Rust linting with clippy',
|
|
139
|
+
'import \\* from': 'Warn about wildcard imports (bad for tree-shaking)',
|
|
140
|
+
'npx jest': 'Run JavaScript/TypeScript tests automatically',
|
|
141
|
+
'pytest': 'Run Python tests automatically',
|
|
142
|
+
'go test': 'Run Go tests automatically',
|
|
143
|
+
'cargo test': 'Run Rust tests automatically',
|
|
144
|
+
|
|
145
|
+
// Stop hooks
|
|
146
|
+
'eslint': 'Run ESLint on changed files before stopping',
|
|
147
|
+
'flake8.*git diff': 'Run Python linting on changed files',
|
|
148
|
+
'bandit': 'Run Python security analysis',
|
|
149
|
+
'go vet.*git diff': 'Run Go analysis on changed files',
|
|
150
|
+
'staticcheck': 'Run Go static analysis',
|
|
151
|
+
'cargo clippy.*git diff': 'Run Rust linting on changed files',
|
|
152
|
+
'bundlesize': 'Analyze bundle size impact of changes',
|
|
153
|
+
|
|
154
|
+
// Notification hooks
|
|
155
|
+
'notifications.log': 'Log Claude Code notifications'
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Try to match command with known descriptions
|
|
159
|
+
for (const [pattern, description] of Object.entries(descriptions)) {
|
|
160
|
+
if (command.includes(pattern)) {
|
|
161
|
+
return description;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Generate description based on command analysis
|
|
166
|
+
if (command.includes('eslint')) {
|
|
167
|
+
return 'Run ESLint linting';
|
|
168
|
+
} else if (command.includes('prettier')) {
|
|
169
|
+
return 'Format code with Prettier';
|
|
170
|
+
} else if (command.includes('tsc')) {
|
|
171
|
+
return 'TypeScript type checking';
|
|
172
|
+
} else if (command.includes('jest') || command.includes('vitest')) {
|
|
173
|
+
return 'Run tests automatically';
|
|
174
|
+
} else if (command.includes('audit')) {
|
|
175
|
+
return 'Security audit for dependencies';
|
|
176
|
+
} else if (command.includes('bundlesize') || command.includes('bundle')) {
|
|
177
|
+
return 'Bundle size analysis';
|
|
178
|
+
} else if (command.includes('console.log')) {
|
|
179
|
+
return 'Detect console.log statements';
|
|
180
|
+
} else if (command.includes('import')) {
|
|
181
|
+
return 'Import statement validation';
|
|
182
|
+
} else if (command.includes('log')) {
|
|
183
|
+
return 'Logging functionality';
|
|
184
|
+
} else {
|
|
185
|
+
// Fallback: use type and matcher
|
|
186
|
+
const matcherDesc = matcher || 'all tools';
|
|
187
|
+
return `${type} hook for ${matcherDesc}`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Gets hooks for a specific language
|
|
193
|
+
* @param {string} language - The programming language
|
|
194
|
+
* @returns {Array} Array of available hooks for the language
|
|
195
|
+
*/
|
|
196
|
+
function getHooksForLanguage(language) {
|
|
197
|
+
const templateDir = path.join(__dirname, '..', 'templates', language);
|
|
198
|
+
const settingsPath = path.join(templateDir, '.claude', 'settings.json');
|
|
199
|
+
|
|
200
|
+
return getHooksFromSettings(settingsPath);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Filters hooks based on user selection
|
|
205
|
+
* @param {Object} originalSettings - Original settings object
|
|
206
|
+
* @param {Array} selectedHookIds - Array of selected hook IDs
|
|
207
|
+
* @param {Array} availableHooks - Array of available hooks
|
|
208
|
+
* @returns {Object} Filtered settings object
|
|
209
|
+
*/
|
|
210
|
+
function filterHooksBySelection(originalSettings, selectedHookIds, availableHooks) {
|
|
211
|
+
if (!originalSettings.hooks) {
|
|
212
|
+
return originalSettings;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const filteredSettings = JSON.parse(JSON.stringify(originalSettings));
|
|
216
|
+
filteredSettings.hooks = {};
|
|
217
|
+
|
|
218
|
+
// Create a map of selected hooks for quick lookup
|
|
219
|
+
const selectedHooks = new Map();
|
|
220
|
+
availableHooks.forEach(hook => {
|
|
221
|
+
if (selectedHookIds.includes(hook.id)) {
|
|
222
|
+
selectedHooks.set(hook.id, hook);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Group selected hooks by type
|
|
227
|
+
const hooksByType = {
|
|
228
|
+
PreToolUse: [],
|
|
229
|
+
PostToolUse: [],
|
|
230
|
+
Notification: [],
|
|
231
|
+
Stop: []
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
selectedHooks.forEach(hook => {
|
|
235
|
+
if (hooksByType[hook.type]) {
|
|
236
|
+
hooksByType[hook.type].push(hook);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Rebuild hook structure
|
|
241
|
+
Object.keys(hooksByType).forEach(type => {
|
|
242
|
+
if (hooksByType[type].length > 0) {
|
|
243
|
+
filteredSettings.hooks[type] = [];
|
|
244
|
+
|
|
245
|
+
// Group hooks by matcher and originalGroup
|
|
246
|
+
const groupMap = new Map();
|
|
247
|
+
|
|
248
|
+
hooksByType[type].forEach(hook => {
|
|
249
|
+
const groupKey = `${hook.matcher}-${JSON.stringify(hook.originalGroup.matcher)}`;
|
|
250
|
+
if (!groupMap.has(groupKey)) {
|
|
251
|
+
groupMap.set(groupKey, {
|
|
252
|
+
matcher: hook.originalGroup.matcher,
|
|
253
|
+
hooks: []
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
groupMap.get(groupKey).hooks.push(hook.originalHook);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
filteredSettings.hooks[type] = Array.from(groupMap.values());
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return filteredSettings;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
module.exports = {
|
|
267
|
+
getHooksFromSettings,
|
|
268
|
+
getHooksForLanguage,
|
|
269
|
+
filterHooksBySelection,
|
|
270
|
+
getHookDescription
|
|
271
|
+
};
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const { detectProject } = require('./utils');
|
|
|
7
7
|
const { getTemplateConfig } = require('./templates');
|
|
8
8
|
const { createPrompts, interactivePrompts } = require('./prompts');
|
|
9
9
|
const { copyTemplateFiles } = require('./file-operations');
|
|
10
|
+
const { getHooksForLanguage } = require('./hook-scanner');
|
|
10
11
|
|
|
11
12
|
async function createClaudeConfig(options = {}) {
|
|
12
13
|
const targetDir = options.directory || process.cwd();
|
|
@@ -22,10 +23,15 @@ async function createClaudeConfig(options = {}) {
|
|
|
22
23
|
let config;
|
|
23
24
|
if (options.yes) {
|
|
24
25
|
// Use defaults
|
|
26
|
+
const selectedLanguage = options.language || projectInfo.detectedLanguage || 'common';
|
|
27
|
+
const availableHooks = getHooksForLanguage(selectedLanguage);
|
|
28
|
+
const defaultHooks = availableHooks.filter(hook => hook.checked).map(hook => hook.id);
|
|
29
|
+
|
|
25
30
|
config = {
|
|
26
|
-
language:
|
|
31
|
+
language: selectedLanguage,
|
|
27
32
|
framework: options.framework || projectInfo.detectedFramework || 'none',
|
|
28
|
-
features: []
|
|
33
|
+
features: [],
|
|
34
|
+
hooks: defaultHooks
|
|
29
35
|
};
|
|
30
36
|
} else {
|
|
31
37
|
// Interactive prompts with back navigation
|
|
@@ -41,6 +47,12 @@ async function createClaudeConfig(options = {}) {
|
|
|
41
47
|
// Get template configuration
|
|
42
48
|
const templateConfig = getTemplateConfig(config);
|
|
43
49
|
|
|
50
|
+
// Add selected hooks to template config
|
|
51
|
+
if (config.hooks) {
|
|
52
|
+
templateConfig.selectedHooks = config.hooks;
|
|
53
|
+
templateConfig.language = config.language; // Ensure language is available for hook filtering
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
if (options.dryRun) {
|
|
45
57
|
console.log(chalk.yellow('🔍 Dry run - showing what would be copied:'));
|
|
46
58
|
templateConfig.files.forEach(file => {
|
|
@@ -73,6 +85,10 @@ async function createClaudeConfig(options = {}) {
|
|
|
73
85
|
if (config.framework !== 'none') {
|
|
74
86
|
console.log(chalk.yellow(`🎯 Framework-specific commands for ${config.framework} are available`));
|
|
75
87
|
}
|
|
88
|
+
|
|
89
|
+
if (config.hooks && config.hooks.length > 0) {
|
|
90
|
+
console.log(chalk.magenta(`🔧 ${config.hooks.length} automation hooks have been configured`));
|
|
91
|
+
}
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
module.exports = createClaudeConfig;
|
package/src/prompts.js
CHANGED
|
@@ -13,6 +13,7 @@ class CustomCheckboxPrompt extends inquirer.prompt.prompts.checkbox {
|
|
|
13
13
|
inquirer.registerPrompt('checkbox', CustomCheckboxPrompt);
|
|
14
14
|
const { getAvailableLanguages, getFrameworksForLanguage } = require('./templates');
|
|
15
15
|
const { getCommandsForLanguageAndFramework } = require('./command-scanner');
|
|
16
|
+
const { getHooksForLanguage } = require('./hook-scanner');
|
|
16
17
|
|
|
17
18
|
async function interactivePrompts(projectInfo, options = {}) {
|
|
18
19
|
const state = {
|
|
@@ -24,7 +25,7 @@ async function interactivePrompts(projectInfo, options = {}) {
|
|
|
24
25
|
// Build steps array based on options
|
|
25
26
|
if (!options.language) state.steps.push('language');
|
|
26
27
|
if (!options.framework) state.steps.push('framework');
|
|
27
|
-
state.steps.push('commands', 'confirm');
|
|
28
|
+
state.steps.push('commands', 'hooks', 'confirm');
|
|
28
29
|
|
|
29
30
|
while (state.currentStep < state.steps.length) {
|
|
30
31
|
const stepName = state.steps[state.currentStep];
|
|
@@ -143,10 +144,37 @@ function getStepConfig(stepName, currentAnswers, projectInfo, options) {
|
|
|
143
144
|
pageSize: 10
|
|
144
145
|
};
|
|
145
146
|
|
|
147
|
+
case 'hooks':
|
|
148
|
+
const hookLanguage = currentAnswers.language || options.language;
|
|
149
|
+
|
|
150
|
+
if (!hookLanguage || hookLanguage === 'common') {
|
|
151
|
+
return null; // Skip hooks selection for common templates
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const availableHooks = getHooksForLanguage(hookLanguage);
|
|
155
|
+
|
|
156
|
+
if (availableHooks.length === 0) {
|
|
157
|
+
return null; // Skip if no hooks available
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
type: 'checkbox',
|
|
162
|
+
name: 'hooks',
|
|
163
|
+
message: 'Select automation hooks to include (use space to select):',
|
|
164
|
+
choices: availableHooks.map(hook => ({
|
|
165
|
+
value: hook.id,
|
|
166
|
+
name: `${hook.type}: ${hook.description}`,
|
|
167
|
+
checked: hook.checked
|
|
168
|
+
})),
|
|
169
|
+
prefix: chalk.magenta('🔧'),
|
|
170
|
+
pageSize: 15
|
|
171
|
+
};
|
|
172
|
+
|
|
146
173
|
case 'confirm':
|
|
147
174
|
const confirmLanguage = currentAnswers.language || options.language || 'common';
|
|
148
175
|
const confirmFramework = currentAnswers.framework || options.framework || 'none';
|
|
149
176
|
const commandCount = currentAnswers.commands ? currentAnswers.commands.length : 0;
|
|
177
|
+
const hookCount = currentAnswers.hooks ? currentAnswers.hooks.length : 0;
|
|
150
178
|
|
|
151
179
|
let message = `Setup Claude Code for ${chalk.cyan(confirmLanguage)}`;
|
|
152
180
|
if (confirmFramework !== 'none') {
|
|
@@ -155,6 +183,9 @@ function getStepConfig(stepName, currentAnswers, projectInfo, options) {
|
|
|
155
183
|
if (commandCount > 0) {
|
|
156
184
|
message += ` (${chalk.yellow(commandCount)} commands)`;
|
|
157
185
|
}
|
|
186
|
+
if (hookCount > 0) {
|
|
187
|
+
message += ` (${chalk.magenta(hookCount)} hooks)`;
|
|
188
|
+
}
|
|
158
189
|
message += '?';
|
|
159
190
|
|
|
160
191
|
return {
|
|
@@ -250,6 +281,37 @@ function createPrompts(projectInfo, options = {}) {
|
|
|
250
281
|
}
|
|
251
282
|
});
|
|
252
283
|
|
|
284
|
+
// Hook selection
|
|
285
|
+
prompts.push({
|
|
286
|
+
type: 'checkbox',
|
|
287
|
+
name: 'hooks',
|
|
288
|
+
message: 'Select automation hooks to include (use space to select):',
|
|
289
|
+
choices: (answers) => {
|
|
290
|
+
const selectedLanguage = answers.language || options.language;
|
|
291
|
+
|
|
292
|
+
if (!selectedLanguage || selectedLanguage === 'common') {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const availableHooks = getHooksForLanguage(selectedLanguage);
|
|
297
|
+
|
|
298
|
+
return availableHooks.map(hook => ({
|
|
299
|
+
value: hook.id,
|
|
300
|
+
name: `${hook.type}: ${hook.description}`,
|
|
301
|
+
checked: hook.checked
|
|
302
|
+
}));
|
|
303
|
+
},
|
|
304
|
+
prefix: chalk.magenta('🔧'),
|
|
305
|
+
pageSize: 15,
|
|
306
|
+
when: (answers) => {
|
|
307
|
+
const selectedLanguage = answers.language || options.language;
|
|
308
|
+
if (!selectedLanguage || selectedLanguage === 'common') {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
const availableHooks = getHooksForLanguage(selectedLanguage);
|
|
312
|
+
return availableHooks.length > 0;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
253
315
|
|
|
254
316
|
// Confirmation
|
|
255
317
|
prompts.push({
|
|
@@ -259,6 +321,7 @@ function createPrompts(projectInfo, options = {}) {
|
|
|
259
321
|
const language = answers.language || options.language || 'common';
|
|
260
322
|
const framework = answers.framework || options.framework || 'none';
|
|
261
323
|
const commandCount = answers.commands ? answers.commands.length : 0;
|
|
324
|
+
const hookCount = answers.hooks ? answers.hooks.length : 0;
|
|
262
325
|
|
|
263
326
|
let message = `Setup Claude Code for ${chalk.cyan(language)}`;
|
|
264
327
|
if (framework !== 'none') {
|
|
@@ -267,6 +330,9 @@ function createPrompts(projectInfo, options = {}) {
|
|
|
267
330
|
if (commandCount > 0) {
|
|
268
331
|
message += ` (${chalk.yellow(commandCount)} commands)`;
|
|
269
332
|
}
|
|
333
|
+
if (hookCount > 0) {
|
|
334
|
+
message += ` (${chalk.magenta(hookCount)} hooks)`;
|
|
335
|
+
}
|
|
270
336
|
message += '?';
|
|
271
337
|
|
|
272
338
|
return message;
|
package/src/templates.js
CHANGED
|
@@ -47,30 +47,27 @@ const TEMPLATES_CONFIG = {
|
|
|
47
47
|
},
|
|
48
48
|
'python': {
|
|
49
49
|
name: 'Python',
|
|
50
|
-
description: '
|
|
50
|
+
description: 'Optimized for Python development',
|
|
51
51
|
files: [
|
|
52
|
-
{ source: '
|
|
53
|
-
{ source: '
|
|
54
|
-
]
|
|
55
|
-
comingSoon: true
|
|
52
|
+
{ source: 'python/CLAUDE.md', destination: 'CLAUDE.md' },
|
|
53
|
+
{ source: 'python/.claude', destination: '.claude' }
|
|
54
|
+
]
|
|
56
55
|
},
|
|
57
56
|
'rust': {
|
|
58
57
|
name: 'Rust',
|
|
59
|
-
description: '
|
|
58
|
+
description: 'Optimized for Rust development',
|
|
60
59
|
files: [
|
|
61
|
-
{ source: '
|
|
62
|
-
{ source: '
|
|
63
|
-
]
|
|
64
|
-
comingSoon: true
|
|
60
|
+
{ source: 'rust/CLAUDE.md', destination: 'CLAUDE.md' },
|
|
61
|
+
{ source: 'rust/.claude', destination: '.claude' }
|
|
62
|
+
]
|
|
65
63
|
},
|
|
66
64
|
'go': {
|
|
67
65
|
name: 'Go',
|
|
68
|
-
description: '
|
|
66
|
+
description: 'Optimized for Go development',
|
|
69
67
|
files: [
|
|
70
|
-
{ source: '
|
|
71
|
-
{ source: '
|
|
72
|
-
]
|
|
73
|
-
comingSoon: true
|
|
68
|
+
{ source: 'go/CLAUDE.md', destination: 'CLAUDE.md' },
|
|
69
|
+
{ source: 'go/.claude', destination: '.claude' }
|
|
70
|
+
]
|
|
74
71
|
}
|
|
75
72
|
};
|
|
76
73
|
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"defaultMode": "allowEdits"
|
|
27
27
|
},
|
|
28
28
|
"env": {
|
|
29
|
-
"BASH_DEFAULT_TIMEOUT_MS": "
|
|
30
|
-
"BASH_MAX_OUTPUT_LENGTH": "
|
|
29
|
+
"BASH_DEFAULT_TIMEOUT_MS": "60000",
|
|
30
|
+
"BASH_MAX_OUTPUT_LENGTH": "20000",
|
|
31
31
|
"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "1",
|
|
32
32
|
"NODE_ENV": "development"
|
|
33
33
|
},
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"hooks": [
|
|
50
50
|
{
|
|
51
51
|
"type": "command",
|
|
52
|
-
"command": "
|
|
52
|
+
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"); CONTENT=$(echo $STDIN_JSON | jq -r '.tool_input.content // \"\"); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ ]] && echo \"$CONTENT\" | grep -q 'console\\.log'; then echo 'Warning: console.log statements should be removed before committing' >&2; exit 2; fi"
|
|
53
53
|
}
|
|
54
54
|
]
|
|
55
55
|
},
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"hooks": [
|
|
59
59
|
{
|
|
60
60
|
"type": "command",
|
|
61
|
-
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" == \"package.json\" ]]; then echo 'Checking for vulnerable dependencies...';
|
|
61
|
+
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" == \"package.json\" ]]; then echo 'Checking for vulnerable dependencies...'; npm audit --audit-level=moderate; fi",
|
|
62
62
|
"timeout": 60
|
|
63
63
|
}
|
|
64
64
|
]
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"hooks": [
|
|
91
91
|
{
|
|
92
92
|
"type": "command",
|
|
93
|
-
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(ts|tsx)$ ]] && grep -q 'import
|
|
93
|
+
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ ]] && grep -q 'import \\* from' \"$FILE\"; then echo 'Warning: Avoid wildcard imports for better tree-shaking' >&2; exit 2; fi"
|
|
94
94
|
}
|
|
95
95
|
]
|
|
96
96
|
},
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"hooks": [
|
|
100
100
|
{
|
|
101
101
|
"type": "command",
|
|
102
|
-
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ && \"$FILE\" != *\".test.\"* && \"$FILE\" != *\".spec.\"* ]]; then
|
|
102
|
+
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ && \"$FILE\" != *\".test.\"* && \"$FILE\" != *\".spec.\"* ]]; then DIR=$(dirname \"$FILE\"); BASENAME=$(basename \"$FILE\" | sed -E 's/\\.(js|jsx|ts|tsx)$//'); for TEST_FILE in \"$DIR/$BASENAME.test.\"* \"$DIR/$BASENAME.spec.\"*; do if [ -f \"$TEST_FILE\" ]; then echo \"Running tests for $TEST_FILE...\"; if command -v jest >/dev/null 2>&1; then npx jest \"$TEST_FILE\" --passWithNoTests; elif command -v vitest >/dev/null 2>&1; then npx vitest run \"$TEST_FILE\"; fi; break; fi; done; fi",
|
|
103
103
|
"timeout": 60
|
|
104
104
|
}
|
|
105
105
|
]
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"hooks": [
|
|
133
133
|
{
|
|
134
134
|
"type": "command",
|
|
135
|
-
"command": "if [[ -f package.json && $(git status --porcelain | grep -E '\\.js$|\\.jsx$|\\.ts$|\\.tsx$') ]]; then echo 'Analyzing bundle size impact...'; npx bundlesize; fi",
|
|
135
|
+
"command": "if [[ -f package.json && $(git status --porcelain | grep -E '\\.js$|\\.jsx$|\\.ts$|\\.tsx$') ]]; then echo 'Analyzing bundle size impact...'; if command -v bundlesize >/dev/null 2>&1; then npx bundlesize; elif npm list webpack-bundle-analyzer >/dev/null 2>&1; then echo 'Use: npx webpack-bundle-analyzer build/static/js/*.js'; else echo 'No bundle analysis tool found'; fi; fi",
|
|
136
136
|
"timeout": 60
|
|
137
137
|
}
|
|
138
138
|
]
|