claude-git-hooks 2.4.0 → 2.5.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/bin/claude-hooks CHANGED
@@ -12,6 +12,11 @@ import { executeClaude, extractJSON } 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';
15
+ import { getOrPromptTaskId, formatWithTaskId } from '../lib/utils/task-id.js';
16
+ import { createPullRequest, getReviewersForFiles, parseGitHubRepo, setupGitHubMcp, getGitHubMcpStatus } from '../lib/utils/github-client.js';
17
+ import { showPRPreview, promptConfirmation, promptMenu, showSuccess, showError, showInfo, showWarning, showSpinner, promptEditField } from '../lib/utils/interactive-ui.js';
18
+ import { setupGitHubMCP } from '../lib/utils/mcp-setup.js';
19
+ import logger from '../lib/utils/logger.js';
15
20
 
16
21
  // Why: ES6 modules don't have __dirname, need to recreate it
17
22
  const __filename = fileURLToPath(import.meta.url);
@@ -452,6 +457,17 @@ async function install(args) {
452
457
  warning('config.json not found - using defaults');
453
458
  }
454
459
 
460
+ // Create settings.local.json for sensitive data (gitignored)
461
+ const settingsLocalPath = path.join(claudeDir, 'settings.local.json');
462
+ if (!fs.existsSync(settingsLocalPath)) {
463
+ const settingsLocalContent = {
464
+ "_comment": "Local settings - DO NOT COMMIT. This file is gitignored.",
465
+ "githubToken": ""
466
+ };
467
+ fs.writeFileSync(settingsLocalPath, JSON.stringify(settingsLocalContent, null, 2));
468
+ info('settings.local.json created (add your GitHub token here)');
469
+ }
470
+
455
471
  // Configure Git
456
472
  configureGit();
457
473
 
@@ -469,10 +485,14 @@ async function install(args) {
469
485
  console.log(' šŸŽÆ Use presets: backend, frontend, fullstack, database, ai, default');
470
486
  console.log(' šŸš€ Enable parallel analysis: set subagents.enabled = true');
471
487
  console.log(' šŸ› Enable debug mode: claude-hooks --debug true');
488
+ console.log('\nšŸ”— GitHub PR Creation (v2.5.0+):');
489
+ console.log(' claude-hooks setup-github # Configure GitHub token for create-pr');
490
+ console.log(' claude-hooks create-pr main # Create PR with auto-generated metadata');
472
491
  console.log('\nšŸ“– Example config.json:');
473
492
  console.log(' {');
474
493
  console.log(' "preset": "backend",');
475
- console.log(' "subagents": { "enabled": true, "model": "haiku", "batchSize": 3 }');
494
+ console.log(' "subagents": { "enabled": true, "model": "haiku", "batchSize": 3 },');
495
+ console.log(' "github": { "pr": { "reviewers": ["your-username"] } }');
476
496
  console.log(' }');
477
497
  console.log('\nFor more options: claude-hooks --help');
478
498
  }
