claude-git-hooks 2.1.0 → 2.3.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/CHANGELOG.md +178 -0
- package/README.md +203 -79
- package/bin/claude-hooks +295 -119
- package/lib/config.js +163 -0
- package/lib/hooks/pre-commit.js +179 -67
- package/lib/hooks/prepare-commit-msg.js +47 -41
- package/lib/utils/claude-client.js +93 -11
- package/lib/utils/file-operations.js +1 -65
- package/lib/utils/file-utils.js +65 -0
- package/lib/utils/package-info.js +75 -0
- package/lib/utils/preset-loader.js +209 -0
- package/lib/utils/prompt-builder.js +83 -67
- package/lib/utils/resolution-prompt.js +12 -2
- package/package.json +49 -50
- package/templates/ANALYZE_DIFF.md +33 -0
- package/templates/COMMIT_MESSAGE.md +24 -0
- package/templates/SUBAGENT_INSTRUCTION.md +1 -0
- package/templates/config.example.json +41 -0
- package/templates/presets/ai/ANALYSIS_PROMPT.md +133 -0
- package/templates/presets/ai/PRE_COMMIT_GUIDELINES.md +176 -0
- package/templates/presets/ai/config.json +12 -0
- package/templates/presets/ai/preset.json +42 -0
- package/templates/presets/backend/ANALYSIS_PROMPT.md +85 -0
- package/templates/presets/backend/PRE_COMMIT_GUIDELINES.md +87 -0
- package/templates/presets/backend/config.json +12 -0
- package/templates/presets/backend/preset.json +49 -0
- package/templates/presets/database/ANALYSIS_PROMPT.md +114 -0
- package/templates/presets/database/PRE_COMMIT_GUIDELINES.md +143 -0
- package/templates/presets/database/config.json +12 -0
- package/templates/presets/database/preset.json +38 -0
- package/templates/presets/default/config.json +12 -0
- package/templates/presets/default/preset.json +53 -0
- package/templates/presets/frontend/ANALYSIS_PROMPT.md +99 -0
- package/templates/presets/frontend/PRE_COMMIT_GUIDELINES.md +95 -0
- package/templates/presets/frontend/config.json +12 -0
- package/templates/presets/frontend/preset.json +50 -0
- package/templates/presets/fullstack/ANALYSIS_PROMPT.md +107 -0
- package/templates/presets/fullstack/CONSISTENCY_CHECKS.md +147 -0
- package/templates/presets/fullstack/PRE_COMMIT_GUIDELINES.md +125 -0
- package/templates/presets/fullstack/config.json +12 -0
- package/templates/presets/fullstack/preset.json +55 -0
- package/templates/shared/ANALYSIS_PROMPT.md +103 -0
- package/templates/shared/ANALYZE_DIFF.md +33 -0
- package/templates/shared/COMMIT_MESSAGE.md +24 -0
- package/templates/shared/PRE_COMMIT_GUIDELINES.md +145 -0
- package/templates/shared/RESOLUTION_PROMPT.md +32 -0
- package/templates/check-version.sh +0 -266
package/bin/claude-hooks
CHANGED
|
@@ -9,6 +9,9 @@ import https from 'https';
|
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import { dirname } from 'path';
|
|
11
11
|
import { executeClaude, extractJSON } from '../lib/utils/claude-client.js';
|
|
12
|
+
import { loadPrompt } from '../lib/utils/prompt-builder.js';
|
|
13
|
+
import { listPresets } from '../lib/utils/preset-loader.js';
|
|
14
|
+
import { getConfig } from '../lib/config.js';
|
|
12
15
|
|
|
13
16
|
// Why: ES6 modules don't have __dirname, need to recreate it
|
|
14
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -126,31 +129,6 @@ function warning(message) {
|
|
|
126
129
|
log(`⚠️ ${message}`, 'yellow');
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
// Function to read password securely
|
|
130
|
-
function readPassword(prompt) {
|
|
131
|
-
return new Promise((resolve) => {
|
|
132
|
-
const rl = readline.createInterface({
|
|
133
|
-
input: process.stdin,
|
|
134
|
-
output: process.stdout
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Disable echo
|
|
138
|
-
rl.stdoutMuted = true;
|
|
139
|
-
rl._writeToOutput = function _writeToOutput(stringToWrite) {
|
|
140
|
-
if (rl.stdoutMuted)
|
|
141
|
-
rl.output.write("*");
|
|
142
|
-
else
|
|
143
|
-
rl.output.write(stringToWrite);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
rl.question(prompt, (password) => {
|
|
147
|
-
rl.close();
|
|
148
|
-
console.log(); // New line
|
|
149
|
-
resolve(password);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
132
|
// Entertainment system
|
|
155
133
|
class Entertainment {
|
|
156
134
|
static jokes = [
|
|
@@ -332,6 +310,11 @@ async function install(args) {
|
|
|
332
310
|
const isForce = args.includes('--force');
|
|
333
311
|
const skipAuth = args.includes('--skip-auth');
|
|
334
312
|
|
|
313
|
+
// Check for updates (unless --skip-auth flag)
|
|
314
|
+
if (!skipAuth && !isForce) {
|
|
315
|
+
await checkVersionAndPromptUpdate();
|
|
316
|
+
}
|
|
317
|
+
|
|
335
318
|
if (isForce) {
|
|
336
319
|
info('Installing Claude Git Hooks (force mode)...');
|
|
337
320
|
} else {
|
|
@@ -396,28 +379,79 @@ async function install(args) {
|
|
|
396
379
|
success('.claude directory created');
|
|
397
380
|
}
|
|
398
381
|
|
|
399
|
-
// Copy
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
382
|
+
// Copy ALL template files (.md and .json) to .claude directory
|
|
383
|
+
const templateFiles = fs.readdirSync(templatesPath)
|
|
384
|
+
.filter(file => {
|
|
385
|
+
const filePath = path.join(templatesPath, file);
|
|
386
|
+
return fs.statSync(filePath).isFile() && (file.endsWith('.md') || file.endsWith('.json'));
|
|
387
|
+
});
|
|
405
388
|
|
|
406
|
-
|
|
407
|
-
const destPath = path.join(claudeDir, file);
|
|
389
|
+
templateFiles.forEach(file => {
|
|
408
390
|
const sourcePath = path.join(templatesPath, file);
|
|
391
|
+
const destPath = path.join(claudeDir, file);
|
|
409
392
|
|
|
410
393
|
// In force mode or if it doesn't exist, copy the file
|
|
411
394
|
if (isForce || !fs.existsSync(destPath)) {
|
|
412
395
|
if (fs.existsSync(sourcePath)) {
|
|
413
396
|
fs.copyFileSync(sourcePath, destPath);
|
|
414
397
|
success(`${file} installed in .claude/`);
|
|
415
|
-
} else {
|
|
416
|
-
warning(`Template file not found: ${file}`);
|
|
417
398
|
}
|
|
399
|
+
} else {
|
|
400
|
+
info(`${file} already exists (skipped)`);
|
|
418
401
|
}
|
|
419
402
|
});
|
|
420
403
|
|
|
404
|
+
// Copy presets directory structure
|
|
405
|
+
const presetsSourcePath = path.join(templatesPath, 'presets');
|
|
406
|
+
const presetsDestPath = path.join(claudeDir, 'presets');
|
|
407
|
+
|
|
408
|
+
if (fs.existsSync(presetsSourcePath)) {
|
|
409
|
+
// Create presets directory in .claude
|
|
410
|
+
if (!fs.existsSync(presetsDestPath)) {
|
|
411
|
+
fs.mkdirSync(presetsDestPath, { recursive: true });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Copy each preset directory
|
|
415
|
+
const presetDirs = fs.readdirSync(presetsSourcePath)
|
|
416
|
+
.filter(item => fs.statSync(path.join(presetsSourcePath, item)).isDirectory());
|
|
417
|
+
|
|
418
|
+
presetDirs.forEach(presetName => {
|
|
419
|
+
const presetSource = path.join(presetsSourcePath, presetName);
|
|
420
|
+
const presetDest = path.join(presetsDestPath, presetName);
|
|
421
|
+
|
|
422
|
+
// Create preset directory
|
|
423
|
+
if (!fs.existsSync(presetDest)) {
|
|
424
|
+
fs.mkdirSync(presetDest, { recursive: true });
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Copy all files in preset directory
|
|
428
|
+
const presetFiles = fs.readdirSync(presetSource);
|
|
429
|
+
presetFiles.forEach(file => {
|
|
430
|
+
const sourceFile = path.join(presetSource, file);
|
|
431
|
+
const destFile = path.join(presetDest, file);
|
|
432
|
+
|
|
433
|
+
if (fs.statSync(sourceFile).isFile()) {
|
|
434
|
+
if (isForce || !fs.existsSync(destFile)) {
|
|
435
|
+
fs.copyFileSync(sourceFile, destFile);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
success(`${presetDirs.length} presets installed in .claude/presets/`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Special handling for config.json: rename config.example.json → config.json
|
|
445
|
+
const configExamplePath = path.join(claudeDir, 'config.example.json');
|
|
446
|
+
const configPath = path.join(claudeDir, 'config.json');
|
|
447
|
+
|
|
448
|
+
if (fs.existsSync(configExamplePath) && !fs.existsSync(configPath)) {
|
|
449
|
+
fs.copyFileSync(configExamplePath, configPath);
|
|
450
|
+
success('config.json created from example (customize as needed)');
|
|
451
|
+
} else if (!fs.existsSync(configPath)) {
|
|
452
|
+
warning('config.json not found - using defaults');
|
|
453
|
+
}
|
|
454
|
+
|
|
421
455
|
// Configure Git
|
|
422
456
|
configureGit();
|
|
423
457
|
|
|
@@ -429,16 +463,16 @@ async function install(args) {
|
|
|
429
463
|
console.log(' git commit -m "auto" # Generate message automatically');
|
|
430
464
|
console.log(' git commit -m "message" # Analyze code before commit');
|
|
431
465
|
console.log(' git commit --no-verify # Skip analysis completely');
|
|
432
|
-
console.log('\n
|
|
433
|
-
console.log('
|
|
434
|
-
console.log('
|
|
435
|
-
console.log('
|
|
436
|
-
console.log('
|
|
437
|
-
console.log('
|
|
438
|
-
console.log('
|
|
439
|
-
console.log('
|
|
440
|
-
console.log('
|
|
441
|
-
console.log('
|
|
466
|
+
console.log('\n💡 Configuration:');
|
|
467
|
+
console.log(' 📁 All templates installed in .claude/');
|
|
468
|
+
console.log(' 📝 Edit .claude/config.json to customize settings');
|
|
469
|
+
console.log(' 🎯 Use presets: backend, frontend, fullstack, database, ai, default');
|
|
470
|
+
console.log(' 🚀 Enable parallel analysis: set subagents.enabled = true');
|
|
471
|
+
console.log('\n📖 Example config.json:');
|
|
472
|
+
console.log(' {');
|
|
473
|
+
console.log(' "preset": "backend",');
|
|
474
|
+
console.log(' "subagents": { "enabled": true, "model": "haiku", "batchSize": 3 }');
|
|
475
|
+
console.log(' }');
|
|
442
476
|
console.log('\nFor more options: claude-hooks --help');
|
|
443
477
|
}
|
|
444
478
|
|
|
@@ -596,9 +630,6 @@ function updateGitignore() {
|
|
|
596
630
|
const claudeEntries = [
|
|
597
631
|
'# Claude Git Hooks',
|
|
598
632
|
'.claude/',
|
|
599
|
-
'debug-claude-response.json',
|
|
600
|
-
'claude_resolution_prompt.md',
|
|
601
|
-
'.claude-pr-analysis.json',
|
|
602
633
|
];
|
|
603
634
|
|
|
604
635
|
let gitignoreContent = '';
|
|
@@ -759,6 +790,9 @@ async function analyzeDiff(args) {
|
|
|
759
790
|
return;
|
|
760
791
|
}
|
|
761
792
|
|
|
793
|
+
// Load configuration
|
|
794
|
+
const config = await getConfig();
|
|
795
|
+
|
|
762
796
|
const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
763
797
|
|
|
764
798
|
if (!currentBranch) {
|
|
@@ -862,9 +896,9 @@ async function analyzeDiff(args) {
|
|
|
862
896
|
}
|
|
863
897
|
|
|
864
898
|
// Check if subagents should be used
|
|
865
|
-
const useSubagents =
|
|
866
|
-
const subagentModel =
|
|
867
|
-
let subagentBatchSize =
|
|
899
|
+
const useSubagents = config.subagents.enabled;
|
|
900
|
+
const subagentModel = config.subagents.model;
|
|
901
|
+
let subagentBatchSize = config.subagents.batchSize;
|
|
868
902
|
// Validate batch size (must be >= 1)
|
|
869
903
|
if (subagentBatchSize < 1) {
|
|
870
904
|
subagentBatchSize = 1;
|
|
@@ -873,37 +907,19 @@ async function analyzeDiff(args) {
|
|
|
873
907
|
? `\n\nIMPORTANT PARALLEL PROCESSING: If analyzing 3+ files, process them in batches of ${subagentBatchSize}. For EACH batch, create that many subagents in parallel using Task tool (send single message with multiple Task calls). Each subagent analyzes one file and provides insights. After ALL batches complete, consolidate into SINGLE JSON with ONE cohesive PR title/description. Model: ${subagentModel}. Example: 4 files with BATCH_SIZE=1 → 4 sequential batches of 1 subagent each. Example: 4 files with BATCH_SIZE=3 → batch 1 has 3 parallel subagents (files 1-3), batch 2 has 1 subagent (file 4).\n`
|
|
874
908
|
: '';
|
|
875
909
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
Respond EXCLUSIVELY with a valid JSON with this structure:
|
|
890
|
-
{
|
|
891
|
-
"prTitle": "Interesting PR title",
|
|
892
|
-
"prDescription": "detailed PR description with markdown",
|
|
893
|
-
"suggestedBranchName": "type/suggested-branch-name",
|
|
894
|
-
"changeType": "feature|fix|refactor|docs|test|chore",
|
|
895
|
-
"breakingChanges": false,
|
|
896
|
-
"testingNotes": "notes on necessary testing or 'None'"
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
=== COMMITS ===
|
|
900
|
-
${commits}
|
|
901
|
-
|
|
902
|
-
=== CHANGED FILES ===
|
|
903
|
-
${diffFiles}
|
|
904
|
-
|
|
905
|
-
=== FULL DIFF ===
|
|
906
|
-
${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (truncated diff)' : ''}`;
|
|
910
|
+
// Truncate full diff if too large
|
|
911
|
+
const truncatedDiff = fullDiff.length > 50000
|
|
912
|
+
? fullDiff.substring(0, 50000) + '\n... (truncated diff)'
|
|
913
|
+
: fullDiff;
|
|
914
|
+
|
|
915
|
+
// Load prompt from template
|
|
916
|
+
const prompt = await loadPrompt('ANALYZE_DIFF.md', {
|
|
917
|
+
CONTEXT_DESCRIPTION: contextDescription,
|
|
918
|
+
SUBAGENT_INSTRUCTION: subagentInstruction,
|
|
919
|
+
COMMITS: commits,
|
|
920
|
+
DIFF_FILES: diffFiles,
|
|
921
|
+
FULL_DIFF: truncatedDiff
|
|
922
|
+
});
|
|
907
923
|
|
|
908
924
|
info('Sending to Claude for analysis...');
|
|
909
925
|
const startTime = Date.now();
|
|
@@ -964,7 +980,13 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (truncated di
|
|
|
964
980
|
}
|
|
965
981
|
};
|
|
966
982
|
|
|
967
|
-
|
|
983
|
+
// Ensure .claude/out directory exists
|
|
984
|
+
const outputDir = '.claude/out';
|
|
985
|
+
if (!fs.existsSync(outputDir)) {
|
|
986
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const outputFile = '.claude/out/pr-analysis.json';
|
|
968
990
|
fs.writeFileSync(outputFile, JSON.stringify(outputData, null, 2));
|
|
969
991
|
|
|
970
992
|
const elapsed = Date.now() - startTime;
|
|
@@ -1035,20 +1057,13 @@ function status() {
|
|
|
1035
1057
|
const gitignorePath = '.gitignore';
|
|
1036
1058
|
if (fs.existsSync(gitignorePath)) {
|
|
1037
1059
|
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
1038
|
-
const
|
|
1039
|
-
let allPresent = true;
|
|
1060
|
+
const claudeIgnore = '.claude/';
|
|
1040
1061
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1046
|
-
warning(`${entry}: missing`);
|
|
1047
|
-
allPresent = false;
|
|
1048
|
-
}
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
if (!allPresent) {
|
|
1062
|
+
const regex = new RegExp(`^${claudeIgnore.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
|
|
1063
|
+
if (regex.test(gitignoreContent)) {
|
|
1064
|
+
success(`${claudeIgnore}: included (protects all Claude files)`);
|
|
1065
|
+
} else {
|
|
1066
|
+
warning(`${claudeIgnore}: missing`);
|
|
1052
1067
|
info('\nRun "claude-hooks install" to update .gitignore');
|
|
1053
1068
|
}
|
|
1054
1069
|
} else {
|
|
@@ -1056,13 +1071,6 @@ function status() {
|
|
|
1056
1071
|
}
|
|
1057
1072
|
}
|
|
1058
1073
|
|
|
1059
|
-
// Comando set-mode
|
|
1060
|
-
function setMode(mode) {
|
|
1061
|
-
if (!checkGitRepo()) {
|
|
1062
|
-
error('You are not in a Git repository.');
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
1074
|
// Cross-platform version comparison (semver)
|
|
1067
1075
|
// Why: Pure JavaScript, no bash dependency
|
|
1068
1076
|
// Returns: 0 if equal, 1 if v1 > v2, -1 if v1 < v2
|
|
@@ -1132,6 +1140,13 @@ async function update() {
|
|
|
1132
1140
|
}
|
|
1133
1141
|
}
|
|
1134
1142
|
|
|
1143
|
+
// Show version command
|
|
1144
|
+
// Why: Reusable function to display current version from package.json
|
|
1145
|
+
function showVersion() {
|
|
1146
|
+
const pkg = getPackageJson();
|
|
1147
|
+
console.log(`${pkg.name} v${pkg.version}`);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1135
1150
|
// Comando help
|
|
1136
1151
|
function showHelp() {
|
|
1137
1152
|
console.log(`
|
|
@@ -1149,6 +1164,10 @@ Commands:
|
|
|
1149
1164
|
disable [hook] Disable hooks (all or one specific)
|
|
1150
1165
|
status Show the status of hooks
|
|
1151
1166
|
analyze-diff [base] Analyze differences between branches and generate PR info
|
|
1167
|
+
presets List all available presets
|
|
1168
|
+
--set-preset <name> Set the active preset
|
|
1169
|
+
preset current Show the current active preset
|
|
1170
|
+
--version, -v Show the current version
|
|
1152
1171
|
help Show this help
|
|
1153
1172
|
|
|
1154
1173
|
Available hooks:
|
|
@@ -1163,6 +1182,9 @@ Examples:
|
|
|
1163
1182
|
claude-hooks enable # Enable all hooks
|
|
1164
1183
|
claude-hooks status # View current status
|
|
1165
1184
|
claude-hooks analyze-diff main # Analyze differences with main
|
|
1185
|
+
claude-hooks presets # List available presets
|
|
1186
|
+
claude-hooks --set-preset backend # Set backend preset
|
|
1187
|
+
claude-hooks preset current # Show current preset
|
|
1166
1188
|
|
|
1167
1189
|
Commit use cases:
|
|
1168
1190
|
git commit -m "message" # Manual message + blocking analysis
|
|
@@ -1173,30 +1195,165 @@ Commit use cases:
|
|
|
1173
1195
|
Analyze-diff use case:
|
|
1174
1196
|
claude-hooks analyze-diff main # Analyze changes vs main and generate:
|
|
1175
1197
|
→ PR Title: "feat: add user authentication module"
|
|
1176
|
-
→ PR Description: "## Summary\n- Added JWT authentication..."
|
|
1198
|
+
→ PR Description: "## Summary\n- Added JWT authentication..."
|
|
1177
1199
|
→ Suggested branch: "feature/user-authentication"
|
|
1178
1200
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1201
|
+
Presets (v2.3.0+):
|
|
1202
|
+
Built-in tech-stack specific configurations:
|
|
1203
|
+
- backend: Spring Boot + SQL Server (.java, .xml, .yml)
|
|
1204
|
+
- frontend: React + Material-UI (.js, .jsx, .ts, .tsx, .css)
|
|
1205
|
+
- fullstack: Backend + Frontend with API consistency checks
|
|
1206
|
+
- database: SQL Server migrations and procedures (.sql)
|
|
1207
|
+
- ai: Node.js + Claude CLI integration (.js, .json, .md)
|
|
1208
|
+
- default: General-purpose mixed languages
|
|
1209
|
+
|
|
1210
|
+
Configuration (v2.2.0+):
|
|
1211
|
+
Create .claude/config.json in your project to customize:
|
|
1212
|
+
- Preset selection
|
|
1213
|
+
- Analysis settings (maxFileSize, maxFiles, timeout)
|
|
1214
|
+
- Commit message generation (autoKeyword, timeout)
|
|
1215
|
+
- Parallel execution (enabled, model, batchSize)
|
|
1216
|
+
- Template paths and output files
|
|
1217
|
+
- Debug mode
|
|
1218
|
+
|
|
1219
|
+
Example: .claude/config.json
|
|
1220
|
+
{
|
|
1221
|
+
"preset": "backend",
|
|
1222
|
+
"analysis": { "maxFiles": 30, "timeout": 180000 },
|
|
1223
|
+
"subagents": {
|
|
1224
|
+
"enabled": true, # Enable parallel execution
|
|
1225
|
+
"model": "haiku", # haiku (fast) | sonnet | opus
|
|
1226
|
+
"batchSize": 2 # Files per batch (1=fastest)
|
|
1227
|
+
},
|
|
1228
|
+
"system": { "debug": true }
|
|
1229
|
+
}
|
|
1193
1230
|
|
|
1194
|
-
|
|
1231
|
+
Parallel Analysis (v2.2.0+):
|
|
1232
|
+
When analyzing 3+ files, parallel execution runs multiple Claude CLI
|
|
1233
|
+
processes simultaneously for faster analysis:
|
|
1234
|
+
- batchSize: 1 → Maximum speed (1 file per process)
|
|
1235
|
+
- batchSize: 2 → Balanced (2 files per process)
|
|
1236
|
+
- batchSize: 4+ → Fewer API calls but slower
|
|
1237
|
+
- Speed improvement: up to 4x faster with batchSize: 1
|
|
1238
|
+
|
|
1239
|
+
Customization:
|
|
1240
|
+
Override prompts by copying to .claude/:
|
|
1241
|
+
cp templates/COMMIT_MESSAGE.md .claude/
|
|
1242
|
+
cp templates/ANALYZE_DIFF.md .claude/
|
|
1243
|
+
cp templates/CLAUDE_PRE_COMMIT_SONAR.md .claude/
|
|
1244
|
+
# Edit as needed - system uses .claude/ version if exists
|
|
1195
1245
|
|
|
1196
1246
|
More information: https://github.com/pablorovito/claude-git-hooks
|
|
1197
1247
|
`);
|
|
1198
1248
|
}
|
|
1199
1249
|
|
|
1250
|
+
// Preset Management Functions
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Shows all available presets
|
|
1254
|
+
*/
|
|
1255
|
+
async function showPresets() {
|
|
1256
|
+
try {
|
|
1257
|
+
const presets = await listPresets();
|
|
1258
|
+
|
|
1259
|
+
if (presets.length === 0) {
|
|
1260
|
+
warning('No presets found');
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
console.log('');
|
|
1265
|
+
info('Available presets:');
|
|
1266
|
+
console.log('');
|
|
1267
|
+
|
|
1268
|
+
presets.forEach(preset => {
|
|
1269
|
+
console.log(` ${colors.green}${preset.name}${colors.reset}`);
|
|
1270
|
+
console.log(` ${preset.displayName}`);
|
|
1271
|
+
console.log(` ${colors.blue}${preset.description}${colors.reset}`);
|
|
1272
|
+
console.log('');
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
info('To set a preset: claude-hooks --set-preset <name>');
|
|
1276
|
+
info('To see current preset: claude-hooks preset current');
|
|
1277
|
+
console.log('');
|
|
1278
|
+
} catch (err) {
|
|
1279
|
+
error(`Failed to list presets: ${err.message}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Sets the active preset
|
|
1285
|
+
*/
|
|
1286
|
+
async function setPreset(presetName) {
|
|
1287
|
+
if (!presetName) {
|
|
1288
|
+
error('Please specify a preset name: claude-hooks --set-preset <name>');
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
try {
|
|
1293
|
+
// Verify preset exists
|
|
1294
|
+
const presets = await listPresets();
|
|
1295
|
+
const preset = presets.find(p => p.name === presetName);
|
|
1296
|
+
|
|
1297
|
+
if (!preset) {
|
|
1298
|
+
error(`Preset "${presetName}" not found`);
|
|
1299
|
+
info('Available presets:');
|
|
1300
|
+
presets.forEach(p => console.log(` - ${p.name}`));
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Update .claude/config.json
|
|
1305
|
+
const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
1306
|
+
const configDir = path.join(repoRoot, '.claude');
|
|
1307
|
+
const configPath = path.join(configDir, 'config.json');
|
|
1308
|
+
|
|
1309
|
+
// Ensure .claude directory exists
|
|
1310
|
+
if (!fs.existsSync(configDir)) {
|
|
1311
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Load existing config or create new
|
|
1315
|
+
let config = {};
|
|
1316
|
+
if (fs.existsSync(configPath)) {
|
|
1317
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Set preset
|
|
1321
|
+
config.preset = presetName;
|
|
1322
|
+
|
|
1323
|
+
// Save config
|
|
1324
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1325
|
+
|
|
1326
|
+
success(`Preset '${preset.displayName}' activated`);
|
|
1327
|
+
info(`Configuration saved to ${configPath}`);
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
error(`Failed to set preset: ${err.message}`);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Shows the current active preset
|
|
1335
|
+
*/
|
|
1336
|
+
async function currentPreset() {
|
|
1337
|
+
try {
|
|
1338
|
+
const config = await getConfig();
|
|
1339
|
+
const presetName = config.preset || 'default';
|
|
1340
|
+
|
|
1341
|
+
const presets = await listPresets();
|
|
1342
|
+
const preset = presets.find(p => p.name === presetName);
|
|
1343
|
+
|
|
1344
|
+
if (preset) {
|
|
1345
|
+
console.log('');
|
|
1346
|
+
success(`Current preset: ${preset.displayName} (${preset.name})`);
|
|
1347
|
+
console.log(` ${colors.blue}${preset.description}${colors.reset}`);
|
|
1348
|
+
console.log('');
|
|
1349
|
+
} else {
|
|
1350
|
+
warning(`Current preset "${presetName}" not found`);
|
|
1351
|
+
}
|
|
1352
|
+
} catch (err) {
|
|
1353
|
+
error(`Failed to get current preset: ${err.message}`);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1200
1357
|
// Main
|
|
1201
1358
|
async function main() {
|
|
1202
1359
|
const args = process.argv.slice(2);
|
|
@@ -1224,6 +1381,25 @@ async function main() {
|
|
|
1224
1381
|
case 'analyze-diff':
|
|
1225
1382
|
await analyzeDiff(args.slice(1));
|
|
1226
1383
|
break;
|
|
1384
|
+
case 'presets':
|
|
1385
|
+
await showPresets();
|
|
1386
|
+
break;
|
|
1387
|
+
case '--set-preset':
|
|
1388
|
+
await setPreset(args[1]);
|
|
1389
|
+
break;
|
|
1390
|
+
case 'preset':
|
|
1391
|
+
// Handle subcommands: preset current
|
|
1392
|
+
if (args[1] === 'current') {
|
|
1393
|
+
await currentPreset();
|
|
1394
|
+
} else {
|
|
1395
|
+
error(`Unknown preset subcommand: ${args[1]}`);
|
|
1396
|
+
}
|
|
1397
|
+
break;
|
|
1398
|
+
case '--version':
|
|
1399
|
+
case '-v':
|
|
1400
|
+
case 'version':
|
|
1401
|
+
showVersion();
|
|
1402
|
+
break;
|
|
1227
1403
|
case 'help':
|
|
1228
1404
|
case '--help':
|
|
1229
1405
|
case '-h':
|