claude-git-hooks 2.44.0 → 2.51.2
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 +147 -0
- package/CLAUDE.md +11 -12
- package/README.md +7 -0
- package/lib/commands/analyze-diff.js +27 -0
- package/lib/commands/analyze-pr.js +9 -17
- package/lib/commands/back-merge.js +14 -0
- package/lib/commands/close-release.js +22 -0
- package/lib/commands/create-pr.js +32 -0
- package/lib/commands/create-release.js +22 -0
- package/lib/commands/help.js +29 -58
- package/lib/config.js +19 -91
- package/lib/defaults.json +130 -0
- package/lib/hooks/pre-commit.js +36 -0
- package/lib/utils/analysis-engine.js +7 -3
- package/lib/utils/claude-client.js +9 -5
- package/lib/utils/config-registry.js +257 -0
- package/lib/utils/diff-analysis-orchestrator.js +13 -8
- package/lib/utils/judge.js +7 -4
- package/lib/utils/linear-connector.js +6 -4
- package/lib/utils/linter-runner.js +16 -1
- package/lib/utils/pr-metadata-engine.js +11 -8
- package/lib/utils/version-manager.js +6 -8
- package/package.json +12 -4
|
@@ -23,10 +23,11 @@ import path from 'path';
|
|
|
23
23
|
import { executeClaudeWithRetry, extractJSON } from './claude-client.js';
|
|
24
24
|
import { loadPrompt } from './prompt-builder.js';
|
|
25
25
|
import logger from './logger.js';
|
|
26
|
+
import { getDefaultSection, resolveSection } from './config-registry.js';
|
|
26
27
|
|
|
27
|
-
// Orchestration
|
|
28
|
-
const ORCHESTRATOR_MODEL =
|
|
29
|
-
|
|
28
|
+
// Orchestration config from lib/defaults.json (sync fallback)
|
|
29
|
+
const { model: ORCHESTRATOR_MODEL, timeout: ORCHESTRATOR_TIMEOUT } =
|
|
30
|
+
getDefaultSection('orchestrator');
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Counts added/removed lines in a unified diff string
|
|
@@ -184,6 +185,10 @@ const _buildCommonContext = (filesData, batchGroups) => {
|
|
|
184
185
|
* @returns {Promise<{batches: Array<{files: FileData[], rationale: string, model: string}>, commonContext: string}>}
|
|
185
186
|
*/
|
|
186
187
|
export const orchestrateBatches = async (filesData, { headless = false } = {}) => {
|
|
188
|
+
// Resolve orchestrator config: remote settings.json > local defaults.json
|
|
189
|
+
const orchConfig = await resolveSection('orchestrator');
|
|
190
|
+
const orchModel = orchConfig?.model || ORCHESTRATOR_MODEL;
|
|
191
|
+
const orchTimeout = orchConfig?.timeout || ORCHESTRATOR_TIMEOUT;
|
|
187
192
|
logger.debug('diff-analysis-orchestrator - orchestrateBatches', 'Building file overview', {
|
|
188
193
|
fileCount: filesData.length
|
|
189
194
|
});
|
|
@@ -214,20 +219,20 @@ export const orchestrateBatches = async (filesData, { headless = false } = {}) =
|
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
logger.debug('diff-analysis-orchestrator - orchestrateBatches', 'Calling Opus orchestrator', {
|
|
217
|
-
model:
|
|
218
|
-
timeout:
|
|
222
|
+
model: orchModel,
|
|
223
|
+
timeout: orchTimeout
|
|
219
224
|
});
|
|
220
225
|
|
|
221
226
|
let rawResponse;
|
|
222
227
|
try {
|
|
223
228
|
rawResponse = await executeClaudeWithRetry(prompt, {
|
|
224
|
-
model:
|
|
225
|
-
timeout:
|
|
229
|
+
model: orchModel,
|
|
230
|
+
timeout: orchTimeout,
|
|
226
231
|
headless,
|
|
227
232
|
telemetryContext: {
|
|
228
233
|
hook: 'orchestrator',
|
|
229
234
|
fileCount: filesData.length,
|
|
230
|
-
model:
|
|
235
|
+
model: orchModel
|
|
231
236
|
}
|
|
232
237
|
});
|
|
233
238
|
} catch (err) {
|
package/lib/utils/judge.js
CHANGED
|
@@ -26,9 +26,9 @@ import { formatBlockingIssues, getAffectedFiles, formatFileContents } from './re
|
|
|
26
26
|
import { getRepoRoot, getRepoName, getCurrentBranch } from './git-operations.js';
|
|
27
27
|
import logger from './logger.js';
|
|
28
28
|
import { recordMetric } from './metrics.js';
|
|
29
|
+
import { getDefaultSection, resolveSection } from './config-registry.js';
|
|
29
30
|
|
|
30
|
-
const JUDGE_DEFAULT_MODEL = '
|
|
31
|
-
const JUDGE_TIMEOUT = 360000;
|
|
31
|
+
const { model: JUDGE_DEFAULT_MODEL, timeout: JUDGE_TIMEOUT } = getDefaultSection('judge');
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Applies a single search/replace fix to a file and stages it
|
|
@@ -103,7 +103,10 @@ const applyFix = async (fix, repoRoot) => {
|
|
|
103
103
|
* @returns {Promise<{fixedCount: number, falsePositiveCount: number, remainingIssues: Array, verdicts: Array}>}
|
|
104
104
|
*/
|
|
105
105
|
const judgeAndFix = async (analysisResult, filesData, config, { headless = false } = {}) => {
|
|
106
|
-
|
|
106
|
+
// Resolve judge config: remote settings.json > local defaults.json
|
|
107
|
+
const judgeConfig = await resolveSection('judge');
|
|
108
|
+
const model = config.judge?.model ?? judgeConfig?.model ?? JUDGE_DEFAULT_MODEL;
|
|
109
|
+
const judgeTimeout = judgeConfig?.timeout ?? JUDGE_TIMEOUT;
|
|
107
110
|
const repoRoot = getRepoRoot();
|
|
108
111
|
|
|
109
112
|
// Get all issues: prefer details (all severities), fallback to blockingIssues
|
|
@@ -135,7 +138,7 @@ const judgeAndFix = async (analysisResult, filesData, config, { headless = false
|
|
|
135
138
|
// Call LLM
|
|
136
139
|
const response = await executeClaudeWithRetry(prompt, {
|
|
137
140
|
model,
|
|
138
|
-
timeout:
|
|
141
|
+
timeout: judgeTimeout,
|
|
139
142
|
headless
|
|
140
143
|
});
|
|
141
144
|
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
import https from 'https';
|
|
20
20
|
import logger from './logger.js';
|
|
21
21
|
import { loadToken } from './token-store.js';
|
|
22
|
+
import { getDefaultSection } from './config-registry.js';
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
24
|
+
const linearDefaults = getDefaultSection('linear') || {};
|
|
25
|
+
const LINEAR_HOSTNAME = linearDefaults.hostname || 'api.linear.app';
|
|
26
|
+
const LINEAR_PATH = linearDefaults.path || '/graphql';
|
|
27
|
+
const LINEAR_TIMEOUT = linearDefaults.timeout || 10000;
|
|
28
|
+
const MAX_RETRIES = linearDefaults.maxRetries || 2;
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Custom error for Linear connector failures
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import { getRepoRoot } from './git-operations.js';
|
|
28
28
|
import { which } from './which-command.js';
|
|
29
29
|
import { fetchRemoteConfig } from './remote-config.js';
|
|
30
|
+
import { resolveSection } from './config-registry.js';
|
|
30
31
|
import logger from './logger.js';
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -422,6 +423,9 @@ export async function runLinters(files, config, presetName = 'default') {
|
|
|
422
423
|
const timeout = lintConfig.timeout || 30000;
|
|
423
424
|
|
|
424
425
|
const tools = await getLinterToolsForPreset(presetName);
|
|
426
|
+
|
|
427
|
+
// Merge remote tools.json overrides (timeout, perFile, enabled) into tool definitions
|
|
428
|
+
const remoteToolsConfig = await resolveSection('tools');
|
|
425
429
|
const results = [];
|
|
426
430
|
|
|
427
431
|
logger.debug('linter-runner - runLinters', 'Starting linters', {
|
|
@@ -431,7 +435,18 @@ export async function runLinters(files, config, presetName = 'default') {
|
|
|
431
435
|
autoFix
|
|
432
436
|
});
|
|
433
437
|
|
|
434
|
-
for (
|
|
438
|
+
for (let toolDef of tools) {
|
|
439
|
+
// Apply remote tool config overrides (remote > local)
|
|
440
|
+
const remoteToolOverride = remoteToolsConfig?.[toolDef.name];
|
|
441
|
+
if (remoteToolOverride) {
|
|
442
|
+
toolDef = { ...toolDef };
|
|
443
|
+
if (remoteToolOverride.timeout !== undefined) toolDef.timeout = remoteToolOverride.timeout;
|
|
444
|
+
if (remoteToolOverride.perFile !== undefined) toolDef.perFile = remoteToolOverride.perFile;
|
|
445
|
+
if (remoteToolOverride.enabled === false) {
|
|
446
|
+
logger.info(`Skipping ${toolDef.name} (disabled by remote config)`);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
435
450
|
// Filter files by tool's extensions
|
|
436
451
|
const matchingFiles = filterFilesByTool(files, toolDef);
|
|
437
452
|
|
|
@@ -32,6 +32,7 @@ import { executeClaudeWithRetry, extractJSON } from './claude-client.js';
|
|
|
32
32
|
import { loadPrompt } from './prompt-builder.js';
|
|
33
33
|
import { getConfig } from '../config.js';
|
|
34
34
|
import logger from './logger.js';
|
|
35
|
+
import { getDefaultSection, resolveSection } from './config-registry.js';
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* @typedef {Object} BranchContext
|
|
@@ -58,13 +59,11 @@ import logger from './logger.js';
|
|
|
58
59
|
*/
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* Internal defaults
|
|
62
|
+
* Internal defaults loaded from lib/defaults.json
|
|
62
63
|
* Why: Convention over configuration - sensible defaults for 95% of cases
|
|
64
|
+
* Fallbacks: timeout=180000 (3 min), maxDiffSize=51200 (50KB)
|
|
63
65
|
*/
|
|
64
|
-
const DEFAULTS = {
|
|
65
|
-
timeout: 180000, // 3 minutes
|
|
66
|
-
maxDiffSize: 50000 // 50KB
|
|
67
|
-
};
|
|
66
|
+
const DEFAULTS = getDefaultSection('prMetadata') || { timeout: 180000, maxDiffSize: 50000 };
|
|
68
67
|
|
|
69
68
|
/**
|
|
70
69
|
* Builds diff payload with tiered reduction strategy
|
|
@@ -310,12 +309,14 @@ export const getBranchContext = async (targetBranch) => {
|
|
|
310
309
|
|
|
311
310
|
logger.debug('pr-metadata-engine - getBranchContext', 'Commits retrieved');
|
|
312
311
|
|
|
313
|
-
// Build diff payload with tiered reduction
|
|
312
|
+
// Build diff payload with tiered reduction (remote settings.json > local defaults)
|
|
313
|
+
const resolvedPrMeta = await resolveSection('prMetadata');
|
|
314
|
+
const maxDiffSize = resolvedPrMeta?.maxDiffSize || DEFAULTS.maxDiffSize;
|
|
314
315
|
const {
|
|
315
316
|
payload: diff,
|
|
316
317
|
isTruncated,
|
|
317
318
|
truncationDetails
|
|
318
|
-
} = buildDiffPayload(baseBranch, 'HEAD', files,
|
|
319
|
+
} = buildDiffPayload(baseBranch, 'HEAD', files, maxDiffSize);
|
|
319
320
|
|
|
320
321
|
logger.debug('pr-metadata-engine - getBranchContext', 'Diff payload built', {
|
|
321
322
|
diffSize: diff.length,
|
|
@@ -371,7 +372,9 @@ export const getBranchContext = async (targetBranch) => {
|
|
|
371
372
|
export const generatePRMetadata = async (context, options = {}) => {
|
|
372
373
|
const config = await getConfig();
|
|
373
374
|
const configTimeout = config.analysis?.timeout;
|
|
374
|
-
const
|
|
375
|
+
const resolvedPrMetaGen = await resolveSection('prMetadata');
|
|
376
|
+
const defaultTimeout = resolvedPrMetaGen?.timeout || DEFAULTS.timeout;
|
|
377
|
+
const { timeout = configTimeout || defaultTimeout, hook = 'pr-metadata', headless = false, costTracker = null } = options;
|
|
375
378
|
|
|
376
379
|
logger.debug('pr-metadata-engine - generatePRMetadata', 'Generating PR metadata', {
|
|
377
380
|
filesCount: context.filesCount,
|
|
@@ -377,11 +377,13 @@ export function writeVersionToFile(filePath, type, newVersion) {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
/**
|
|
380
|
-
* Updates version in
|
|
381
|
-
* Why: Applies version update to
|
|
380
|
+
* Updates version in the given files (resolved mutations from the selector)
|
|
381
|
+
* Why: Applies version update to the concrete list of files the caller provides.
|
|
382
|
+
* Every item in the array is a mutation to apply — the caller is responsible
|
|
383
|
+
* for filtering before calling this function.
|
|
382
384
|
*
|
|
383
|
-
* @param {Array} files - Array of VersionFileDescriptor objects
|
|
384
|
-
* @param {string} newVersion - New version string
|
|
385
|
+
* @param {Array} files - Array of VersionFileDescriptor objects to update
|
|
386
|
+
* @param {string} newVersion - New version string (used when file has no targetVersion)
|
|
385
387
|
*/
|
|
386
388
|
export function updateVersionFiles(files, newVersion) {
|
|
387
389
|
logger.debug('version-manager - updateVersionFiles', 'Updating version files', {
|
|
@@ -402,10 +404,6 @@ export function updateVersionFiles(files, newVersion) {
|
|
|
402
404
|
const errors = [];
|
|
403
405
|
|
|
404
406
|
for (const file of files) {
|
|
405
|
-
if (!file.selected) {
|
|
406
|
-
continue; // Skip unselected files
|
|
407
|
-
}
|
|
408
|
-
|
|
409
407
|
try {
|
|
410
408
|
// Verify file still exists
|
|
411
409
|
if (!fs.existsSync(file.path)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-git-hooks",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.51.2",
|
|
4
4
|
"description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,8 +21,14 @@
|
|
|
21
21
|
"format": "prettier --write \"lib/**/*.js\" \"bin/**\" \"test/**/*.js\"",
|
|
22
22
|
"precommit": "npm run lint && npm run test:smoke",
|
|
23
23
|
"prepublishOnly": "npm run test:all",
|
|
24
|
-
"library:
|
|
25
|
-
"library:
|
|
24
|
+
"library:check": "node .library/bin/library check",
|
|
25
|
+
"library:regenerate": "node .library/bin/library regenerate",
|
|
26
|
+
"library:extract": "node .library/bin/library extract",
|
|
27
|
+
"library:tokens": "node .library/bin/library tokens",
|
|
28
|
+
"library:graph": "node .library/bin/library graph",
|
|
29
|
+
"library:inject": "node .library/bin/library inject",
|
|
30
|
+
"library:validate": "node .library/bin/library validate",
|
|
31
|
+
"library:report": "node .library/bin/library report"
|
|
26
32
|
},
|
|
27
33
|
"keywords": [
|
|
28
34
|
"git",
|
|
@@ -70,6 +76,8 @@
|
|
|
70
76
|
"jest": "^29.7.0",
|
|
71
77
|
"js-tiktoken": "^1.0.18",
|
|
72
78
|
"madge": "^8.0.0",
|
|
73
|
-
"prettier": "^3.2.0"
|
|
79
|
+
"prettier": "^3.2.0",
|
|
80
|
+
"tree-sitter-wasms": "^0.1.13",
|
|
81
|
+
"web-tree-sitter": "^0.24.7"
|
|
74
82
|
}
|
|
75
83
|
}
|