claude-git-hooks 2.19.0 → 2.21.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 +46 -0
- package/CLAUDE.md +115 -39
- package/README.md +99 -22
- package/bin/claude-hooks +1 -0
- package/lib/cli-metadata.js +26 -1
- package/lib/commands/analyze-pr.js +683 -0
- package/lib/commands/diff-batch-info.js +105 -0
- package/lib/commands/help.js +70 -38
- package/lib/commands/install.js +0 -10
- package/lib/commands/setup-linear.js +96 -0
- package/lib/config.js +21 -27
- package/lib/hooks/pre-commit.js +43 -17
- package/lib/hooks/prepare-commit-msg.js +3 -24
- package/lib/utils/analysis-engine.js +62 -46
- package/lib/utils/claude-client.js +21 -69
- package/lib/utils/diff-analysis-orchestrator.js +332 -0
- package/lib/utils/github-api.js +176 -112
- package/lib/utils/judge.js +195 -0
- package/lib/utils/linear-connector.js +287 -0
- package/lib/utils/package-info.js +0 -11
- package/lib/utils/pr-statistics.js +85 -0
- package/lib/utils/prompt-builder.js +15 -21
- package/lib/utils/resolution-prompt.js +1 -8
- package/lib/utils/telemetry.js +46 -10
- package/lib/utils/token-store.js +159 -0
- package/package.json +1 -1
- package/templates/ANALYZE_PR.md +79 -0
- package/templates/CLAUDE_RESOLUTION_PROMPT.md +17 -9
- package/templates/DIFF_ANALYSIS_ORCHESTRATION_PROMPT.md +70 -0
- package/templates/config.advanced.example.json +56 -31
- package/templates/config.example.json +0 -11
- package/templates/settings.local.example.json +2 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: diff-batch-info.js
|
|
3
|
+
* Purpose: Display intelligent diff-analysis orchestration config and speed telemetry
|
|
4
|
+
*
|
|
5
|
+
* Shows:
|
|
6
|
+
* - Orchestration model and threshold
|
|
7
|
+
* - Default analysis model from config
|
|
8
|
+
* - Per-model avg analysis times from telemetry
|
|
9
|
+
* - Avg orchestration overhead
|
|
10
|
+
* - Overall failure rate
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getStatistics } from '../utils/telemetry.js';
|
|
14
|
+
import logger from '../utils/logger.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs the batch-info command
|
|
18
|
+
* Why: Surfaces orchestration configuration and speed telemetry so users can
|
|
19
|
+
* understand the adaptive batch system behavior without needing debug mode.
|
|
20
|
+
*/
|
|
21
|
+
export async function runDiffBatchInfo() {
|
|
22
|
+
console.log(
|
|
23
|
+
'\n╔════════════════════════════════════════════════════════════════════╗'
|
|
24
|
+
);
|
|
25
|
+
console.log(
|
|
26
|
+
'║ INTELLIGENT ANALYSIS ORCHESTRATION INFO ║'
|
|
27
|
+
);
|
|
28
|
+
console.log(
|
|
29
|
+
'╚════════════════════════════════════════════════════════════════════╝\n'
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
console.log('━━━ ORCHESTRATION CONFIGURATION ━━━');
|
|
33
|
+
console.log(' Orchestration model: opus (internal, not user-configurable)');
|
|
34
|
+
console.log(' Orchestrator threshold: 3 files (commits with ≥3 files use intelligent grouping)');
|
|
35
|
+
console.log();
|
|
36
|
+
|
|
37
|
+
console.log('━━━ HOW IT WORKS ━━━');
|
|
38
|
+
console.log(' < 3 files: Sequential analysis (single Claude call)');
|
|
39
|
+
console.log(' ≥ 3 files: Orchestrator groups files semantically,');
|
|
40
|
+
console.log(' assigns model per batch, injects shared context');
|
|
41
|
+
console.log();
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const stats = await getStatistics(7);
|
|
45
|
+
|
|
46
|
+
if (!stats.enabled) {
|
|
47
|
+
console.log(' Telemetry is disabled — no speed data available.');
|
|
48
|
+
console.log(
|
|
49
|
+
' To enable, ensure "system.telemetry" is not set to false in .claude/config.json'
|
|
50
|
+
);
|
|
51
|
+
console.log();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (stats.totalEvents === 0) {
|
|
56
|
+
console.log('━━━ SPEED TELEMETRY (last 7 days) ━━━');
|
|
57
|
+
console.log(' No telemetry data yet.');
|
|
58
|
+
console.log(
|
|
59
|
+
' Run an analysis with ≥3 files to start collecting per-model timing data.'
|
|
60
|
+
);
|
|
61
|
+
console.log();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('━━━ SPEED TELEMETRY (last 7 days) ━━━');
|
|
66
|
+
console.log(` Total events: ${stats.totalEvents}`);
|
|
67
|
+
console.log(` Successful batches: ${stats.batchSuccesses}`);
|
|
68
|
+
console.log(` Failure rate: ${stats.failureRate}%`);
|
|
69
|
+
console.log();
|
|
70
|
+
|
|
71
|
+
if (Object.keys(stats.avgAnalysisTimeByModel).length > 0) {
|
|
72
|
+
console.log(' Average analysis time by model:');
|
|
73
|
+
for (const [model, ms] of Object.entries(stats.avgAnalysisTimeByModel)) {
|
|
74
|
+
const seconds = (ms / 1000).toFixed(1);
|
|
75
|
+
console.log(` ${model.padEnd(8)}: ${seconds}s`);
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (stats.avgOrchestrationTime > 0) {
|
|
81
|
+
const orchSeconds = (stats.avgOrchestrationTime / 1000).toFixed(1);
|
|
82
|
+
console.log(` Avg orchestration overhead: ${orchSeconds}s`);
|
|
83
|
+
console.log(
|
|
84
|
+
' (Orchestrator call for semantic grouping — one-time per commit)'
|
|
85
|
+
);
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (Object.keys(stats.successesByHook).length > 0) {
|
|
90
|
+
console.log(' Successes by hook:');
|
|
91
|
+
for (const [hook, count] of Object.entries(stats.successesByHook)) {
|
|
92
|
+
console.log(` ${hook}: ${count}`);
|
|
93
|
+
}
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
logger.debug('diff-batch-info - runDiffBatchInfo', 'Failed to load telemetry', err);
|
|
98
|
+
console.log(' Could not load telemetry data.');
|
|
99
|
+
console.log();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('📂 Telemetry files: .claude/telemetry/');
|
|
103
|
+
console.log('💡 Use --debug true to see orchestrator decisions in real time');
|
|
104
|
+
console.log();
|
|
105
|
+
}
|
package/lib/commands/help.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Purpose: Help, version display, and AI-powered help commands
|
|
4
4
|
*
|
|
5
5
|
* Features:
|
|
6
|
-
* -
|
|
6
|
+
* - Dynamic help from command registry (no args, --help, -h)
|
|
7
7
|
* - AI help: routes questions to Claude with CLAUDE.md as knowledge base
|
|
8
8
|
* - Report issue: interactive issue creation guided by Claude
|
|
9
9
|
*/
|
|
@@ -17,6 +17,7 @@ import { loadPrompt } from '../utils/prompt-builder.js';
|
|
|
17
17
|
import { fetchFileContent, fetchDirectoryListing, createIssue } from '../utils/github-api.js';
|
|
18
18
|
import { promptMenu, promptEditField, promptConfirmation } from '../utils/interactive-ui.js';
|
|
19
19
|
import logger from '../utils/logger.js';
|
|
20
|
+
import { commands } from '../cli-metadata.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Get claude-hooks source repo coordinates from package.json
|
|
@@ -67,56 +68,87 @@ export async function runShowHelp(args = []) {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
|
-
* Display
|
|
71
|
-
* Why:
|
|
71
|
+
* Display help text generated from command registry
|
|
72
|
+
* Why: Single source of truth — adding a command to cli-metadata.js auto-updates help
|
|
72
73
|
*/
|
|
73
74
|
export function showStaticHelp() {
|
|
75
|
+
const lines = formatCommandLines(commands);
|
|
76
|
+
|
|
74
77
|
console.log(`
|
|
75
78
|
Claude Git Hooks - Code analysis and automatic messages with Claude CLI
|
|
76
79
|
|
|
77
80
|
Usage: claude-hooks <command> [options]
|
|
78
81
|
|
|
79
82
|
Commands:
|
|
80
|
-
|
|
81
|
-
analyze-diff [base] Analyze diff and generate PR metadata
|
|
82
|
-
bump-version [type] [options] Bump version with CHANGELOG and Git tag
|
|
83
|
-
<type>: major | minor | patch (optional for suffix-only operations)
|
|
84
|
-
--dry-run Preview changes without applying
|
|
85
|
-
--interactive Force file selection menu
|
|
86
|
-
--no-commit Skip automatic commit
|
|
87
|
-
--no-tag Skip Git tag creation
|
|
88
|
-
--push Push tag to remote
|
|
89
|
-
--remove-suffix Remove version suffix (no type required)
|
|
90
|
-
--set-suffix <value> Set/replace version suffix (no type required)
|
|
91
|
-
--suffix <value> Add version suffix (e.g., SNAPSHOT)
|
|
92
|
-
--update-changelog [branch] Generate CHANGELOG entry
|
|
93
|
-
create-pr [base] Create PR with auto-generated metadata
|
|
94
|
-
--debug <true | false | status> Toggle debug mode
|
|
95
|
-
disable [hook] Disable hooks (all or specific)
|
|
96
|
-
enable [hook] Enable hooks (all or specific)
|
|
97
|
-
generate-changelog [version] Generate CHANGELOG entry independently
|
|
98
|
-
--base-branch <branch> Compare against branch (default: main)
|
|
99
|
-
--release Mark as released
|
|
100
|
-
help [question] Show help or ask AI a question
|
|
101
|
-
--report-issue Create GitHub issue interactively
|
|
102
|
-
install [options] Install hooks in current repository
|
|
103
|
-
--force Reinstall even if already exist
|
|
104
|
-
--skip-auth Skip Claude authentication check
|
|
105
|
-
migrate-config Migrate legacy config to v2.8.0 format
|
|
106
|
-
preset current Show current active preset
|
|
107
|
-
presets List all available presets
|
|
108
|
-
--set-preset <name> Set the active preset
|
|
109
|
-
setup-github Configure GitHub token for PR creation
|
|
110
|
-
status Show hook status
|
|
111
|
-
telemetry [show | clear] View or clear telemetry data
|
|
112
|
-
uninstall Remove hooks from repository
|
|
113
|
-
update Update to latest version
|
|
114
|
-
--version, -v Show current version
|
|
83
|
+
${lines}
|
|
115
84
|
|
|
116
85
|
More information: https://github.com/mscope-S-L/git-hooks
|
|
117
86
|
`);
|
|
118
87
|
}
|
|
119
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Format command registry entries into aligned help lines
|
|
91
|
+
*
|
|
92
|
+
* @param {Array} cmds - Command entries from cli-metadata.js
|
|
93
|
+
* @returns {string} Formatted, alphabetically sorted command lines
|
|
94
|
+
*/
|
|
95
|
+
function formatCommandLines(cmds) {
|
|
96
|
+
const entries = [];
|
|
97
|
+
|
|
98
|
+
for (const cmd of cmds) {
|
|
99
|
+
// Skip version/help aliases (shown via their primary entry)
|
|
100
|
+
if (cmd.name === 'version') continue;
|
|
101
|
+
|
|
102
|
+
// Build display name with args/aliases
|
|
103
|
+
let name = cmd.name;
|
|
104
|
+
if (cmd.aliases) {
|
|
105
|
+
name = `${cmd.name}, ${cmd.aliases.join(', ')}`;
|
|
106
|
+
}
|
|
107
|
+
if (cmd.args) {
|
|
108
|
+
const argLabel = cmd.args.values
|
|
109
|
+
? cmd.args.values.join(' | ')
|
|
110
|
+
: cmd.args.name;
|
|
111
|
+
name += ` [${argLabel}]`;
|
|
112
|
+
}
|
|
113
|
+
if (cmd.subcommands) {
|
|
114
|
+
name += ` [${cmd.subcommands.join(' | ')}]`;
|
|
115
|
+
}
|
|
116
|
+
if (cmd.flags) {
|
|
117
|
+
name += ' [options]';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
entries.push({ name, description: cmd.description, flags: cmd.flags });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Sort alphabetically (strip leading -- for comparison)
|
|
124
|
+
entries.sort((a, b) => {
|
|
125
|
+
const aKey = a.name.replace(/^--/, '');
|
|
126
|
+
const bKey = b.name.replace(/^--/, '');
|
|
127
|
+
return aKey.localeCompare(bKey);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Calculate padding
|
|
131
|
+
const maxLen = Math.max(...entries.map((e) => e.name.length));
|
|
132
|
+
|
|
133
|
+
const lines = [];
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
const padding = ' '.repeat(maxLen - entry.name.length + 4);
|
|
136
|
+
lines.push(` ${entry.name}${padding}${entry.description}`);
|
|
137
|
+
|
|
138
|
+
// Add flag lines indented under command
|
|
139
|
+
if (entry.flags) {
|
|
140
|
+
const flagEntries = Object.entries(entry.flags).sort(([a], [b]) => a.localeCompare(b));
|
|
141
|
+
for (const [flag, meta] of flagEntries) {
|
|
142
|
+
const flagName = meta.takesValue ? `${flag} <value>` : flag;
|
|
143
|
+
const flagPad = ' '.repeat(maxLen - flagName.length + 2);
|
|
144
|
+
lines.push(` ${flagName}${flagPad}${meta.description}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
/**
|
|
121
153
|
* Print AI help response with version source footer
|
|
122
154
|
*
|
package/lib/commands/install.js
CHANGED
|
@@ -327,11 +327,6 @@ export function extractLegacySettings(rawConfig) {
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
// Subagent batchSize (allowed)
|
|
331
|
-
if (rawConfig.subagents?.batchSize !== undefined) {
|
|
332
|
-
allowedOverrides.subagents = { batchSize: rawConfig.subagents.batchSize };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
330
|
// Advanced params (preserved with warning in manual migration)
|
|
336
331
|
if (rawConfig.analysis?.ignoreExtensions !== undefined) {
|
|
337
332
|
if (!allowedOverrides.analysis) allowedOverrides.analysis = {};
|
|
@@ -343,11 +338,6 @@ export function extractLegacySettings(rawConfig) {
|
|
|
343
338
|
allowedOverrides.commitMessage.taskIdPattern = rawConfig.commitMessage.taskIdPattern;
|
|
344
339
|
}
|
|
345
340
|
|
|
346
|
-
if (rawConfig.subagents?.model !== undefined) {
|
|
347
|
-
if (!allowedOverrides.subagents) allowedOverrides.subagents = {};
|
|
348
|
-
allowedOverrides.subagents.model = rawConfig.subagents.model;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
341
|
return allowedOverrides;
|
|
352
342
|
}
|
|
353
343
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: setup-linear.js
|
|
3
|
+
* Purpose: Interactive Linear authentication setup command
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Checking existing token
|
|
7
|
+
* - Prompting user for new token
|
|
8
|
+
* - Validating token via Linear API
|
|
9
|
+
* - Saving token to .claude/settings.local.json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { testConnection, loadLinearToken } from '../utils/linear-connector.js';
|
|
13
|
+
import { saveToken } from '../utils/token-store.js';
|
|
14
|
+
import {
|
|
15
|
+
promptConfirmation,
|
|
16
|
+
promptEditField,
|
|
17
|
+
showSuccess,
|
|
18
|
+
showError,
|
|
19
|
+
showInfo,
|
|
20
|
+
showWarning
|
|
21
|
+
} from '../utils/interactive-ui.js';
|
|
22
|
+
import logger from '../utils/logger.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Run Linear authentication setup
|
|
26
|
+
* Why: Interactive flow to configure Linear token for PR analysis enrichment
|
|
27
|
+
*
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
*/
|
|
30
|
+
export async function runSetupLinear() {
|
|
31
|
+
showInfo('Linear Authentication Setup');
|
|
32
|
+
|
|
33
|
+
// Check existing token
|
|
34
|
+
const existingToken = loadLinearToken();
|
|
35
|
+
if (existingToken) {
|
|
36
|
+
showInfo('Existing Linear token found. Validating...');
|
|
37
|
+
|
|
38
|
+
const healthy = await testConnection(existingToken);
|
|
39
|
+
if (healthy) {
|
|
40
|
+
showSuccess('Linear token is valid and connected.');
|
|
41
|
+
|
|
42
|
+
const reconfigure = await promptConfirmation('Configure a different token?', false);
|
|
43
|
+
if (!reconfigure) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
showWarning('Existing token is invalid or expired.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Show instructions
|
|
52
|
+
logger.info('Create a Linear API key:');
|
|
53
|
+
logger.info(' 1. Go to: https://linear.app/settings/api');
|
|
54
|
+
logger.info(' 2. Click "Create key"');
|
|
55
|
+
logger.info(' 3. Give it a label (e.g., "claude-hooks")');
|
|
56
|
+
logger.info(' 4. Copy the key (starts with lin_api_...)');
|
|
57
|
+
|
|
58
|
+
// Prompt for token
|
|
59
|
+
const token = await promptEditField('Paste your Linear API key (lin_api_...)', '');
|
|
60
|
+
|
|
61
|
+
if (!token || token.trim() === '') {
|
|
62
|
+
showWarning('No token provided. Setup cancelled.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const trimmedToken = token.trim();
|
|
67
|
+
|
|
68
|
+
// Basic format check
|
|
69
|
+
if (!trimmedToken.startsWith('lin_api_')) {
|
|
70
|
+
showWarning('Token format looks unusual (expected lin_api_...)');
|
|
71
|
+
const proceed = await promptConfirmation('Continue anyway?', false);
|
|
72
|
+
if (!proceed) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Save token
|
|
78
|
+
const saveResult = saveToken('linearToken', trimmedToken);
|
|
79
|
+
if (!saveResult.success) {
|
|
80
|
+
showError(`Failed to save token: ${saveResult.error}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
showSuccess(`Token saved to ${saveResult.path}`);
|
|
84
|
+
|
|
85
|
+
// Validate with Linear API
|
|
86
|
+
showInfo('Validating token...');
|
|
87
|
+
|
|
88
|
+
const healthy = await testConnection(trimmedToken);
|
|
89
|
+
if (healthy) {
|
|
90
|
+
showSuccess('Linear connection verified.');
|
|
91
|
+
} else {
|
|
92
|
+
showWarning('Token validation failed - may not work correctly');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
showSuccess('Setup complete! Linear ticket context will be used in: claude-hooks analyze-pr');
|
|
96
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* v2.8.0 Changes:
|
|
8
8
|
* - Removed 21 redundant parameters (hardcoded sensible defaults)
|
|
9
|
-
* - Only
|
|
10
|
-
* -
|
|
11
|
-
* - User can override: github.pr.*, subagents.batchSize
|
|
9
|
+
* - Only 4 user-configurable parameters remain
|
|
10
|
+
* - User can override: github.pr.*
|
|
12
11
|
* - Advanced params moved to config.advanced.example.json
|
|
13
12
|
*
|
|
14
13
|
* User-configurable:
|
|
@@ -16,12 +15,10 @@
|
|
|
16
15
|
* - github.pr.defaultBase
|
|
17
16
|
* - github.pr.reviewers
|
|
18
17
|
* - github.pr.labelRules
|
|
19
|
-
* - subagents.batchSize
|
|
20
18
|
*
|
|
21
19
|
* Advanced (in example file only):
|
|
22
20
|
* - analysis.ignoreExtensions
|
|
23
21
|
* - commitMessage.taskIdPattern
|
|
24
|
-
* - subagents.model
|
|
25
22
|
*/
|
|
26
23
|
|
|
27
24
|
import fs from 'fs';
|
|
@@ -35,7 +32,7 @@ import logger from './utils/logger.js';
|
|
|
35
32
|
const HARDCODED = {
|
|
36
33
|
analysis: {
|
|
37
34
|
maxFileSize: 1000000, // 1MB - sufficient for most files
|
|
38
|
-
maxFiles:
|
|
35
|
+
maxFiles: 30, // Reasonable limit per commit
|
|
39
36
|
timeout: 300000, // 5 minutes - adequate for Claude API
|
|
40
37
|
contextLines: 3, // Git default
|
|
41
38
|
ignoreExtensions: [] // Can be set in advanced config only
|
|
@@ -46,9 +43,7 @@ const HARDCODED = {
|
|
|
46
43
|
taskIdPattern: '([A-Z]{1,3}[-\\s]\\d{3,5})' // Jira/GitHub/Linear pattern
|
|
47
44
|
},
|
|
48
45
|
subagents: {
|
|
49
|
-
enabled: true
|
|
50
|
-
model: 'haiku', // Fast and cost-effective
|
|
51
|
-
batchSize: 3 // Reasonable parallelization
|
|
46
|
+
enabled: true // Enable by default (faster analysis via orchestration)
|
|
52
47
|
},
|
|
53
48
|
templates: {
|
|
54
49
|
baseDir: '.claude/prompts',
|
|
@@ -57,7 +52,6 @@ const HARDCODED = {
|
|
|
57
52
|
commitMessage: 'COMMIT_MESSAGE.md',
|
|
58
53
|
analyzeDiff: 'ANALYZE_DIFF.md',
|
|
59
54
|
resolution: 'CLAUDE_RESOLUTION_PROMPT.md',
|
|
60
|
-
subagentInstruction: 'SUBAGENT_INSTRUCTION.md',
|
|
61
55
|
createGithubPR: 'CREATE_GITHUB_PR.md'
|
|
62
56
|
},
|
|
63
57
|
output: {
|
|
@@ -76,6 +70,21 @@ const HARDCODED = {
|
|
|
76
70
|
},
|
|
77
71
|
github: {
|
|
78
72
|
enabled: true // Always enabled
|
|
73
|
+
},
|
|
74
|
+
prAnalysis: {
|
|
75
|
+
model: 'sonnet',
|
|
76
|
+
timeout: 300000, // 5 minutes
|
|
77
|
+
inlineCategories: ['bug', 'security', 'performance', 'hotspot'],
|
|
78
|
+
generalCategories: [
|
|
79
|
+
'ticket-alignment',
|
|
80
|
+
'scope',
|
|
81
|
+
'style',
|
|
82
|
+
'good-practice',
|
|
83
|
+
'extensibility',
|
|
84
|
+
'observability',
|
|
85
|
+
'documentation',
|
|
86
|
+
'testing'
|
|
87
|
+
]
|
|
79
88
|
}
|
|
80
89
|
};
|
|
81
90
|
|
|
@@ -106,10 +115,6 @@ const defaults = {
|
|
|
106
115
|
}
|
|
107
116
|
},
|
|
108
117
|
|
|
109
|
-
// Subagent configuration (preset can override)
|
|
110
|
-
subagents: {
|
|
111
|
-
batchSize: 3 // Files per parallel batch
|
|
112
|
-
}
|
|
113
118
|
};
|
|
114
119
|
|
|
115
120
|
/**
|
|
@@ -196,8 +201,8 @@ const loadUserConfig = async (baseDir = process.cwd()) => {
|
|
|
196
201
|
|
|
197
202
|
/**
|
|
198
203
|
* Extracts only allowed parameters from legacy config
|
|
199
|
-
*
|
|
200
|
-
* Advanced: analysis.ignoreExtensions, commitMessage.taskIdPattern
|
|
204
|
+
* Allowed: github.pr.*
|
|
205
|
+
* Advanced: analysis.ignoreExtensions, commitMessage.taskIdPattern
|
|
201
206
|
*
|
|
202
207
|
* @param {Object} legacyConfig - Legacy format config
|
|
203
208
|
* @returns {Object} Allowed parameters only
|
|
@@ -219,11 +224,6 @@ const extractAllowedParams = (legacyConfig) => {
|
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
226
|
|
|
222
|
-
// Subagent batchSize (allowed)
|
|
223
|
-
if (legacyConfig.subagents?.batchSize !== undefined) {
|
|
224
|
-
allowed.subagents = { batchSize: legacyConfig.subagents.batchSize };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
227
|
// Advanced params (allowed but warn)
|
|
228
228
|
if (legacyConfig.analysis?.ignoreExtensions !== undefined) {
|
|
229
229
|
if (!allowed.analysis) allowed.analysis = {};
|
|
@@ -237,12 +237,6 @@ const extractAllowedParams = (legacyConfig) => {
|
|
|
237
237
|
logger.warning('ℹ️ Using advanced parameter: commitMessage.taskIdPattern');
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
if (legacyConfig.subagents?.model !== undefined) {
|
|
241
|
-
if (!allowed.subagents) allowed.subagents = {};
|
|
242
|
-
allowed.subagents.model = legacyConfig.subagents.model;
|
|
243
|
-
logger.warning('ℹ️ Using advanced parameter: subagents.model');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
240
|
return allowed;
|
|
247
241
|
};
|
|
248
242
|
|
package/lib/hooks/pre-commit.js
CHANGED
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
|
|
22
22
|
import { getStagedFiles, getRepoRoot } from '../utils/git-operations.js';
|
|
23
23
|
import { filterFiles } from '../utils/file-operations.js';
|
|
24
|
-
import { buildFilesData, runAnalysis, displayResults } from '../utils/analysis-engine.js';
|
|
24
|
+
import { buildFilesData, runAnalysis, displayResults, hasAnyIssues } from '../utils/analysis-engine.js';
|
|
25
25
|
import { generateResolutionPrompt, shouldGeneratePrompt } from '../utils/resolution-prompt.js';
|
|
26
26
|
import { loadPreset } from '../utils/preset-loader.js';
|
|
27
|
-
import { getVersion
|
|
27
|
+
import { getVersion } from '../utils/package-info.js';
|
|
28
28
|
import logger from '../utils/logger.js';
|
|
29
29
|
import { getConfig } from '../config.js';
|
|
30
30
|
|
|
@@ -145,21 +145,9 @@ const main = async () => {
|
|
|
145
145
|
// Step 4: Log analysis configuration
|
|
146
146
|
logger.info(`Sending ${filesData.length} files for review...`);
|
|
147
147
|
|
|
148
|
-
// Display
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const batchSize = config.subagents?.batchSize || 3;
|
|
152
|
-
|
|
153
|
-
if (subagentsEnabled && filesData.length >= 3) {
|
|
154
|
-
const { numBatches, shouldShowBatches } = calculateBatches(filesData.length, batchSize);
|
|
155
|
-
console.log(
|
|
156
|
-
`⚡ Batch optimization: ${subagentModel} model, ${batchSize} files per batch`
|
|
157
|
-
);
|
|
158
|
-
if (shouldShowBatches) {
|
|
159
|
-
console.log(
|
|
160
|
-
`📊 Analyzing ${filesData.length} files in ${numBatches} batch${numBatches > 1 ? 'es' : ''}`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
148
|
+
// Display analysis routing hint
|
|
149
|
+
if (filesData.length >= 3) {
|
|
150
|
+
logger.info('⚡ Intelligent orchestration: grouping files and assigning models');
|
|
163
151
|
}
|
|
164
152
|
|
|
165
153
|
// Step 5: Run analysis using shared engine
|
|
@@ -171,6 +159,44 @@ const main = async () => {
|
|
|
171
159
|
// Step 6: Display results using shared function
|
|
172
160
|
displayResults(result);
|
|
173
161
|
|
|
162
|
+
// Step 6.5: Judge — auto-fix all issues
|
|
163
|
+
if (config.judge?.enabled !== false && hasAnyIssues(result)) {
|
|
164
|
+
try {
|
|
165
|
+
const { judgeAndFix } = await import('../utils/judge.js');
|
|
166
|
+
const judgeResult = await judgeAndFix(result, filesData, config);
|
|
167
|
+
|
|
168
|
+
// Update result with remaining issues (fixed + false positives removed)
|
|
169
|
+
result.blockingIssues = judgeResult.remainingIssues.filter(
|
|
170
|
+
(i) =>
|
|
171
|
+
['blocker', 'critical'].includes(
|
|
172
|
+
(i.severity || '').toLowerCase()
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
result.details = judgeResult.remainingIssues;
|
|
176
|
+
|
|
177
|
+
// Recalculate quality gate — pass only if ALL issues resolved
|
|
178
|
+
if (judgeResult.remainingIssues.length === 0) {
|
|
179
|
+
result.issues = {
|
|
180
|
+
blocker: 0,
|
|
181
|
+
critical: 0,
|
|
182
|
+
major: 0,
|
|
183
|
+
minor: 0,
|
|
184
|
+
info: 0
|
|
185
|
+
};
|
|
186
|
+
result.QUALITY_GATE = 'PASSED';
|
|
187
|
+
result.approved = true;
|
|
188
|
+
} else {
|
|
189
|
+
result.QUALITY_GATE = 'FAILED';
|
|
190
|
+
result.approved = false;
|
|
191
|
+
}
|
|
192
|
+
} catch (err) {
|
|
193
|
+
logger.warning(`Judge failed: ${err.message}`);
|
|
194
|
+
logger.warning('Commit blocked — judge could not verify issues');
|
|
195
|
+
result.QUALITY_GATE = 'FAILED';
|
|
196
|
+
result.approved = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
174
200
|
// Step 7: Check quality gate
|
|
175
201
|
const qualityGatePassed = result.QUALITY_GATE === 'PASSED';
|
|
176
202
|
const approved = result.approved !== false;
|
|
@@ -21,7 +21,7 @@ import fs from 'fs/promises';
|
|
|
21
21
|
import { getStagedFiles, getStagedStats, getFileDiff } from '../utils/git-operations.js';
|
|
22
22
|
import { analyzeCode } from '../utils/claude-client.js';
|
|
23
23
|
import { loadPrompt } from '../utils/prompt-builder.js';
|
|
24
|
-
import { getVersion
|
|
24
|
+
import { getVersion } from '../utils/package-info.js';
|
|
25
25
|
import logger from '../utils/logger.js';
|
|
26
26
|
import { getConfig } from '../config.js';
|
|
27
27
|
import { getOrPromptTaskId, formatWithTaskId } from '../utils/task-id.js';
|
|
@@ -152,14 +152,7 @@ const main = async () => {
|
|
|
152
152
|
|
|
153
153
|
// Display configuration info
|
|
154
154
|
const version = await getVersion();
|
|
155
|
-
const subagentsEnabled = config.subagents?.enabled || false;
|
|
156
|
-
const subagentModel = config.subagents?.model || 'haiku';
|
|
157
|
-
const batchSize = config.subagents?.batchSize || 3;
|
|
158
|
-
|
|
159
155
|
console.log(`\n🤖 claude-git-hooks v${version}`);
|
|
160
|
-
if (subagentsEnabled) {
|
|
161
|
-
console.log(`⚡ Parallel analysis: ${subagentModel} model, batch size ${batchSize}`);
|
|
162
|
-
}
|
|
163
156
|
|
|
164
157
|
logger.info('Generating commit message automatically...');
|
|
165
158
|
|
|
@@ -235,28 +228,14 @@ const main = async () => {
|
|
|
235
228
|
|
|
236
229
|
logger.debug('prepare-commit-msg - main', 'Prompt built', { promptLength: prompt.length });
|
|
237
230
|
|
|
238
|
-
// Calculate batches if subagents enabled and applicable
|
|
239
|
-
if (subagentsEnabled && filesData.length >= 3) {
|
|
240
|
-
const { numBatches, shouldShowBatches } = calculateBatches(filesData.length, batchSize);
|
|
241
|
-
if (shouldShowBatches) {
|
|
242
|
-
console.log(
|
|
243
|
-
`📊 Analyzing ${filesData.length} files in ${numBatches} batch${numBatches > 1 ? 'es' : ''}`
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
231
|
// Generate message with Claude
|
|
249
232
|
logger.info('Sending to Claude...');
|
|
250
233
|
|
|
251
234
|
// Build telemetry context
|
|
252
235
|
const telemetryContext = {
|
|
253
236
|
fileCount: filesData.length,
|
|
254
|
-
batchSize:
|
|
255
|
-
totalBatches:
|
|
256
|
-
subagentsEnabled && filesData.length >= 3
|
|
257
|
-
? Math.ceil(filesData.length / (config.subagents?.batchSize || 3))
|
|
258
|
-
: 1,
|
|
259
|
-
model: subagentModel,
|
|
237
|
+
batchSize: filesData.length,
|
|
238
|
+
totalBatches: 1,
|
|
260
239
|
hook: 'prepare-commit-msg'
|
|
261
240
|
};
|
|
262
241
|
|