@@ -509,14 +529,12 @@ async function checkAndInstallDependencies(sudoPassword = null, skipAuth = false
509
529
 
510
530
  // v2.0.0+: Unix tools (sed, awk, grep, etc.) no longer needed (pure Node.js implementation)
511
531
 
512
- // Check and install Claude CLI
513
- await checkAndInstallClaude();
514
-
515
- // Check Claude authentication (if not skipped)
532
+ // Check and install Claude CLI (skip if --skip-auth)
516
533
  if (!skipAuth) {
534
+ await checkAndInstallClaude();
517
535
  await checkClaudeAuth();
518
536
  } else {
519
- warning('Skipping Claude authentication verification (--skip-auth)');
537
+ warning('Skipping Claude CLI verification and authentication (--skip-auth)');
520
538
  }
521
539
 
522
540
  // Clear password from memory
@@ -530,9 +548,19 @@ function isWindows() {
530
548
  }
531
549
 
532
550
  // Get Claude command based on platform
533
- // Why: On Windows, Claude CLI runs in WSL, so we need 'wsl claude'
551
+ // Why: On Windows, try native Claude first, then WSL as fallback
534
552
  function getClaudeCommand() {
535
- return isWindows() ? 'wsl claude' : 'claude';
553
+ if (isWindows()) {
554
+ // Try native Windows Claude first
555
+ try {
556
+ execSync('claude --version', { stdio: 'ignore', timeout: 3000 });
557
+ return 'claude';
558
+ } catch (e) {
559
+ // Fallback to WSL
560
+ return 'wsl claude';
561
+ }
562
+ }
563
+ return 'claude';
536
564
  }
537
565
 
538
566
  // Check if we need to install dependencies
@@ -629,7 +657,7 @@ function updateGitignore() {
629
657
 
630
658
  const gitignorePath = '.gitignore';
631
659
  const claudeEntries = [
632
- '# Claude Git Hooks',
660
+ '# Claude Git Hooks (includes .claude/settings.local.json for tokens)',
633
661
  '.claude/',
634
662
  ];
635
663
 
@@ -1017,6 +1045,392 @@ async function analyzeDiff(args) {
1017
1045
  }
1018
1046
  }
1019
1047
 
1048
+ // Create PR command (v2.5.0+ - Octokit-based)
1049
+ async function createPr(args) {
1050
+ logger.debug('create-pr', 'Starting create-pr command', { args });
1051
+
1052
+ if (!checkGitRepo()) {
1053
+ error('You are not in a Git repository.');
1054
+ logger.debug('create-pr', 'Not in a git repository, exiting');
1055
+ return;
1056
+ }
1057
+
1058
+ try {
1059
+ // Load configuration
1060
+ logger.debug('create-pr', 'Loading configuration');
1061
+ const config = await getConfig();
1062
+ logger.debug('create-pr', 'Configuration loaded', {
1063
+ preset: config.preset,
1064
+ githubEnabled: config.github?.enabled,
1065
+ defaultBase: config.github?.pr?.defaultBase
1066
+ });
1067
+
1068
+ // Import GitHub API module
1069
+ logger.debug('create-pr', 'Importing GitHub API modules');
1070
+ const { createPullRequest, GitHubAPIError, validateToken, findExistingPR } = await import('../lib/utils/github-api.js');
1071
+ const { parseGitHubRepo } = await import('../lib/utils/github-client.js');
1072
+
1073
+ showInfo('šŸš€ Creating Pull Request...');
1074
+ console.log('');
1075
+
1076
+ // Step 1: Validate GitHub token
1077
+ logger.debug('create-pr', 'Step 1: Validating GitHub token');
1078
+ const tokenValidation = await validateToken();
1079
+ if (!tokenValidation.valid) {
1080
+ logger.error('create-pr', 'GitHub authentication failed', { error: tokenValidation.error });
1081
+ showError('GitHub authentication failed');
1082
+ console.log('');
1083
+ console.log('Please configure your GitHub token:');
1084
+ console.log(' Option 1: Set GITHUB_TOKEN environment variable');
1085
+ console.log(' Option 2: Add token to .claude/settings.local.json:');
1086
+ console.log(' { "githubToken": "ghp_your_token_here" }');
1087
+ console.log(' Option 3: Run: claude-hooks setup-github');
1088
+ console.log('');
1089
+ process.exit(1);
1090
+ }
1091
+
1092
+ logger.debug('create-pr', 'Token validation successful', {
1093
+ user: tokenValidation.user,
1094
+ hasRepoScope: tokenValidation.hasRepoScope,
1095
+ scopes: tokenValidation.scopes
1096
+ });
1097
+
1098
+ showSuccess(`Authenticated as: ${tokenValidation.user}`);
1099
+ if (!tokenValidation.hasRepoScope) {
1100
+ showWarning('Token may lack "repo" scope - PR creation might fail');
1101
+ }
1102
+
1103
+ // Step 2: Get or prompt for task-id (with config for pattern)
1104
+ logger.debug('create-pr', 'Step 2: Getting or prompting for task-id');
1105
+ const taskId = await getOrPromptTaskId({
1106
+ prompt: true, // DO prompt for PRs (unlike commit messages)
1107
+ required: false, // Allow skipping
1108
+ config: config // Pass config for custom pattern
1109
+ });
1110
+ logger.debug('create-pr', 'Task ID determined', { taskId });
1111
+
1112
+ // Step 3: Parse arguments and determine base branch
1113
+ logger.debug('create-pr', 'Step 3: Parsing arguments and determining base branch', { args });
1114
+ let baseBranchArg = args[0];
1115
+ if (baseBranchArg && /^[A-Z]{2,10}-\d+$/i.test(baseBranchArg)) {
1116
+ baseBranchArg = args[1];
1117
+ }
1118
+ const baseBranch = baseBranchArg || config.github?.pr?.defaultBase || 'develop';
1119
+ logger.debug('create-pr', 'Base branch determined', { baseBranch, fromConfig: !baseBranchArg });
1120
+
1121
+ // Step 4: Get current branch and repo info
1122
+ logger.debug('create-pr', 'Step 4: Getting current branch and repo info');
1123
+ const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
1124
+ if (!currentBranch) {
1125
+ logger.error('create-pr', 'Could not determine current branch');
1126
+ error('Could not determine current branch');
1127
+ return;
1128
+ }
1129
+
1130
+ const repoInfo = parseGitHubRepo();
1131
+ logger.debug('create-pr', 'Repository and branch info', {
1132
+ owner: repoInfo.owner,
1133
+ repo: repoInfo.repo,
1134
+ currentBranch,
1135
+ baseBranch
1136
+ });
1137
+
1138
+ showInfo(`Repository: ${repoInfo.fullName}`);
1139
+ showInfo(`Branch: ${currentBranch} → ${baseBranch}`);
1140
+
1141
+ // Step 5: Check for existing PR
1142
+ logger.debug('create-pr', 'Step 5: Checking for existing PR');
1143
+ const existingPR = await findExistingPR({
1144
+ owner: repoInfo.owner,
1145
+ repo: repoInfo.repo,
1146
+ head: currentBranch,
1147
+ base: baseBranch
1148
+ });
1149
+
1150
+ if (existingPR) {
1151
+ logger.debug('create-pr', 'Existing PR found, exiting', {
1152
+ prNumber: existingPR.number,
1153
+ prUrl: existingPR.html_url
1154
+ });
1155
+ showWarning(`A PR already exists for this branch: #${existingPR.number}`);
1156
+ console.log(` ${existingPR.html_url}`);
1157
+ console.log('');
1158
+ return;
1159
+ }
1160
+
1161
+ logger.debug('create-pr', 'No existing PR found, continuing');
1162
+
1163
+ // Step 6: Update remote and check for differences
1164
+ logger.debug('create-pr', 'Step 6: Fetching latest changes from remote');
1165
+ execSync('git fetch', { stdio: 'ignore' });
1166
+ const compareWith = `origin/${baseBranch}...HEAD`;
1167
+
1168
+ try {
1169
+ execSync(`git rev-parse --verify origin/${baseBranch}`, { stdio: 'ignore' });
1170
+ } catch (e) {
1171
+ error(`Base branch origin/${baseBranch} does not exist`);
1172
+ return;
1173
+ }
1174
+
1175
+ let diffFiles;
1176
+ try {
1177
+ diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
1178
+ if (!diffFiles) {
1179
+ showWarning('No differences with remote branch. Nothing to create a PR for.');
1180
+ return;
1181
+ }
1182
+ } catch (e) {
1183
+ error('Error getting differences: ' + e.message);
1184
+ return;
1185
+ }
1186
+
1187
+ const filesArray = diffFiles.split('\n').filter(f => f.trim());
1188
+ logger.debug('create-pr', 'Modified files detected', {
1189
+ fileCount: filesArray.length,
1190
+ files: filesArray
1191
+ });
1192
+ showInfo(`Found ${filesArray.length} modified file(s)`);
1193
+
1194
+ // Step 7: Generate PR metadata with Claude (reuse analyze-diff logic)
1195
+ logger.debug('create-pr', 'Step 7: Generating PR metadata with Claude');
1196
+ let fullDiff, commits;
1197
+ try {
1198
+ fullDiff = execSync(`git diff ${compareWith}`, { encoding: 'utf8' });
1199
+ commits = execSync(`git log origin/${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
1200
+ } catch (e) {
1201
+ error('Error getting diff or commits: ' + e.message);
1202
+ return;
1203
+ }
1204
+
1205
+ const truncatedDiff = fullDiff.length > 50000
1206
+ ? fullDiff.substring(0, 50000) + '\n... (truncated)'
1207
+ : fullDiff;
1208
+
1209
+ const contextDescription = `${currentBranch} vs origin/${baseBranch}`;
1210
+ const prompt = await loadPrompt('ANALYZE_DIFF.md', {
1211
+ CONTEXT_DESCRIPTION: contextDescription,
1212
+ SUBAGENT_INSTRUCTION: '',
1213
+ COMMITS: commits,
1214
+ DIFF_FILES: diffFiles,
1215
+ FULL_DIFF: truncatedDiff
1216
+ });
1217
+
1218
+ showInfo('Generating PR metadata with Claude...');
1219
+ logger.debug('create-pr', 'Calling Claude with prompt', { promptLength: prompt.length });
1220
+ const response = await executeClaude(prompt, { timeout: 180000 });
1221
+ logger.debug('create-pr', 'Claude response received', { responseLength: response.length });
1222
+
1223
+ const analysisResult = extractJSON(response);
1224
+ logger.debug('create-pr', 'Analysis result extracted', {
1225
+ hasResult: !!analysisResult,
1226
+ hasPrTitle: !!analysisResult?.prTitle
1227
+ });
1228
+
1229
+ if (!analysisResult || !analysisResult.prTitle) {
1230
+ logger.error('create-pr', 'Failed to generate PR metadata from analysis', { analysisResult });
1231
+ error('Failed to generate PR metadata from analysis');
1232
+ return;
1233
+ }
1234
+
1235
+ // Step 8: Prepare PR data
1236
+ logger.debug('create-pr', 'Step 8: Preparing PR data');
1237
+ let prTitle = analysisResult.prTitle;
1238
+ if (taskId) {
1239
+ prTitle = formatWithTaskId(prTitle, taskId);
1240
+ logger.debug('create-pr', 'Task ID added to title', { prTitle });
1241
+ }
1242
+
1243
+ const prBody = analysisResult.prDescription || analysisResult.description || '';
1244
+ logger.debug('create-pr', 'PR title and body prepared', {
1245
+ titleLength: prTitle.length,
1246
+ bodyLength: prBody.length
1247
+ });
1248
+
1249
+ // Step 9: Get labels from preset
1250
+ logger.debug('create-pr', 'Step 9: Getting labels from preset');
1251
+ let labels = [];
1252
+ if (config.preset && config.github?.pr?.labelRules) {
1253
+ labels = config.github.pr.labelRules[config.preset] || [];
1254
+ }
1255
+ if (analysisResult.breakingChanges) {
1256
+ labels.push('breaking-change');
1257
+ }
1258
+ logger.debug('create-pr', 'Labels determined', { labels, preset: config.preset });
1259
+
1260
+ // Step 10: Get reviewers from CODEOWNERS and config
1261
+ logger.debug('create-pr', 'Step 10: Getting reviewers from CODEOWNERS and config');
1262
+ const reviewers = await getReviewersForFiles(filesArray, config.github?.pr);
1263
+ logger.debug('create-pr', 'Reviewers determined', { reviewers, sources: 'CODEOWNERS + config' });
1264
+
1265
+ // Step 11: Show PR preview
1266
+ const prData = {
1267
+ title: prTitle,
1268
+ body: prBody,
1269
+ head: currentBranch,
1270
+ base: baseBranch,
1271
+ labels,
1272
+ reviewers
1273
+ };
1274
+
1275
+ showPRPreview(prData);
1276
+
1277
+ // Step 12: Prompt for confirmation
1278
+ const action = await promptMenu(
1279
+ 'What would you like to do?',
1280
+ [
1281
+ { key: 'c', label: 'Create PR' },
1282
+ { key: 'x', label: 'Cancel' }
1283
+ ],
1284
+ 'c'
1285
+ );
1286
+
1287
+ if (action === 'x') {
1288
+ showInfo('PR creation cancelled');
1289
+
1290
+ // Save metadata for later use
1291
+ const outputDir = '.claude/out';
1292
+ if (!fs.existsSync(outputDir)) {
1293
+ fs.mkdirSync(outputDir, { recursive: true });
1294
+ }
1295
+ const outputFile = path.join(outputDir, 'pr-metadata.json');
1296
+ fs.writeFileSync(outputFile, JSON.stringify(prData, null, 2));
1297
+ showInfo(`PR metadata saved to ${outputFile}`);
1298
+ return;
1299
+ }
1300
+
1301
+ // Step 13: Create PR via Octokit
1302
+ logger.debug('create-pr', 'Step 13: Creating PR via Octokit');
1303
+ showInfo('Creating pull request on GitHub...');
1304
+
1305
+ try {
1306
+ logger.debug('create-pr', 'Calling createPullRequest API', {
1307
+ owner: repoInfo.owner,
1308
+ repo: repoInfo.repo,
1309
+ head: prData.head,
1310
+ base: prData.base
1311
+ });
1312
+
1313
+ const result = await createPullRequest({
1314
+ owner: repoInfo.owner,
1315
+ repo: repoInfo.repo,
1316
+ title: prData.title,
1317
+ body: prData.body,
1318
+ head: prData.head,
1319
+ base: prData.base,
1320
+ draft: false,
1321
+ labels: prData.labels,
1322
+ reviewers: prData.reviewers
1323
+ });
1324
+
1325
+ logger.debug('create-pr', 'PR created successfully', {
1326
+ prNumber: result.number,
1327
+ prUrl: result.html_url
1328
+ });
1329
+
1330
+ console.log('');
1331
+ showSuccess('Pull request created successfully!');
1332
+ console.log('');
1333
+ console.log(` PR #${result.number}: ${result.html_url}`);
1334
+ console.log('');
1335
+
1336
+ if (result.reviewers.length > 0) {
1337
+ showInfo(`Reviewers requested: ${result.reviewers.join(', ')}`);
1338
+ }
1339
+ if (result.labels.length > 0) {
1340
+ showInfo(`Labels added: ${result.labels.join(', ')}`);
1341
+ }
1342
+
1343
+ } catch (apiError) {
1344
+ logger.error('create-pr', 'Failed to create pull request', apiError);
1345
+ showError('Failed to create pull request');
1346
+ console.error('');
1347
+ console.error(` ${apiError.message}`);
1348
+
1349
+ if (apiError.context?.suggestion) {
1350
+ console.error('');
1351
+ console.error(` šŸ’” ${apiError.context.suggestion}`);
1352
+ }
1353
+ console.error('');
1354
+
1355
+ // Save PR metadata for manual creation or retry
1356
+ const outputDir = '.claude/out';
1357
+ if (!fs.existsSync(outputDir)) {
1358
+ fs.mkdirSync(outputDir, { recursive: true });
1359
+ }
1360
+ const outputFile = path.join(outputDir, 'pr-metadata.json');
1361
+ fs.writeFileSync(outputFile, JSON.stringify({
1362
+ ...prData,
1363
+ error: apiError.message,
1364
+ timestamp: new Date().toISOString()
1365
+ }, null, 2));
1366
+
1367
+ logger.debug('create-pr', 'PR metadata saved', { outputFile });
1368
+ showInfo(`PR metadata saved to ${outputFile}`);
1369
+ showInfo('You can create the PR manually using this data');
1370
+
1371
+ process.exit(1);
1372
+ }
1373
+
1374
+ } catch (err) {
1375
+ logger.error('create-pr', 'Error creating PR', err);
1376
+ showError('Error creating PR: ' + err.message);
1377
+
1378
+ if (err.context) {
1379
+ logger.debug('create-pr', 'Error context', err.context);
1380
+ console.error('Context:', JSON.stringify(err.context, null, 2));
1381
+ }
1382
+
1383
+ process.exit(1);
1384
+ }
1385
+ }
1386
+
1387
+ // Setup GitHub authentication
1388
+ async function setupGitHub() {
1389
+ const { validateToken } = await import('../lib/utils/github-api.js');
1390
+
1391
+ console.log('');
1392
+ info('GitHub Authentication Setup');
1393
+ console.log('');
1394
+
1395
+ // Check existing token
1396
+ try {
1397
+ const validation = await validateToken();
1398
+ if (validation.valid) {
1399
+ success(`Already authenticated as: ${validation.user}`);
1400
+ console.log(` Scopes: ${validation.scopes.join(', ')}`);
1401
+
1402
+ if (!validation.hasRepoScope) {
1403
+ warning('Token lacks "repo" scope - PR creation may fail');
1404
+ }
1405
+
1406
+ console.log('');
1407
+ info('To use a different token, edit .claude/settings.local.json');
1408
+ return;
1409
+ }
1410
+ } catch (e) {
1411
+ // No token configured, continue with setup
1412
+ }
1413
+
1414
+ console.log('No GitHub token found. You have several options:');
1415
+ console.log('');
1416
+ console.log('Option 1: Create .claude/settings.local.json');
1417
+ console.log(' {');
1418
+ console.log(' "githubToken": "ghp_your_token_here"');
1419
+ console.log(' }');
1420
+ console.log('');
1421
+ console.log('Option 2: Set environment variable');
1422
+ console.log(' export GITHUB_TOKEN="ghp_your_token_here"');
1423
+ console.log('');
1424
+ console.log('Option 3: Run setup-mcp (if you also want MCP features)');
1425
+ console.log(' claude-hooks setup-mcp');
1426
+ console.log('');
1427
+ console.log('To create a token:');
1428
+ console.log(' 1. Go to https://github.com/settings/tokens/new');
1429
+ console.log(' 2. Select scopes: repo, read:org');
1430
+ console.log(' 3. Generate and copy the token');
1431
+ console.log('');
1432
+ }
1433
+
1020
1434
  // Comando status
1021
1435
  function status() {
1022
1436
  if (!checkGitRepo()) {
@@ -1165,6 +1579,8 @@ Commands:
1165
1579
  disable [hook] Disable hooks (all or one specific)
1166
1580
  status Show the status of hooks
1167
1581
  analyze-diff [base] Analyze differences between branches and generate PR info
1582
+ create-pr [base] Create pull request with auto-generated metadata and reviewers
1583
+ setup-github Setup GitHub login (required for create-pr)
1168
1584
  presets List all available presets
1169
1585
  --set-preset <name> Set the active preset
1170
1586
  preset current Show the current active preset
@@ -1184,6 +1600,9 @@ Examples:
1184
1600
  claude-hooks enable # Enable all hooks
1185
1601
  claude-hooks status # View current status
1186
1602
  claude-hooks analyze-diff main # Analyze differences with main
1603
+ claude-hooks setup-github # Configure GitHub authentication for PR creation
1604
+ claude-hooks setup-mcp # Setup GitHub MCP (one-time setup)
1605
+ claude-hooks create-pr develop # Create PR targeting develop branch
1187
1606
  claude-hooks presets # List available presets
1188
1607
  claude-hooks --set-preset backend # Set backend preset
1189
1608
  claude-hooks preset current # Show current preset
@@ -1202,6 +1621,20 @@ Analyze-diff use case:
1202
1621
  → PR Description: "## Summary\n- Added JWT authentication..."
1203
1622
  → Suggested branch: "feature/user-authentication"
1204
1623
 
1624
+ Create-pr use case (v2.5.0+):
1625
+ claude-hooks create-pr develop # Create PR targeting develop:
1626
+ → Validates GitHub token
1627
+ → Extracts task-id from branch (IX-123, #456, LIN-123)
1628
+ → Analyzes diff and generates PR metadata with Claude
1629
+ → Creates PR directly via GitHub API (Octokit)
1630
+ → Adds labels based on preset
1631
+ → Returns PR URL
1632
+
1633
+ Token configuration:
1634
+ → .claude/settings.local.json (recommended, gitignored)
1635
+ → GITHUB_TOKEN environment variable
1636
+ → Claude Desktop config (auto-detected)
1637
+
1205
1638
  Presets (v2.3.0+):
1206
1639
  Built-in tech-stack specific configurations:
1207
1640
  - backend: Spring Boot + SQL Server (.java, .xml, .yml)
@@ -1310,7 +1743,7 @@ async function updateConfig(propertyPath, value, options = {}) {
1310
1743
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
1311
1744
 
1312
1745
  // Show success message
1313
- const message = successMessage ? successMessage(value) : 'Configuration updated';
1746
+ const message = successMessage ? await successMessage(value) : 'Configuration updated';
1314
1747
  success(message);
1315
1748
  info(`Configuration saved to ${configPath}`);
1316
1749
  } catch (err) {
@@ -1472,6 +1905,15 @@ async function main() {
1472
1905
  case 'analyze-diff':
1473
1906
  await analyzeDiff(args.slice(1));
1474
1907
  break;
1908
+ case 'create-pr':
1909
+ await createPr(args.slice(1));
1910
+ break;
1911
+ case 'setup-mcp':
1912
+ await setupGitHubMCP();
1913
+ break;
1914
+ case 'setup-github':
1915
+ await setupGitHub();
1916
+ break;
1475
1917
  case 'presets':
1476
1918
  await showPresets();
1477
1919
  break;
package/lib/config.js CHANGED
@@ -38,6 +38,11 @@ const defaults = {
38
38
  commitMessage: {
39
39
  autoKeyword: 'auto', // Keyword to trigger auto-generation
40
40
  timeout: 300000,
41
+ // Task-ID detection pattern (from branch name)
42
+ // Default: 1-3 uppercase letters + separator + 3-5 digits
43
+ // Examples: "ABC-12345", "IX-123", "DE 4567"
44
+ // Regex is case-insensitive and extracted from branch name
45
+ taskIdPattern: '([A-Z]{1,3}[-\\s]\\d{3,5})'
41
46
  },
42
47
 
43
48
  // Subagent configuration (parallel analysis)
@@ -56,6 +61,7 @@ const defaults = {
56
61
  analyzeDiff: 'ANALYZE_DIFF.md', // PR analysis prompt
57
62
  resolution: 'CLAUDE_RESOLUTION_PROMPT.md', // Issue resolution prompt
58
63
  subagentInstruction: 'SUBAGENT_INSTRUCTION.md', // Parallel analysis instruction
64
+ createGithubPR: 'CREATE_GITHUB_PR.md', // GitHub PR creation via MCP
59
65
  },
60
66
 
61
67
  // Output file paths (relative to repo root)
@@ -76,6 +82,29 @@ const defaults = {
76
82
  git: {
77
83
  diffFilter: 'ACM', // Added, Copied, Modified (excludes Deleted)
78
84
  },
85
+
86
+ // GitHub integration (v2.5.0+)
87
+ github: {
88
+ enabled: true, // Changed from false - enable by default since Octokit works without MCP
89
+
90
+ // Pull Request configuration
91
+ pr: {
92
+ defaultBase: 'develop', // Default base branch for PRs
93
+
94
+ // Reviewers (usernames without @)
95
+ reviewers: [], // Example: ["juan.perez", "maria.garcia"]
96
+
97
+ // Labels by preset
98
+ labelRules: {
99
+ backend: ['backend', 'java'],
100
+ frontend: ['frontend', 'react'],
101
+ fullstack: ['fullstack'],
102
+ database: ['database', 'sql'],
103
+ ai: ['ai', 'tooling'],
104
+ default: []
105
+ },
106
+ },
107
+ },
79
108
  };
80
109
 
81
110
  /**
@@ -27,8 +27,6 @@ import {
27
27
  } from '../utils/git-operations.js';
28
28
  import {
29
29
  filterFiles,
30
- filterSkipAnalysis,
31
- readFile
32
30
  } from '../utils/file-operations.js';
33
31
  import { analyzeCode, analyzeCodeParallel, chunkArray } from '../utils/claude-client.js';
34
32
  import { buildAnalysisPrompt } from '../utils/prompt-builder.js';
@@ -282,6 +280,8 @@ const main = async () => {
282
280
  logger.warning('Consider splitting the commit into smaller parts');
283
281
  process.exit(0);
284
282
  }
283
+
284
+ logger.info("test log");
285
285
 
286
286
  // Step 3: Build file data for prompt
287
287
  logger.debug('pre-commit - main', 'Building file data for analysis');
@@ -290,9 +290,6 @@ const main = async () => {
290
290
  // Get diff
291
291
  let diff = getFileDiff(filePath);
292
292
 
293
- // Apply SKIP_ANALYSIS filtering
294
- diff = filterSkipAnalysis(diff);
295
-
296
293
  // Check if new file
297
294
  const isNew = isNewFile(filePath);
298
295
 
@@ -300,7 +297,6 @@ const main = async () => {
300
297
  let content = null;
301
298
  if (isNew) {
302
299
  content = await getFileContentFromStaging(filePath);
303
- content = filterSkipAnalysis(content);
304
300
  }
305
301
 
306
302
  return {