claude-git-hooks 2.7.1 → 2.9.1
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 +163 -0
- package/README.md +71 -51
- package/bin/claude-hooks +381 -25
- package/lib/config.js +177 -83
- package/lib/hooks/pre-commit.js +21 -2
- package/lib/hooks/prepare-commit-msg.js +15 -4
- package/lib/utils/claude-client.js +238 -61
- package/lib/utils/claude-diagnostics.js +50 -6
- package/lib/utils/prompt-builder.js +10 -9
- package/lib/utils/resolution-prompt.js +2 -2
- package/lib/utils/telemetry.js +507 -0
- package/package.json +1 -1
- package/templates/config.advanced.example.json +113 -0
- package/templates/config.example.json +53 -36
- package/templates/presets/ai/config.json +5 -12
- package/templates/presets/backend/config.json +5 -12
- package/templates/presets/database/config.json +5 -12
- package/templates/presets/default/config.json +5 -12
- package/templates/presets/frontend/config.json +5 -12
- package/templates/presets/fullstack/config.json +5 -12
package/bin/claude-hooks
CHANGED
|
@@ -8,7 +8,7 @@ import readline from 'readline';
|
|
|
8
8
|
import https from 'https';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import { dirname } from 'path';
|
|
11
|
-
import { executeClaude, extractJSON } from '../lib/utils/claude-client.js';
|
|
11
|
+
import { executeClaude, executeClaudeWithRetry, extractJSON, analyzeCode } from '../lib/utils/claude-client.js';
|
|
12
12
|
import { loadPrompt } from '../lib/utils/prompt-builder.js';
|
|
13
13
|
import { listPresets } from '../lib/utils/preset-loader.js';
|
|
14
14
|
import { getConfig } from '../lib/config.js';
|
|
@@ -16,6 +16,7 @@ import { getOrPromptTaskId, formatWithTaskId } from '../lib/utils/task-id.js';
|
|
|
16
16
|
import { createPullRequest, getReviewersForFiles, parseGitHubRepo, setupGitHubMcp, getGitHubMcpStatus } from '../lib/utils/github-client.js';
|
|
17
17
|
import { showPRPreview, promptConfirmation, promptMenu, showSuccess, showError, showInfo, showWarning, showSpinner, promptEditField } from '../lib/utils/interactive-ui.js';
|
|
18
18
|
import { setupGitHubMCP } from '../lib/utils/mcp-setup.js';
|
|
19
|
+
import { displayStatistics as showTelemetryStats, clearTelemetry as clearTelemetryData } from '../lib/utils/telemetry.js';
|
|
19
20
|
import logger from '../lib/utils/logger.js';
|
|
20
21
|
|
|
21
22
|
// Why: ES6 modules don't have __dirname, need to recreate it
|
|
@@ -398,28 +399,64 @@ async function install(args) {
|
|
|
398
399
|
}
|
|
399
400
|
});
|
|
400
401
|
|
|
401
|
-
//
|
|
402
|
+
// Create .claude/prompts directory for markdown templates
|
|
403
|
+
const promptsDir = path.join(claudeDir, 'prompts');
|
|
404
|
+
if (!fs.existsSync(promptsDir)) {
|
|
405
|
+
fs.mkdirSync(promptsDir, { recursive: true });
|
|
406
|
+
success('.claude/prompts directory created');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Copy template files (.md and .json) to appropriate locations
|
|
402
410
|
const templateFiles = fs.readdirSync(templatesPath)
|
|
403
411
|
.filter(file => {
|
|
404
412
|
const filePath = path.join(templatesPath, file);
|
|
405
|
-
|
|
413
|
+
// Exclude example.json files and only include .md and .json files
|
|
414
|
+
return fs.statSync(filePath).isFile() &&
|
|
415
|
+
(file.endsWith('.md') || file.endsWith('.json')) &&
|
|
416
|
+
!file.includes('example.json');
|
|
406
417
|
});
|
|
407
418
|
|
|
408
419
|
templateFiles.forEach(file => {
|
|
409
420
|
const sourcePath = path.join(templatesPath, file);
|
|
410
|
-
|
|
421
|
+
let destPath;
|
|
422
|
+
let destLocation;
|
|
423
|
+
|
|
424
|
+
// .md files go to .claude/prompts/, .json files go to .claude/
|
|
425
|
+
if (file.endsWith('.md')) {
|
|
426
|
+
destPath = path.join(promptsDir, file);
|
|
427
|
+
destLocation = '.claude/prompts/';
|
|
428
|
+
} else {
|
|
429
|
+
destPath = path.join(claudeDir, file);
|
|
430
|
+
destLocation = '.claude/';
|
|
431
|
+
}
|
|
411
432
|
|
|
412
433
|
// In force mode or if it doesn't exist, copy the file
|
|
413
434
|
if (isForce || !fs.existsSync(destPath)) {
|
|
414
435
|
if (fs.existsSync(sourcePath)) {
|
|
415
436
|
fs.copyFileSync(sourcePath, destPath);
|
|
416
|
-
success(`${file} installed in
|
|
437
|
+
success(`${file} installed in ${destLocation}`);
|
|
417
438
|
}
|
|
418
439
|
} else {
|
|
419
440
|
info(`${file} already exists (skipped)`);
|
|
420
441
|
}
|
|
421
442
|
});
|
|
422
443
|
|
|
444
|
+
// Clean up old .md files from .claude/ root (v2.8.0 migration)
|
|
445
|
+
// .md files should now be in .claude/prompts/, not .claude/
|
|
446
|
+
const oldMdFiles = fs.readdirSync(claudeDir)
|
|
447
|
+
.filter(file => {
|
|
448
|
+
const filePath = path.join(claudeDir, file);
|
|
449
|
+
return fs.statSync(filePath).isFile() && file.endsWith('.md');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
if (oldMdFiles.length > 0) {
|
|
453
|
+
oldMdFiles.forEach(file => {
|
|
454
|
+
const oldPath = path.join(claudeDir, file);
|
|
455
|
+
fs.unlinkSync(oldPath);
|
|
456
|
+
info(`Removed old template from .claude/: ${file} (now in prompts/)`);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
423
460
|
// Copy presets directory structure
|
|
424
461
|
const presetsSourcePath = path.join(templatesPath, 'presets');
|
|
425
462
|
const presetsDestPath = path.join(claudeDir, 'presets');
|
|
@@ -460,15 +497,74 @@ async function install(args) {
|
|
|
460
497
|
success(`${presetDirs.length} presets installed in .claude/presets/`);
|
|
461
498
|
}
|
|
462
499
|
|
|
463
|
-
// Special handling for config.json
|
|
464
|
-
const configExamplePath = path.join(claudeDir, 'config.example.json');
|
|
500
|
+
// Special handling for config.json (v2.8.0+): backup old, create new simplified
|
|
465
501
|
const configPath = path.join(claudeDir, 'config.json');
|
|
502
|
+
const configOldDir = path.join(claudeDir, 'config_old');
|
|
503
|
+
const configExampleDir = path.join(claudeDir, 'config_example');
|
|
504
|
+
|
|
505
|
+
// Create config_old directory if needed
|
|
506
|
+
if (!fs.existsSync(configOldDir)) {
|
|
507
|
+
fs.mkdirSync(configOldDir, { recursive: true });
|
|
508
|
+
}
|
|
466
509
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
510
|
+
// Create config_example directory
|
|
511
|
+
if (!fs.existsSync(configExampleDir)) {
|
|
512
|
+
fs.mkdirSync(configExampleDir, { recursive: true });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Copy example configs to config_example/ directly from templates/
|
|
516
|
+
const exampleConfigs = ['config.example.json', 'config.advanced.example.json'];
|
|
517
|
+
exampleConfigs.forEach(exampleFile => {
|
|
518
|
+
const sourcePath = path.join(templatesPath, exampleFile);
|
|
519
|
+
const destPath = path.join(configExampleDir, exampleFile);
|
|
520
|
+
if (fs.existsSync(sourcePath)) {
|
|
521
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
success('Example configs installed in .claude/config_example/');
|
|
525
|
+
|
|
526
|
+
// Backup existing config if it exists (legacy format migration)
|
|
527
|
+
let needsMigration = false;
|
|
528
|
+
if (fs.existsSync(configPath)) {
|
|
529
|
+
const backupPath = path.join(configOldDir, `config.json.${Date.now()}`);
|
|
530
|
+
fs.copyFileSync(configPath, backupPath);
|
|
531
|
+
info(`Existing config backed up: ${backupPath}`);
|
|
532
|
+
|
|
533
|
+
// Read old config to check if it's legacy format
|
|
534
|
+
const oldConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
535
|
+
if (!oldConfig.version || oldConfig.version !== '2.8.0') {
|
|
536
|
+
warning('Legacy config detected - will be replaced with v2.8.0 format');
|
|
537
|
+
needsMigration = true;
|
|
538
|
+
|
|
539
|
+
// Delete old config to force new format
|
|
540
|
+
fs.unlinkSync(configPath);
|
|
541
|
+
} else {
|
|
542
|
+
info('Config already in v2.8.0 format - keeping existing file');
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Create new config.json from minimal example if it doesn't exist
|
|
547
|
+
if (!fs.existsSync(configPath)) {
|
|
548
|
+
// Read example and extract minimal config
|
|
549
|
+
const examplePath = path.join(configExampleDir, 'config.example.json');
|
|
550
|
+
const exampleContent = fs.readFileSync(examplePath, 'utf8');
|
|
551
|
+
const exampleJson = JSON.parse(exampleContent);
|
|
552
|
+
|
|
553
|
+
// Create minimal config: just version and preset
|
|
554
|
+
const minimalConfig = {
|
|
555
|
+
version: exampleJson.version,
|
|
556
|
+
preset: exampleJson.preset
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
fs.writeFileSync(configPath, JSON.stringify(minimalConfig, null, 4));
|
|
560
|
+
success('config.json created with minimal v2.8.0 format');
|
|
561
|
+
info('📝 Customize: .claude/config.json (see config_example/ for examples)');
|
|
562
|
+
|
|
563
|
+
// Auto-run migration if needed to preserve settings
|
|
564
|
+
if (needsMigration) {
|
|
565
|
+
info('🔄 Auto-migrating settings from backup...');
|
|
566
|
+
await autoMigrateConfig(configPath, path.join(configOldDir, fs.readdirSync(configOldDir).sort().pop()));
|
|
567
|
+
}
|
|
472
568
|
}
|
|
473
569
|
|
|
474
570
|
// Create settings.local.json for sensitive data (gitignored)
|
|
@@ -493,21 +589,31 @@ async function install(args) {
|
|
|
493
589
|
console.log(' git commit -m "auto" # Generate message automatically');
|
|
494
590
|
console.log(' git commit -m "message" # Analyze code before commit');
|
|
495
591
|
console.log(' git commit --no-verify # Skip analysis completely');
|
|
496
|
-
console.log('\n💡 Configuration:');
|
|
592
|
+
console.log('\n💡 Configuration (v2.8.0):');
|
|
497
593
|
console.log(' 📁 All templates installed in .claude/');
|
|
498
|
-
console.log(' 📝 Edit .claude/config.json
|
|
499
|
-
console.log('
|
|
500
|
-
console.log('
|
|
501
|
-
console.log('
|
|
594
|
+
console.log(' 📝 Edit .claude/config.json (minimal by default)');
|
|
595
|
+
console.log(' 📂 Examples: .claude/config_example/');
|
|
596
|
+
console.log(' 📦 Backups: .claude/config_old/');
|
|
597
|
+
console.log(' 🎯 Presets: backend, frontend, fullstack, database, ai, default');
|
|
598
|
+
console.log(' 🚀 Parallel analysis enabled by default (hardcoded)');
|
|
599
|
+
console.log(' 🐛 Debug mode: claude-hooks --debug true');
|
|
502
600
|
console.log('\n🔗 GitHub PR Creation (v2.5.0+):');
|
|
503
|
-
console.log(' claude-hooks setup-github # Configure GitHub token
|
|
504
|
-
console.log(' claude-hooks create-pr main # Create PR with auto-
|
|
505
|
-
console.log('\n📖
|
|
601
|
+
console.log(' claude-hooks setup-github # Configure GitHub token');
|
|
602
|
+
console.log(' claude-hooks create-pr main # Create PR with auto-metadata');
|
|
603
|
+
console.log('\n📖 Minimal config.json (v2.8.0):');
|
|
604
|
+
console.log(' {');
|
|
605
|
+
console.log(' "version": "2.8.0",');
|
|
606
|
+
console.log(' "preset": "backend"');
|
|
607
|
+
console.log(' }');
|
|
608
|
+
console.log('\n📖 With GitHub customization:');
|
|
506
609
|
console.log(' {');
|
|
610
|
+
console.log(' "version": "2.8.0",');
|
|
507
611
|
console.log(' "preset": "backend",');
|
|
508
|
-
console.log(' "
|
|
509
|
-
console.log('
|
|
612
|
+
console.log(' "overrides": {');
|
|
613
|
+
console.log(' "github": { "pr": { "reviewers": ["your-username"] } }');
|
|
614
|
+
console.log(' }');
|
|
510
615
|
console.log(' }');
|
|
616
|
+
console.log('\n🔧 Advanced: see .claude/config_example/config.advanced.example.json');
|
|
511
617
|
console.log('\nFor more options: claude-hooks --help');
|
|
512
618
|
}
|
|
513
619
|
|
|
@@ -967,9 +1073,22 @@ async function analyzeDiff(args) {
|
|
|
967
1073
|
info('Sending to Claude for analysis...');
|
|
968
1074
|
const startTime = Date.now();
|
|
969
1075
|
|
|
1076
|
+
// Prepare telemetry context
|
|
1077
|
+
const filesChanged = diffFiles.split('\n').length;
|
|
1078
|
+
const telemetryContext = {
|
|
1079
|
+
fileCount: filesChanged,
|
|
1080
|
+
batchSize: filesChanged,
|
|
1081
|
+
totalBatches: 1,
|
|
1082
|
+
model: subagentModel || 'sonnet',
|
|
1083
|
+
hook: 'analyze-diff'
|
|
1084
|
+
};
|
|
1085
|
+
|
|
970
1086
|
try {
|
|
971
|
-
// Use cross-platform
|
|
972
|
-
const response = await
|
|
1087
|
+
// Use cross-platform executeClaudeWithRetry from claude-client.js with telemetry
|
|
1088
|
+
const response = await executeClaudeWithRetry(prompt, {
|
|
1089
|
+
timeout: 180000, // 3 minutes for diff analysis
|
|
1090
|
+
telemetryContext
|
|
1091
|
+
});
|
|
973
1092
|
|
|
974
1093
|
// Extract JSON from response using claude-client utility
|
|
975
1094
|
const result = extractJSON(response);
|
|
@@ -1231,7 +1350,20 @@ async function createPr(args) {
|
|
|
1231
1350
|
|
|
1232
1351
|
showInfo('Generating PR metadata with Claude...');
|
|
1233
1352
|
logger.debug('create-pr', 'Calling Claude with prompt', { promptLength: prompt.length });
|
|
1234
|
-
|
|
1353
|
+
|
|
1354
|
+
// Prepare telemetry context for create-pr
|
|
1355
|
+
const telemetryContext = {
|
|
1356
|
+
fileCount: filesArray.length,
|
|
1357
|
+
batchSize: filesArray.length,
|
|
1358
|
+
totalBatches: 1,
|
|
1359
|
+
model: 'sonnet', // create-pr always uses main model
|
|
1360
|
+
hook: 'create-pr'
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
const response = await executeClaudeWithRetry(prompt, {
|
|
1364
|
+
timeout: 180000,
|
|
1365
|
+
telemetryContext
|
|
1366
|
+
});
|
|
1235
1367
|
logger.debug('create-pr', 'Claude response received', { responseLength: response.length });
|
|
1236
1368
|
|
|
1237
1369
|
const analysisResult = extractJSON(response);
|
|
@@ -1598,6 +1730,7 @@ Commands:
|
|
|
1598
1730
|
presets List all available presets
|
|
1599
1731
|
--set-preset <name> Set the active preset
|
|
1600
1732
|
preset current Show the current active preset
|
|
1733
|
+
telemetry [action] Telemetry management (show or clear)
|
|
1601
1734
|
--debug <value> Set debug mode (true, false, or status)
|
|
1602
1735
|
--version, -v Show the current version
|
|
1603
1736
|
help Show this help
|
|
@@ -1620,6 +1753,8 @@ Examples:
|
|
|
1620
1753
|
claude-hooks presets # List available presets
|
|
1621
1754
|
claude-hooks --set-preset backend # Set backend preset
|
|
1622
1755
|
claude-hooks preset current # Show current preset
|
|
1756
|
+
claude-hooks telemetry show # Show telemetry statistics
|
|
1757
|
+
claude-hooks telemetry clear # Clear telemetry data
|
|
1623
1758
|
claude-hooks --debug true # Enable debug mode
|
|
1624
1759
|
claude-hooks --debug status # Check debug status
|
|
1625
1760
|
|
|
@@ -1853,6 +1988,179 @@ async function currentPreset() {
|
|
|
1853
1988
|
}
|
|
1854
1989
|
}
|
|
1855
1990
|
|
|
1991
|
+
// ============================================================================
|
|
1992
|
+
// DEPRECATED CODE SECTION - Will be removed in v3.0.0
|
|
1993
|
+
// ============================================================================
|
|
1994
|
+
// This section contains migration code for legacy configs (pre-v2.8.0)
|
|
1995
|
+
// Auto-executed during install when legacy config detected
|
|
1996
|
+
// Manual command: claude-hooks migrate-config
|
|
1997
|
+
// ============================================================================
|
|
1998
|
+
|
|
1999
|
+
/**
|
|
2000
|
+
* Extracts allowed settings from legacy config format
|
|
2001
|
+
* Shared by both autoMigrateConfig and migrateConfig
|
|
2002
|
+
*
|
|
2003
|
+
* @param {Object} rawConfig - Legacy format config
|
|
2004
|
+
* @returns {Object} Allowed overrides only
|
|
2005
|
+
*/
|
|
2006
|
+
function extractLegacySettings(rawConfig) {
|
|
2007
|
+
const allowedOverrides = {};
|
|
2008
|
+
|
|
2009
|
+
// GitHub PR config (fully allowed)
|
|
2010
|
+
if (rawConfig.github?.pr) {
|
|
2011
|
+
allowedOverrides.github = { pr: {} };
|
|
2012
|
+
if (rawConfig.github.pr.defaultBase !== undefined) {
|
|
2013
|
+
allowedOverrides.github.pr.defaultBase = rawConfig.github.pr.defaultBase;
|
|
2014
|
+
}
|
|
2015
|
+
if (rawConfig.github.pr.reviewers !== undefined) {
|
|
2016
|
+
allowedOverrides.github.pr.reviewers = rawConfig.github.pr.reviewers;
|
|
2017
|
+
}
|
|
2018
|
+
if (rawConfig.github.pr.labelRules !== undefined) {
|
|
2019
|
+
allowedOverrides.github.pr.labelRules = rawConfig.github.pr.labelRules;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// Subagent batchSize (allowed)
|
|
2024
|
+
if (rawConfig.subagents?.batchSize !== undefined) {
|
|
2025
|
+
allowedOverrides.subagents = { batchSize: rawConfig.subagents.batchSize };
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
// Advanced params (preserved with warning in manual migration)
|
|
2029
|
+
if (rawConfig.analysis?.ignoreExtensions !== undefined) {
|
|
2030
|
+
if (!allowedOverrides.analysis) allowedOverrides.analysis = {};
|
|
2031
|
+
allowedOverrides.analysis.ignoreExtensions = rawConfig.analysis.ignoreExtensions;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
if (rawConfig.commitMessage?.taskIdPattern !== undefined) {
|
|
2035
|
+
if (!allowedOverrides.commitMessage) allowedOverrides.commitMessage = {};
|
|
2036
|
+
allowedOverrides.commitMessage.taskIdPattern = rawConfig.commitMessage.taskIdPattern;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
if (rawConfig.subagents?.model !== undefined) {
|
|
2040
|
+
if (!allowedOverrides.subagents) allowedOverrides.subagents = {};
|
|
2041
|
+
allowedOverrides.subagents.model = rawConfig.subagents.model;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
return allowedOverrides;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
/**
|
|
2048
|
+
* Auto-migrates legacy config during installation
|
|
2049
|
+
* Called automatically by installer when legacy format detected
|
|
2050
|
+
*
|
|
2051
|
+
* @param {string} newConfigPath - Path to new config.json
|
|
2052
|
+
* @param {string} backupConfigPath - Path to backup of old config
|
|
2053
|
+
*/
|
|
2054
|
+
async function autoMigrateConfig(newConfigPath, backupConfigPath) {
|
|
2055
|
+
try {
|
|
2056
|
+
const rawConfig = JSON.parse(fs.readFileSync(backupConfigPath, 'utf8'));
|
|
2057
|
+
const allowedOverrides = extractLegacySettings(rawConfig);
|
|
2058
|
+
|
|
2059
|
+
// Read the newly created minimal config
|
|
2060
|
+
const newConfig = JSON.parse(fs.readFileSync(newConfigPath, 'utf8'));
|
|
2061
|
+
|
|
2062
|
+
// Add overrides if any
|
|
2063
|
+
if (Object.keys(allowedOverrides).length > 0) {
|
|
2064
|
+
newConfig.overrides = allowedOverrides;
|
|
2065
|
+
fs.writeFileSync(newConfigPath, JSON.stringify(newConfig, null, 4));
|
|
2066
|
+
success('✅ Settings migrated from legacy config');
|
|
2067
|
+
info(`📋 Preserved ${Object.keys(allowedOverrides).length} custom settings`);
|
|
2068
|
+
}
|
|
2069
|
+
} catch (err) {
|
|
2070
|
+
warning(`⚠️ Could not auto-migrate settings: ${err.message}`);
|
|
2071
|
+
info('💡 Run "claude-hooks migrate-config" manually if needed');
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
/**
|
|
2076
|
+
* Migrates legacy config.json to v2.8.0 format (Manual command)
|
|
2077
|
+
* Why: Simplifies configuration, reduces redundancy
|
|
2078
|
+
*
|
|
2079
|
+
* DEPRECATED: Will be removed in v3.0.0 (most users will have migrated by then)
|
|
2080
|
+
*/
|
|
2081
|
+
async function migrateConfig() {
|
|
2082
|
+
const claudeDir = '.claude';
|
|
2083
|
+
const configPath = path.join(claudeDir, 'config.json');
|
|
2084
|
+
|
|
2085
|
+
if (!fs.existsSync(configPath)) {
|
|
2086
|
+
info('ℹ️ No config file found. Nothing to migrate.');
|
|
2087
|
+
console.log('\n💡 To create a new config:');
|
|
2088
|
+
console.log(' 1. Run: claude-hooks install --force');
|
|
2089
|
+
console.log(' 2. Or copy from: .claude/config_example/config.example.json');
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
try {
|
|
2094
|
+
const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
2095
|
+
|
|
2096
|
+
// Check if already in v2.8.0 format
|
|
2097
|
+
if (rawConfig.version === '2.8.0') {
|
|
2098
|
+
success('✅ Config is already in v2.8.0 format.');
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
info('📦 Starting config migration to v2.8.0...');
|
|
2103
|
+
|
|
2104
|
+
// Create backup in config_old/
|
|
2105
|
+
const configOldDir = path.join(claudeDir, 'config_old');
|
|
2106
|
+
if (!fs.existsSync(configOldDir)) {
|
|
2107
|
+
fs.mkdirSync(configOldDir, { recursive: true });
|
|
2108
|
+
}
|
|
2109
|
+
const backupPath = path.join(configOldDir, `config.json.${Date.now()}`);
|
|
2110
|
+
fs.copyFileSync(configPath, backupPath);
|
|
2111
|
+
success(`Backup created: ${backupPath}`);
|
|
2112
|
+
|
|
2113
|
+
// Extract only allowed parameters
|
|
2114
|
+
const allowedOverrides = extractLegacySettings(rawConfig);
|
|
2115
|
+
|
|
2116
|
+
// Check for advanced params
|
|
2117
|
+
const hasAdvancedParams = allowedOverrides.analysis?.ignoreExtensions ||
|
|
2118
|
+
allowedOverrides.commitMessage?.taskIdPattern ||
|
|
2119
|
+
allowedOverrides.subagents?.model;
|
|
2120
|
+
|
|
2121
|
+
// Build new config
|
|
2122
|
+
const newConfig = {
|
|
2123
|
+
version: '2.8.0',
|
|
2124
|
+
preset: rawConfig.preset || 'default'
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
// Only add overrides if there are any
|
|
2128
|
+
if (Object.keys(allowedOverrides).length > 0) {
|
|
2129
|
+
newConfig.overrides = allowedOverrides;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// Show diff
|
|
2133
|
+
console.log('\n📝 Migration preview:');
|
|
2134
|
+
console.log(` Old format: ${Object.keys(rawConfig).length} top-level keys`);
|
|
2135
|
+
console.log(` New format: ${Object.keys(newConfig).length} top-level keys`);
|
|
2136
|
+
if (Object.keys(allowedOverrides).length > 0) {
|
|
2137
|
+
console.log(` Preserved: ${Object.keys(allowedOverrides).length} override sections`);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// Write new config
|
|
2141
|
+
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 4));
|
|
2142
|
+
success('✅ Config migrated to v2.8.0 successfully!');
|
|
2143
|
+
|
|
2144
|
+
if (hasAdvancedParams) {
|
|
2145
|
+
warning('⚠️ Advanced parameters detected and preserved');
|
|
2146
|
+
info('📖 See .claude/config.advanced.example.json for documentation');
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
console.log(`\n✨ New config:`);
|
|
2150
|
+
console.log(JSON.stringify(newConfig, null, 2));
|
|
2151
|
+
console.log(`\n💾 Old config backed up to: ${backupPath}`);
|
|
2152
|
+
console.log('\n💡 Many parameters are now hardcoded with sensible defaults');
|
|
2153
|
+
console.log(' See CHANGELOG.md for full list of changes');
|
|
2154
|
+
|
|
2155
|
+
} catch (error) {
|
|
2156
|
+
error(`Failed to migrate config: ${error.message}`);
|
|
2157
|
+
console.log('\n💡 Manual migration:');
|
|
2158
|
+
console.log(' 1. Backup your current config');
|
|
2159
|
+
console.log(' 2. See .claude/config.example.json for new format');
|
|
2160
|
+
console.log(' 3. Copy minimal example and customize');
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
1856
2164
|
/**
|
|
1857
2165
|
* Sets debug mode
|
|
1858
2166
|
* Why: Enables detailed logging for troubleshooting
|
|
@@ -1893,6 +2201,41 @@ async function setDebug(value) {
|
|
|
1893
2201
|
}
|
|
1894
2202
|
|
|
1895
2203
|
// Main
|
|
2204
|
+
/**
|
|
2205
|
+
* Show telemetry statistics
|
|
2206
|
+
* Why: Help users understand JSON parsing patterns and batch performance
|
|
2207
|
+
*/
|
|
2208
|
+
async function showTelemetry() {
|
|
2209
|
+
await showTelemetryStats();
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
/**
|
|
2213
|
+
* Clear telemetry data
|
|
2214
|
+
* Why: Allow users to reset telemetry
|
|
2215
|
+
*/
|
|
2216
|
+
async function clearTelemetry() {
|
|
2217
|
+
const config = await getConfig();
|
|
2218
|
+
|
|
2219
|
+
if (!config.system?.telemetry && config.system?.telemetry !== undefined) {
|
|
2220
|
+
console.log('\n⚠️ Telemetry is currently disabled.\n');
|
|
2221
|
+
console.log('To re-enable (default), remove or set to true in .claude/config.json:');
|
|
2222
|
+
console.log('{');
|
|
2223
|
+
console.log(' "system": {');
|
|
2224
|
+
console.log(' "telemetry": true');
|
|
2225
|
+
console.log(' }');
|
|
2226
|
+
console.log('}\n');
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
const confirmed = await promptConfirmation('Are you sure you want to clear all telemetry data?');
|
|
2231
|
+
if (confirmed) {
|
|
2232
|
+
await clearTelemetryData();
|
|
2233
|
+
success('Telemetry data cleared successfully');
|
|
2234
|
+
} else {
|
|
2235
|
+
info('Telemetry data was not cleared');
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
1896
2239
|
async function main() {
|
|
1897
2240
|
const args = process.argv.slice(2);
|
|
1898
2241
|
const command = args[0];
|
|
@@ -1942,6 +2285,19 @@ async function main() {
|
|
|
1942
2285
|
error(`Unknown preset subcommand: ${args[1]}`);
|
|
1943
2286
|
}
|
|
1944
2287
|
break;
|
|
2288
|
+
case 'migrate-config':
|
|
2289
|
+
await migrateConfig();
|
|
2290
|
+
break;
|
|
2291
|
+
case 'telemetry':
|
|
2292
|
+
// Handle subcommands: telemetry show, telemetry clear
|
|
2293
|
+
if (args[1] === 'show' || args[1] === undefined) {
|
|
2294
|
+
await showTelemetry();
|
|
2295
|
+
} else if (args[1] === 'clear') {
|
|
2296
|
+
await clearTelemetry();
|
|
2297
|
+
} else {
|
|
2298
|
+
error(`Unknown telemetry subcommand: ${args[1]}`);
|
|
2299
|
+
}
|
|
2300
|
+
break;
|
|
1945
2301
|
case '--debug':
|
|
1946
2302
|
await setDebug(args[1]);
|
|
1947
2303
|
break;
|