claude-git-hooks 2.66.1 → 2.68.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 +117 -8
- package/README.md +34 -0
- package/bin/claude-hooks +19 -0
- package/lib/commands/analyze-diff.js +26 -23
- package/lib/commands/analyze.js +3 -1
- package/lib/commands/back-merge.js +39 -33
- package/lib/commands/bump-version.js +20 -15
- package/lib/commands/close-release.js +25 -19
- package/lib/commands/create-pr.js +30 -1
- package/lib/commands/create-release.js +19 -13
- package/lib/commands/install.js +9 -19
- package/lib/commands/update.js +14 -28
- package/lib/defaults.json +9 -0
- package/lib/hooks/pre-commit.js +112 -32
- package/lib/utils/analysis-engine.js +7 -3
- package/lib/utils/auto-update.js +198 -0
- package/lib/utils/config-registry.js +1 -0
- package/lib/utils/library-resolver.js +50 -0
- package/lib/utils/prompt-builder.js +15 -0
- package/lib/utils/skill-registry/catalogue.js +74 -0
- package/lib/utils/skill-registry/feedback-writer.js +196 -0
- package/lib/utils/skill-registry/index.js +254 -0
- package/lib/utils/skill-registry/parser.js +311 -0
- package/lib/utils/skill-registry/resume.js +81 -0
- package/lib/utils/skill-registry/runner.js +265 -0
- package/lib/utils/version-manager.js +9 -3
- package/package.json +84 -85
- package/templates/CLAUDE_ANALYSIS_PROMPT.md +2 -1
- package/templates/config.advanced.example.json +42 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: skill-registry/runner.js
|
|
3
|
+
* Purpose: Run the parsed skill-registry's Verify patterns against a set of
|
|
4
|
+
* staged files. Returns structured findings the pre-commit hook
|
|
5
|
+
* can surface alongside (or instead of) AI analysis.
|
|
6
|
+
*
|
|
7
|
+
* Design:
|
|
8
|
+
* - Inputs: array of rules from parser.js + array of staged absolute file paths.
|
|
9
|
+
* - For each staged file:
|
|
10
|
+
* 1. Read its content.
|
|
11
|
+
* 2. Pick the rules whose scope matches the file's language (backend
|
|
12
|
+
* rules for .java, frontend for .jsx/.tsx/.js/.css, SQL rules for
|
|
13
|
+
* .sql — eventually; for now we map by scope tag).
|
|
14
|
+
* 3. For each rule with an extractable regex from its Verify command,
|
|
15
|
+
* search the file and emit findings with line numbers.
|
|
16
|
+
* - Returns: { totalFindings, byFile: { path → [findings] }, bySeverity: {…} }
|
|
17
|
+
*
|
|
18
|
+
* NOT done here:
|
|
19
|
+
* - File-level greps (e.g. "files that DON'T contain X" / inverted verifies):
|
|
20
|
+
* those need a different model (whole-repo audit, not pre-commit).
|
|
21
|
+
* - Cross-file dependencies (verifies that pipe through xargs).
|
|
22
|
+
* - The deny-list / allow-list around which rules block vs warn — caller
|
|
23
|
+
* decides via the severity field on each rule.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readFileSync } from 'fs';
|
|
27
|
+
import { extname } from 'path';
|
|
28
|
+
import { SCOPE_BY_EXT } from './parser.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run all applicable rules against a set of staged files.
|
|
32
|
+
* @param {Array<object>} rules - From parser.loadRegistry().rules
|
|
33
|
+
* @param {Array<string>} stagedAbsPaths - Absolute file paths
|
|
34
|
+
* @param {object} [options]
|
|
35
|
+
* @param {string} [options.repoRoot] - For displaying relative paths in output
|
|
36
|
+
* @returns {{ totalFindings: number, findings: Array, byFile: object, bySeverity: object }}
|
|
37
|
+
*/
|
|
38
|
+
export function runRules(rules, stagedAbsPaths, options = {}) {
|
|
39
|
+
const repoRoot = options.repoRoot || '';
|
|
40
|
+
const findings = [];
|
|
41
|
+
|
|
42
|
+
// Precompile each rule's matchers once.
|
|
43
|
+
const compiled = compileRules(rules);
|
|
44
|
+
|
|
45
|
+
for (const absPath of stagedAbsPaths) {
|
|
46
|
+
const ext = extname(absPath).toLowerCase();
|
|
47
|
+
const scope = SCOPE_BY_EXT[ext];
|
|
48
|
+
if (!scope) continue;
|
|
49
|
+
|
|
50
|
+
// Skip binary / very large files defensively.
|
|
51
|
+
let content;
|
|
52
|
+
try {
|
|
53
|
+
content = readFileSync(absPath, 'utf8');
|
|
54
|
+
if (content.length > 1_000_000) continue; // 1MB cap
|
|
55
|
+
} catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const applicable = compiled.filter((r) => r.scope === scope);
|
|
60
|
+
if (applicable.length === 0) continue;
|
|
61
|
+
|
|
62
|
+
const lines = content.split('\n');
|
|
63
|
+
for (const rule of applicable) {
|
|
64
|
+
for (const matcher of rule.matchers) {
|
|
65
|
+
// Try matching each line; collect line numbers.
|
|
66
|
+
for (let i = 0; i < lines.length; i++) {
|
|
67
|
+
const line = lines[i];
|
|
68
|
+
if (matcher.exclude && matcher.exclude.test(line)) continue;
|
|
69
|
+
if (matcher.regex.test(line)) {
|
|
70
|
+
// Reset lastIndex for global regexes; we only need the first hit per line.
|
|
71
|
+
matcher.regex.lastIndex = 0;
|
|
72
|
+
findings.push({
|
|
73
|
+
ruleId: rule.id,
|
|
74
|
+
severity: rule.severity,
|
|
75
|
+
title: rule.title,
|
|
76
|
+
scope: rule.scope,
|
|
77
|
+
file: repoRoot ? relativizePath(absPath, repoRoot) : absPath,
|
|
78
|
+
absPath,
|
|
79
|
+
line: i + 1,
|
|
80
|
+
lineText: line.trim().slice(0, 200),
|
|
81
|
+
// Lightweight pointer back to the registry entry for the UI.
|
|
82
|
+
registryRef: `${rule.scope}/improvement-registry.md → ${rule.id}`,
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
matcher.regex.lastIndex = 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return summarize(findings);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convert each rule's verify commands into JS RegExp matchers we can run
|
|
97
|
+
* line-by-line. Rules where the verify can't be extracted are skipped (they
|
|
98
|
+
* remain in the registry for other features like `lookup`).
|
|
99
|
+
*/
|
|
100
|
+
function compileRules(rules) {
|
|
101
|
+
const out = [];
|
|
102
|
+
for (const rule of rules) {
|
|
103
|
+
const matchers = [];
|
|
104
|
+
for (const v of rule.verifies) {
|
|
105
|
+
const m = extractMatcher(v.command);
|
|
106
|
+
if (m) matchers.push(m);
|
|
107
|
+
}
|
|
108
|
+
if (matchers.length > 0) {
|
|
109
|
+
out.push({
|
|
110
|
+
id: rule.id,
|
|
111
|
+
severity: rule.severity,
|
|
112
|
+
title: rule.title,
|
|
113
|
+
scope: rule.scope,
|
|
114
|
+
matchers,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Extract a regex + optional exclude regex from a Verify grep command.
|
|
123
|
+
*
|
|
124
|
+
* Supported shapes (covers ~50 of the 95 current registry entries):
|
|
125
|
+
* grep -rn "PATTERN" path
|
|
126
|
+
* grep -rEn "REGEX" path
|
|
127
|
+
* grep -rn "PATTERN" path --include=*.ext
|
|
128
|
+
* grep -rn "PATTERN" path | grep -v "EXCLUDE"
|
|
129
|
+
* grep -rEn "REGEX1" path | grep -E "REGEX2" (we treat the pipe as AND)
|
|
130
|
+
* rg "PATTERN" path
|
|
131
|
+
*
|
|
132
|
+
* Returns { regex, exclude } or null if the command doesn't fit the supported shapes.
|
|
133
|
+
*/
|
|
134
|
+
function extractMatcher(command) {
|
|
135
|
+
if (!command) return null;
|
|
136
|
+
|
|
137
|
+
// Strip quoted strings before checking for shell control characters —
|
|
138
|
+
// a literal `;` inside the grep regex (e.g. `^import .*\.\*;`) must
|
|
139
|
+
// not trip our reject heuristic.
|
|
140
|
+
const outsideQuotes = command
|
|
141
|
+
.replace(/"[^"]*"/g, '"_"')
|
|
142
|
+
.replace(/'[^']*'/g, "'_'");
|
|
143
|
+
|
|
144
|
+
if (/xargs/.test(outsideQuotes)) return null;
|
|
145
|
+
// Shell control outside quotes (||, &&, ;, $(), backticks for command sub) → file-level / audit-style.
|
|
146
|
+
if (/\|\||&&|;|\$\(|`/.test(outsideQuotes)) return null;
|
|
147
|
+
if (/\s\|\s/.test(outsideQuotes) && !/\|\s+grep/.test(outsideQuotes)) return null;
|
|
148
|
+
|
|
149
|
+
// Split on the (optional) `| grep` pipe. We support: first grep matches,
|
|
150
|
+
// pipe-through grep further filters (positive or `-v` excludes).
|
|
151
|
+
const segments = command.split(/\s\|\s/).map((s) => s.trim());
|
|
152
|
+
const primary = segments[0];
|
|
153
|
+
|
|
154
|
+
const tokens = tokenizeGrepInvocation(primary);
|
|
155
|
+
if (!tokens) return null;
|
|
156
|
+
|
|
157
|
+
// Reject FILE-LEVEL grep modes (list filenames only) — those are
|
|
158
|
+
// whole-repo audits, not line-level pre-commit checks.
|
|
159
|
+
// -l → list matching filenames only
|
|
160
|
+
// -L → list NON-matching filenames only (inverted file presence)
|
|
161
|
+
// We accept `-ln`/`-rln`/`-Ln` because `n` is line numbers (still file-level).
|
|
162
|
+
if (tokens.flags.includes('l') || tokens.flags.includes('L')) return null;
|
|
163
|
+
|
|
164
|
+
const isExtended = tokens.flags.includes('E');
|
|
165
|
+
let pattern;
|
|
166
|
+
try {
|
|
167
|
+
pattern = isExtended ? tokens.pattern : escapeForRegex(tokens.pattern);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let regex;
|
|
173
|
+
try {
|
|
174
|
+
regex = new RegExp(pattern);
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Optional exclude from a `| grep -v "X"` follow-up.
|
|
180
|
+
let exclude = null;
|
|
181
|
+
for (const seg of segments.slice(1)) {
|
|
182
|
+
const t = tokenizeGrepInvocation(seg);
|
|
183
|
+
if (!t) continue;
|
|
184
|
+
if (t.flags.includes('v')) {
|
|
185
|
+
try {
|
|
186
|
+
const excPat = t.flags.includes('E') ? t.pattern : escapeForRegex(t.pattern);
|
|
187
|
+
exclude = new RegExp(excPat);
|
|
188
|
+
} catch {
|
|
189
|
+
/* leave exclude null */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Non-inverted secondary grep == narrowing AND. We could AND it with
|
|
193
|
+
// primary but in practice the registry uses this for file-list filtering
|
|
194
|
+
// (e.g. JBE-004 piping into `grep -A 1`), which is whole-file context
|
|
195
|
+
// not single-line. Conservative: skip.
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { regex, exclude };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Tokenize a single `grep ...` invocation into { flags, pattern, paths }.
|
|
203
|
+
*
|
|
204
|
+
* The pattern is the FIRST positional argument after the flags.
|
|
205
|
+
* Patterns may be quoted with " or ' or backticks; flags can be combined or split.
|
|
206
|
+
*
|
|
207
|
+
* Returns null if we can't recognise the shape (in which case the rule's
|
|
208
|
+
* verify just doesn't fire — silently skipped).
|
|
209
|
+
*/
|
|
210
|
+
function tokenizeGrepInvocation(s) {
|
|
211
|
+
const trimmed = s.trim();
|
|
212
|
+
// Must start with grep / rg / git grep
|
|
213
|
+
let rest;
|
|
214
|
+
if (/^grep\s/.test(trimmed)) rest = trimmed.slice(5).trim();
|
|
215
|
+
else if (/^rg\s/.test(trimmed)) rest = trimmed.slice(3).trim();
|
|
216
|
+
else if (/^git\s+grep\s/.test(trimmed)) rest = trimmed.replace(/^git\s+grep\s/, '').trim();
|
|
217
|
+
else return null;
|
|
218
|
+
|
|
219
|
+
const flags = [];
|
|
220
|
+
while (rest.startsWith('-')) {
|
|
221
|
+
// Stop at '--' (separator)
|
|
222
|
+
if (rest.startsWith('-- ')) {
|
|
223
|
+
rest = rest.slice(3).trim();
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
const m = rest.match(/^-(-?)([A-Za-z]+)(=(\S+))?\s*/);
|
|
227
|
+
if (!m) break;
|
|
228
|
+
const isLong = m[1] === '-';
|
|
229
|
+
if (isLong) {
|
|
230
|
+
flags.push(m[2]);
|
|
231
|
+
} else {
|
|
232
|
+
for (const ch of m[2]) flags.push(ch);
|
|
233
|
+
}
|
|
234
|
+
rest = rest.slice(m[0].length);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// First positional: the pattern. Strip surrounding quotes if any.
|
|
238
|
+
const patMatch = rest.match(/^("([^"]*)"|'([^']*)'|(\S+))/);
|
|
239
|
+
if (!patMatch) return null;
|
|
240
|
+
const pattern = patMatch[2] ?? patMatch[3] ?? patMatch[4];
|
|
241
|
+
|
|
242
|
+
return { flags, pattern };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function escapeForRegex(s) {
|
|
246
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function relativizePath(abs, root) {
|
|
250
|
+
const r = root.replace(/[\\/]+$/, '');
|
|
251
|
+
if (abs.startsWith(r)) {
|
|
252
|
+
return abs.slice(r.length + 1).replace(/\\/g, '/');
|
|
253
|
+
}
|
|
254
|
+
return abs.replace(/\\/g, '/');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function summarize(findings) {
|
|
258
|
+
const byFile = {};
|
|
259
|
+
const bySeverity = { critical: 0, high: 0, medium: 0, low: 0, unknown: 0 };
|
|
260
|
+
for (const f of findings) {
|
|
261
|
+
(byFile[f.file] ||= []).push(f);
|
|
262
|
+
bySeverity[f.severity] = (bySeverity[f.severity] || 0) + 1;
|
|
263
|
+
}
|
|
264
|
+
return { totalFindings: findings.length, findings, byFile, bySeverity };
|
|
265
|
+
}
|
|
@@ -247,15 +247,21 @@ export function discoverVersionFiles(options = {}) {
|
|
|
247
247
|
for (const fileType of fileTypes) {
|
|
248
248
|
const registry = VERSION_FILE_TYPES[fileType];
|
|
249
249
|
if (registry && entry.name === registry.filename) {
|
|
250
|
-
const
|
|
251
|
-
const
|
|
250
|
+
const rawVersion = registry.readVersion(fullPath);
|
|
251
|
+
const version = rawVersion !== null && validateVersionFormat(rawVersion) ? rawVersion : null;
|
|
252
|
+
if (rawVersion !== null && version === null) {
|
|
253
|
+
logger.debug('version-manager - discoverVersionFiles', 'Non-semver version skipped', {
|
|
254
|
+
relativePath: path.relative(repoRoot, fullPath),
|
|
255
|
+
rawVersion
|
|
256
|
+
});
|
|
257
|
+
}
|
|
252
258
|
const descriptor = {
|
|
253
259
|
path: fullPath,
|
|
254
260
|
relativePath: path.relative(repoRoot, fullPath),
|
|
255
261
|
type: fileType,
|
|
256
262
|
projectLabel: registry.projectLabel,
|
|
257
263
|
version,
|
|
258
|
-
selected:
|
|
264
|
+
selected: version !== null
|
|
259
265
|
};
|
|
260
266
|
discoveredFiles.push(descriptor);
|
|
261
267
|
logger.debug('version-manager - discoverVersionFiles', 'Found version file', {
|
package/package.json
CHANGED
|
@@ -1,85 +1,84 @@
|
|
|
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
|
-
"test:e2e": "bash test/manual/sdlc-stability-check.sh",
|
|
20
|
-
"
|
|
21
|
-
"lint": "eslint lib/ bin/claude-hooks .library/librarian/",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"library:
|
|
27
|
-
"library:
|
|
28
|
-
"library:
|
|
29
|
-
"library:
|
|
30
|
-
"library:
|
|
31
|
-
"library:
|
|
32
|
-
"library:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"commit
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"@
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"tree-sitter
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-git-hooks",
|
|
3
|
+
"version": "2.68.0",
|
|
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
|
+
"test:e2e": "bash test/manual/sdlc-stability-check.sh",
|
|
20
|
+
"lint": "eslint lib/ bin/claude-hooks .library/librarian/",
|
|
21
|
+
"lint:fix": "eslint lib/ bin/claude-hooks .library/librarian/ --fix",
|
|
22
|
+
"format": "prettier --write \"lib/**/*.js\" \"bin/**\" \"test/**/*.js\"",
|
|
23
|
+
"precommit": "npm run lint && npm run test:smoke",
|
|
24
|
+
"prepublishOnly": "npm run test:all",
|
|
25
|
+
"library:check": "node .library/bin/library check",
|
|
26
|
+
"library:regenerate": "node .library/bin/library regenerate",
|
|
27
|
+
"library:extract": "node .library/bin/library extract",
|
|
28
|
+
"library:tokens": "node .library/bin/library tokens",
|
|
29
|
+
"library:graph": "node .library/bin/library graph",
|
|
30
|
+
"library:inject": "node .library/bin/library inject",
|
|
31
|
+
"library:validate": "node .library/bin/library validate",
|
|
32
|
+
"library:report": "node .library/bin/library report"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"git",
|
|
36
|
+
"hooks",
|
|
37
|
+
"claude",
|
|
38
|
+
"ai",
|
|
39
|
+
"code-review",
|
|
40
|
+
"commit-messages",
|
|
41
|
+
"pre-commit",
|
|
42
|
+
"automation"
|
|
43
|
+
],
|
|
44
|
+
"author": "Pablo Rovito",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/mscope-S-L/git-hooks.git"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=16.9.0"
|
|
52
|
+
},
|
|
53
|
+
"engineStrict": false,
|
|
54
|
+
"os": [
|
|
55
|
+
"darwin",
|
|
56
|
+
"linux",
|
|
57
|
+
"win32"
|
|
58
|
+
],
|
|
59
|
+
"preferGlobal": true,
|
|
60
|
+
"files": [
|
|
61
|
+
"bin/",
|
|
62
|
+
"lib/",
|
|
63
|
+
"templates/",
|
|
64
|
+
"README.md",
|
|
65
|
+
"CHANGELOG.md",
|
|
66
|
+
"CLAUDE.md",
|
|
67
|
+
"LICENSE"
|
|
68
|
+
],
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@anthropic-ai/sdk": "^0.91.0",
|
|
71
|
+
"@octokit/rest": "^21.0.0",
|
|
72
|
+
"langfuse": "^3.38.20"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@types/jest": "^29.5.0",
|
|
76
|
+
"eslint": "^8.57.1",
|
|
77
|
+
"jest": "^29.7.0",
|
|
78
|
+
"js-tiktoken": "^1.0.18",
|
|
79
|
+
"madge": "^8.0.0",
|
|
80
|
+
"prettier": "^3.2.0",
|
|
81
|
+
"tree-sitter-wasms": "^0.1.13",
|
|
82
|
+
"web-tree-sitter": "^0.24.7"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -30,7 +30,7 @@ OUTPUT_SCHEMA:
|
|
|
30
30
|
"line":int,
|
|
31
31
|
"method":"name",
|
|
32
32
|
"message":"desc",
|
|
33
|
-
"rule":"
|
|
33
|
+
"rule":"JBE-NNN|UIK-NNN|STR-NNN|...|empty"
|
|
34
34
|
}],
|
|
35
35
|
"blockingIssues":[{
|
|
36
36
|
"description":"text",
|
|
@@ -53,5 +53,6 @@ RULES:
|
|
|
53
53
|
- ratings:A(0issues),B(1-2minor),C(1major|3-5minor),D(2+major|1critical),E(1+blocker|2+critical)
|
|
54
54
|
- IMPORTANT: @Autowired usage in Spring is NOT a BLOCKER/CRITICAL issue (max severity: MAJOR)
|
|
55
55
|
- Spring dependency injection patterns (@Autowired) should NOT block commits
|
|
56
|
+
- RULE CATALOGUE: if a `=== PLATFORM ANTIPATTERN CATALOGUE ===` section is present in this prompt, classify each finding against it. When a finding matches a catalogue entry, populate `details[].rule` with the rule ID exactly (e.g. "JBE-001", "UIK-002"). The catalogue's severity is the MINIMUM to assign — don't downgrade. Findings that don't match any catalogue entry leave `rule` empty (those become candidate skill-gaps for the team to review separately).
|
|
56
57
|
|
|
57
58
|
ANALYZE_BELOW:
|
|
@@ -33,6 +33,12 @@
|
|
|
33
33
|
"model": "opus"
|
|
34
34
|
},
|
|
35
35
|
|
|
36
|
+
"skillRegistry": {
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"blockOn": "never",
|
|
39
|
+
"resumeOnCreatePr": true
|
|
40
|
+
},
|
|
41
|
+
|
|
36
42
|
"prAnalysis": {
|
|
37
43
|
"model": "sonnet",
|
|
38
44
|
"timeout": 300000,
|
|
@@ -55,6 +61,11 @@
|
|
|
55
61
|
"failOnError": true,
|
|
56
62
|
"failOnWarning": false,
|
|
57
63
|
"timeout": 30000
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"autoUpdate": {
|
|
67
|
+
"enabled": true,
|
|
68
|
+
"intervalHours": 24
|
|
58
69
|
}
|
|
59
70
|
},
|
|
60
71
|
|
|
@@ -90,6 +101,25 @@
|
|
|
90
101
|
"use_case": "Override the default sonnet model for judge passes"
|
|
91
102
|
},
|
|
92
103
|
|
|
104
|
+
"skillRegistry.enabled": {
|
|
105
|
+
"description": "Enable/disable the mscope automation-skills integration: deterministic rule checks in pre-commit, antipattern catalogue injection into the analysis prompt, and the skill-gap writer. NOTE the cross-repo side effect: when the sibling automation-skills repo is found, unclassified AI findings are appended as [skill-gap] candidates to that repo's skill-feedback.md (reviewed via `automation-skills retro`; never auto-merged into the registry).",
|
|
106
|
+
"default": "true",
|
|
107
|
+
"use_case": "Set to false to fully disable the skill-registry integration (it auto-skips anyway when the skill repo isn't found)"
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
"skillRegistry.blockOn": {
|
|
111
|
+
"description": "Severity threshold at which deterministic skill-registry findings block the commit",
|
|
112
|
+
"default": "never",
|
|
113
|
+
"examples": ["never", "critical", "high", "medium", "low"],
|
|
114
|
+
"use_case": "Set to 'critical' or 'high' once the team is ready to enforce the mscope rule catalogue"
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
"skillRegistry.resumeOnCreatePr": {
|
|
118
|
+
"description": "Run `automation-skills resume` (interactive lessons-learned capture to the skill repo's implementation-history.md) during create-pr, before tags are pushed. Skipped automatically in headless mode or when the automation-skills CLI is not installed.",
|
|
119
|
+
"default": "true",
|
|
120
|
+
"use_case": "Set to false if your team captures lessons through another channel"
|
|
121
|
+
},
|
|
122
|
+
|
|
93
123
|
"prAnalysis.model": {
|
|
94
124
|
"description": "Claude model for PR analysis",
|
|
95
125
|
"default": "sonnet",
|
|
@@ -143,6 +173,18 @@
|
|
|
143
173
|
"description": "Timeout in milliseconds for each linter execution",
|
|
144
174
|
"default": "30000",
|
|
145
175
|
"use_case": "Increase for large projects where linters take longer"
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
"autoUpdate.enabled": {
|
|
179
|
+
"description": "Enable the silent, throttled pre-command auto-update check. When a newer published version is detected before running a command, claude-git-hooks updates itself globally, reinstalls hooks (--force), and asks you to re-run your command. Excludes update/install/uninstall/help/version/migrate-config, and is skipped in --headless mode. The manual `claude-hooks update` command runs verbosely regardless of this setting.",
|
|
180
|
+
"default": "true",
|
|
181
|
+
"use_case": "Set to false to disable automatic background updates and rely only on `claude-hooks update`"
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
"autoUpdate.intervalHours": {
|
|
185
|
+
"description": "Minimum hours between pre-command auto-update checks. Throttle state is stored in .claude/.last-update-check (gitignored).",
|
|
186
|
+
"default": "24",
|
|
187
|
+
"use_case": "Lower it for faster propagation of releases, or raise it to reduce network checks"
|
|
146
188
|
}
|
|
147
189
|
},
|
|
148
190
|
|