claude-git-hooks 2.21.0 → 2.30.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 +148 -6
- package/CLAUDE.md +469 -69
- package/README.md +5 -0
- package/bin/claude-hooks +101 -0
- package/lib/cli-metadata.js +68 -1
- package/lib/commands/analyze-pr.js +19 -24
- package/lib/commands/back-merge.js +740 -0
- package/lib/commands/check-coupling.js +209 -0
- package/lib/commands/close-release.js +485 -0
- package/lib/commands/create-pr.js +62 -3
- package/lib/commands/create-release.js +600 -0
- package/lib/commands/diff-batch-info.js +7 -13
- package/lib/commands/help.js +5 -7
- package/lib/commands/install.js +1 -5
- package/lib/commands/revert-feature.js +436 -0
- package/lib/commands/shadow.js +654 -0
- package/lib/config.js +1 -2
- package/lib/hooks/pre-commit.js +8 -6
- package/lib/utils/authorization.js +429 -0
- package/lib/utils/claude-client.js +14 -7
- package/lib/utils/coupling-detector.js +133 -0
- package/lib/utils/diff-analysis-orchestrator.js +7 -14
- package/lib/utils/git-operations.js +480 -1
- package/lib/utils/github-api.js +182 -0
- package/lib/utils/judge.js +66 -7
- package/lib/utils/linear-connector.js +1 -4
- package/lib/utils/package-info.js +0 -1
- package/lib/utils/token-store.js +5 -3
- package/package.json +69 -69
package/lib/utils/judge.js
CHANGED
|
@@ -22,11 +22,7 @@ import path from 'path';
|
|
|
22
22
|
import { execSync } from 'child_process';
|
|
23
23
|
import { executeClaudeWithRetry, extractJSON } from './claude-client.js';
|
|
24
24
|
import { loadPrompt } from './prompt-builder.js';
|
|
25
|
-
import {
|
|
26
|
-
formatBlockingIssues,
|
|
27
|
-
getAffectedFiles,
|
|
28
|
-
formatFileContents
|
|
29
|
-
} from './resolution-prompt.js';
|
|
25
|
+
import { formatBlockingIssues, getAffectedFiles, formatFileContents } from './resolution-prompt.js';
|
|
30
26
|
import { getRepoRoot, getRepoName, getCurrentBranch } from './git-operations.js';
|
|
31
27
|
import logger from './logger.js';
|
|
32
28
|
import { recordEvent } from './telemetry.js';
|
|
@@ -55,6 +51,29 @@ const applyFix = async (fix, repoRoot) => {
|
|
|
55
51
|
const firstIndex = content.indexOf(search);
|
|
56
52
|
|
|
57
53
|
if (firstIndex === -1) {
|
|
54
|
+
// Debug: diagnose why the search string didn't match
|
|
55
|
+
if (logger.isDebugMode()) {
|
|
56
|
+
const fileCRLF = content.includes('\r\n');
|
|
57
|
+
const searchCRLF = search.includes('\r\n');
|
|
58
|
+
const searchLines = search.split(/\r?\n/);
|
|
59
|
+
const firstLine = searchLines[0].trim();
|
|
60
|
+
// Try to locate the first line loosely in the file
|
|
61
|
+
const looseIndex = content.indexOf(firstLine);
|
|
62
|
+
logger.debug('judge - applyFix', 'Search mismatch diagnosis', {
|
|
63
|
+
file,
|
|
64
|
+
searchLength: search.length,
|
|
65
|
+
searchLines: searchLines.length,
|
|
66
|
+
fileCRLF,
|
|
67
|
+
searchCRLF,
|
|
68
|
+
crlfMismatch: fileCRLF !== searchCRLF,
|
|
69
|
+
firstLineFound: looseIndex !== -1,
|
|
70
|
+
firstLineFoundAt:
|
|
71
|
+
looseIndex !== -1
|
|
72
|
+
? `char ${looseIndex} (approx line ${content.substring(0, looseIndex).split('\n').length})`
|
|
73
|
+
: null,
|
|
74
|
+
searchFirst200Hex: Buffer.from(search.substring(0, 200)).toString('hex')
|
|
75
|
+
});
|
|
76
|
+
}
|
|
58
77
|
return { applied: false, reason: 'search string not found in file' };
|
|
59
78
|
}
|
|
60
79
|
|
|
@@ -122,6 +141,42 @@ const judgeAndFix = async (analysisResult, filesData, config) => {
|
|
|
122
141
|
const parsed = extractJSON(response);
|
|
123
142
|
const fixes = parsed?.fixes || [];
|
|
124
143
|
|
|
144
|
+
// Debug: dump raw LLM response and parsed fixes for diagnosis
|
|
145
|
+
if (logger.isDebugMode()) {
|
|
146
|
+
try {
|
|
147
|
+
const debugDir = path.join(repoRoot, '.claude', 'out');
|
|
148
|
+
await fs.mkdir(debugDir, { recursive: true });
|
|
149
|
+
await fs.writeFile(
|
|
150
|
+
path.join(debugDir, 'judge-debug.json'),
|
|
151
|
+
JSON.stringify(
|
|
152
|
+
{
|
|
153
|
+
timestamp: new Date().toISOString(),
|
|
154
|
+
model,
|
|
155
|
+
issueCount: allIssues.length,
|
|
156
|
+
fixCount: fixes.length,
|
|
157
|
+
rawResponse: response,
|
|
158
|
+
parsedFixes: fixes.map((f) => ({
|
|
159
|
+
file: f.file,
|
|
160
|
+
assessment: f.assessment,
|
|
161
|
+
searchLength: f.search?.length ?? null,
|
|
162
|
+
replaceLength: f.replace?.length ?? null,
|
|
163
|
+
searchHex: f.search
|
|
164
|
+
? Buffer.from(f.search.substring(0, 300)).toString('hex')
|
|
165
|
+
: null,
|
|
166
|
+
search: f.search,
|
|
167
|
+
replace: f.replace
|
|
168
|
+
}))
|
|
169
|
+
},
|
|
170
|
+
null,
|
|
171
|
+
2
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
logger.debug('judge', 'Debug dump written to .claude/out/judge-debug.json');
|
|
175
|
+
} catch (debugErr) {
|
|
176
|
+
logger.debug('judge', 'Failed to write debug dump', debugErr);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
125
180
|
let fixedCount = 0;
|
|
126
181
|
let falsePositiveCount = 0;
|
|
127
182
|
const resolvedIndices = new Set();
|
|
@@ -173,11 +228,15 @@ const judgeAndFix = async (analysisResult, filesData, config) => {
|
|
|
173
228
|
);
|
|
174
229
|
|
|
175
230
|
if (fixedCount > 0) {
|
|
176
|
-
logger.info(
|
|
231
|
+
logger.info(
|
|
232
|
+
` ${fixedCount} fix${fixedCount !== 1 ? 'es' : ''} staged for commit — review with: git diff --cached`
|
|
233
|
+
);
|
|
177
234
|
}
|
|
178
235
|
|
|
179
236
|
if (unresolvedCount > 0) {
|
|
180
|
-
logger.warning(
|
|
237
|
+
logger.warning(
|
|
238
|
+
` ${unresolvedCount} unresolved issue${unresolvedCount !== 1 ? 's' : ''} — commit will be blocked`
|
|
239
|
+
);
|
|
181
240
|
}
|
|
182
241
|
|
|
183
242
|
// Record telemetry
|
|
@@ -268,10 +268,7 @@ export const fetchTicket = async (identifier) => {
|
|
|
268
268
|
|
|
269
269
|
if (attempt < MAX_RETRIES) {
|
|
270
270
|
// Steps c/d: Retry with reconnection
|
|
271
|
-
logger.debug(
|
|
272
|
-
'linear-connector - fetchTicket',
|
|
273
|
-
'Retrying after connection failure'
|
|
274
|
-
);
|
|
271
|
+
logger.debug('linear-connector - fetchTicket', 'Retrying after connection failure');
|
|
275
272
|
// Small delay before retry
|
|
276
273
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
277
274
|
}
|
package/lib/utils/token-store.js
CHANGED
|
@@ -123,7 +123,10 @@ export const saveToken = (settingsKey, token) => {
|
|
|
123
123
|
try {
|
|
124
124
|
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
125
125
|
} catch {
|
|
126
|
-
logger.debug(
|
|
126
|
+
logger.debug(
|
|
127
|
+
'token-store - saveToken',
|
|
128
|
+
'Invalid existing settings, starting fresh'
|
|
129
|
+
);
|
|
127
130
|
settings = {};
|
|
128
131
|
}
|
|
129
132
|
}
|
|
@@ -155,5 +158,4 @@ export const saveToken = (settingsKey, token) => {
|
|
|
155
158
|
* @param {string[]} envVars - Environment variable names to check
|
|
156
159
|
* @returns {boolean}
|
|
157
160
|
*/
|
|
158
|
-
export const hasToken = (settingsKey, envVars = []) =>
|
|
159
|
-
loadToken(settingsKey, envVars) !== null;
|
|
161
|
+
export const hasToken = (settingsKey, envVars = []) => loadToken(settingsKey, envVars) !== null;
|
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "claude-git-hooks",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"claude-hooks": "./bin/claude-hooks"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"test": "npm run test:all",
|
|
11
|
-
"test:all": "npm run lint && npm run test:smoke && npm run test:unit && npm run test:integration",
|
|
12
|
-
"test:smoke": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/smoke --maxWorkers=1",
|
|
13
|
-
"test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/unit --forceExit",
|
|
14
|
-
"test:integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/integration --maxWorkers=1 --testTimeout=30000 --forceExit",
|
|
15
|
-
"test:integration:ci": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/integration/ci-safe.test.js --maxWorkers=1 --testTimeout=30000 --forceExit",
|
|
16
|
-
"test:changed": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/unit --changedSince=main --forceExit",
|
|
17
|
-
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
18
|
-
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
19
|
-
"lint": "eslint lib/ bin/claude-hooks",
|
|
20
|
-
"lint:fix": "eslint lib/ bin/claude-hooks --fix",
|
|
21
|
-
"format": "prettier --write \"lib/**/*.js\" \"bin/**\" \"test/**/*.js\"",
|
|
22
|
-
"precommit": "npm run lint && npm run test:smoke",
|
|
23
|
-
"prepublishOnly": "npm run test:all"
|
|
24
|
-
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
"git",
|
|
27
|
-
"hooks",
|
|
28
|
-
"claude",
|
|
29
|
-
"ai",
|
|
30
|
-
"code-review",
|
|
31
|
-
"commit-messages",
|
|
32
|
-
"pre-commit",
|
|
33
|
-
"automation"
|
|
34
|
-
],
|
|
35
|
-
"author": "Pablo Rovito",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"repository": {
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "https://github.com/mscope-S-L/git-hooks.git"
|
|
40
|
-
},
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=16.9.0"
|
|
43
|
-
},
|
|
44
|
-
"engineStrict": false,
|
|
45
|
-
"os": [
|
|
46
|
-
"darwin",
|
|
47
|
-
"linux",
|
|
48
|
-
"win32"
|
|
49
|
-
],
|
|
50
|
-
"preferGlobal": true,
|
|
51
|
-
"files": [
|
|
52
|
-
"bin/",
|
|
53
|
-
"lib/",
|
|
54
|
-
"templates/",
|
|
55
|
-
"README.md",
|
|
56
|
-
"CHANGELOG.md",
|
|
57
|
-
"CLAUDE.md",
|
|
58
|
-
"LICENSE"
|
|
59
|
-
],
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"@octokit/rest": "^21.0.0"
|
|
62
|
-
},
|
|
63
|
-
"devDependencies": {
|
|
64
|
-
"@types/jest": "^29.5.0",
|
|
65
|
-
"eslint": "^8.57.0",
|
|
66
|
-
"jest": "^29.7.0",
|
|
67
|
-
"prettier": "^3.2.0"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-git-hooks",
|
|
3
|
+
"version": "2.30.2",
|
|
4
|
+
"description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-hooks": "./bin/claude-hooks"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "npm run test:all",
|
|
11
|
+
"test:all": "npm run lint && npm run test:smoke && npm run test:unit && npm run test:integration",
|
|
12
|
+
"test:smoke": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/smoke --maxWorkers=1",
|
|
13
|
+
"test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/unit --forceExit",
|
|
14
|
+
"test:integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/integration --maxWorkers=1 --testTimeout=30000 --forceExit",
|
|
15
|
+
"test:integration:ci": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/integration/ci-safe.test.js --maxWorkers=1 --testTimeout=30000 --forceExit",
|
|
16
|
+
"test:changed": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/unit --changedSince=main --forceExit",
|
|
17
|
+
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
18
|
+
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
19
|
+
"lint": "eslint lib/ bin/claude-hooks",
|
|
20
|
+
"lint:fix": "eslint lib/ bin/claude-hooks --fix",
|
|
21
|
+
"format": "prettier --write \"lib/**/*.js\" \"bin/**\" \"test/**/*.js\"",
|
|
22
|
+
"precommit": "npm run lint && npm run test:smoke",
|
|
23
|
+
"prepublishOnly": "npm run test:all"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"git",
|
|
27
|
+
"hooks",
|
|
28
|
+
"claude",
|
|
29
|
+
"ai",
|
|
30
|
+
"code-review",
|
|
31
|
+
"commit-messages",
|
|
32
|
+
"pre-commit",
|
|
33
|
+
"automation"
|
|
34
|
+
],
|
|
35
|
+
"author": "Pablo Rovito",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/mscope-S-L/git-hooks.git"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=16.9.0"
|
|
43
|
+
},
|
|
44
|
+
"engineStrict": false,
|
|
45
|
+
"os": [
|
|
46
|
+
"darwin",
|
|
47
|
+
"linux",
|
|
48
|
+
"win32"
|
|
49
|
+
],
|
|
50
|
+
"preferGlobal": true,
|
|
51
|
+
"files": [
|
|
52
|
+
"bin/",
|
|
53
|
+
"lib/",
|
|
54
|
+
"templates/",
|
|
55
|
+
"README.md",
|
|
56
|
+
"CHANGELOG.md",
|
|
57
|
+
"CLAUDE.md",
|
|
58
|
+
"LICENSE"
|
|
59
|
+
],
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@octokit/rest": "^21.0.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/jest": "^29.5.0",
|
|
65
|
+
"eslint": "^8.57.0",
|
|
66
|
+
"jest": "^29.7.0",
|
|
67
|
+
"prettier": "^3.2.0"
|
|
68
|
+
}
|
|
69
|
+
}
|