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 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. ⚙️ **Let you choose features** (testing, linting, debugging)
34
- 4. **Confirm before installing**
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** - Coming soon
114
- - **Go** - Coming soon
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 feature selection
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
- - `.claude/hooks/` - Automated hooks for development workflow
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. **Feature Selection** ⚙️
258
+ 4. **Command Selection** 📋
217
259
  ```
218
- ⚙️ Select additional features:
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. **Final Confirmation** 🚀
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
- 6. **Installation** 📁
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.1.7",
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": "echo \"Error: no test specified\" && exit 1",
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",
@@ -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: options.language || projectInfo.detectedLanguage || 'common',
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: 'Configuration for Python projects (Coming Soon)',
50
+ description: 'Optimized for Python development',
51
51
  files: [
52
- { source: 'common/CLAUDE.md', destination: 'CLAUDE.md' },
53
- { source: 'common/.claude', destination: '.claude' }
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: 'Configuration for Rust projects (Coming Soon)',
58
+ description: 'Optimized for Rust development',
60
59
  files: [
61
- { source: 'common/CLAUDE.md', destination: 'CLAUDE.md' },
62
- { source: 'common/.claude', destination: '.claude' }
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: 'Configuration for Go projects (Coming Soon)',
66
+ description: 'Optimized for Go development',
69
67
  files: [
70
- { source: 'common/CLAUDE.md', destination: 'CLAUDE.md' },
71
- { source: 'common/.claude', destination: '.claude' }
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": "30000",
30
- "BASH_MAX_OUTPUT_LENGTH": "10000",
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": "if [[ \"$(echo $STDIN_JSON | jq -r '.tool_input.file_path' | grep -E '\\.js$|\\.jsx$|\\.ts$|\\.tsx$')\" != \"\" && $(echo $STDIN_JSON | jq -r '.tool_input.content' | grep -E 'console\\.log') != \"\" ]]; then echo 'Warning: console.log statements should be removed before committing' >&2; exit 2; fi"
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...'; npx audit-ci --moderate; fi",
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.*from.*\\*' \"$FILE\"; then echo 'Warning: Avoid wildcard imports for better tree-shaking' >&2; exit 2; fi"
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 TEST_FILE=$(echo \"$FILE\" | sed -E 's/\\.(js|jsx|ts|tsx)$/.test.\\1/'); if [ -f \"$TEST_FILE\" ]; then echo \"Running tests for modified file...\"; npx jest \"$TEST_FILE\" --passWithNoTests; fi; fi",
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
  ]