claude-git-hooks 2.61.2 → 2.67.3
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 +329 -3
- package/README.md +34 -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 +52 -90
- package/lib/commands/close-release.js +25 -19
- package/lib/commands/create-pr.js +152 -83
- package/lib/commands/create-release.js +19 -13
- package/lib/commands/help.js +13 -3
- package/lib/defaults.json +5 -0
- package/lib/hooks/pre-commit.js +112 -32
- package/lib/messages/library-warnings.js +128 -10
- package/lib/utils/analysis-engine.js +7 -3
- package/lib/utils/claude-client.js +6 -1
- package/lib/utils/config-registry.js +1 -0
- package/lib/utils/git-tag-manager.js +104 -0
- package/lib/utils/judge.js +2 -1
- 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 +37 -18
- package/package.json +2 -1
- package/templates/CLAUDE_ANALYSIS_PROMPT.md +2 -1
- package/templates/config.advanced.example.json +25 -0
|
@@ -247,14 +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
|
|
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
|
+
}
|
|
251
258
|
const descriptor = {
|
|
252
259
|
path: fullPath,
|
|
253
260
|
relativePath: path.relative(repoRoot, fullPath),
|
|
254
261
|
type: fileType,
|
|
255
262
|
projectLabel: registry.projectLabel,
|
|
256
263
|
version,
|
|
257
|
-
selected:
|
|
264
|
+
selected: version !== null
|
|
258
265
|
};
|
|
259
266
|
discoveredFiles.push(descriptor);
|
|
260
267
|
logger.debug('version-manager - discoverVersionFiles', 'Found version file', {
|
|
@@ -285,19 +292,19 @@ export function discoverVersionFiles(options = {}) {
|
|
|
285
292
|
return a.relativePath.localeCompare(b.relativePath);
|
|
286
293
|
});
|
|
287
294
|
|
|
288
|
-
// Determine resolved version (prefer root-level
|
|
295
|
+
// Determine resolved version from semver files only (prefer root-level, then first found)
|
|
289
296
|
let resolvedVersion = null;
|
|
290
|
-
const
|
|
297
|
+
const semverFiles = discoveredFiles.filter((f) => f.selected);
|
|
298
|
+
const rootFile = semverFiles.find((f) => !f.relativePath.includes(path.sep));
|
|
291
299
|
if (rootFile && rootFile.version) {
|
|
292
300
|
resolvedVersion = rootFile.version;
|
|
293
|
-
} else if (
|
|
294
|
-
|
|
295
|
-
const firstWithVersion = discoveredFiles.find((f) => f.version !== null);
|
|
301
|
+
} else if (semverFiles.length > 0) {
|
|
302
|
+
const firstWithVersion = semverFiles.find((f) => f.version !== null);
|
|
296
303
|
resolvedVersion = firstWithVersion ? firstWithVersion.version : null;
|
|
297
304
|
}
|
|
298
305
|
|
|
299
|
-
// Check for version mismatch
|
|
300
|
-
const versions =
|
|
306
|
+
// Check for version mismatch (only among semver files)
|
|
307
|
+
const versions = semverFiles.filter((f) => f.version !== null).map((f) => f.version);
|
|
301
308
|
const uniqueVersions = [...new Set(versions)];
|
|
302
309
|
const mismatch = uniqueVersions.length > 1;
|
|
303
310
|
|
|
@@ -1149,24 +1156,36 @@ export function compareVersions(version1, version2) {
|
|
|
1149
1156
|
*
|
|
1150
1157
|
* @returns {Promise<Object>} Validation result with alignment status and issues
|
|
1151
1158
|
*/
|
|
1152
|
-
export async function validateVersionAlignment() {
|
|
1153
|
-
logger.debug('version-manager - validateVersionAlignment', 'Validating version alignment'
|
|
1159
|
+
export async function validateVersionAlignment(baseBranch = null) {
|
|
1160
|
+
logger.debug('version-manager - validateVersionAlignment', 'Validating version alignment', {
|
|
1161
|
+
baseBranch
|
|
1162
|
+
});
|
|
1154
1163
|
|
|
1155
1164
|
try {
|
|
1156
1165
|
// Discover all version files
|
|
1157
1166
|
const discovery = discoverVersionFiles();
|
|
1158
1167
|
|
|
1159
|
-
// Get git tag version
|
|
1160
|
-
const {
|
|
1161
|
-
|
|
1168
|
+
// Get git tag version scoped to HEAD when baseBranch is provided
|
|
1169
|
+
const {
|
|
1170
|
+
getLatestLocalTag, getLatestLocalTagOnBranch,
|
|
1171
|
+
parseTagVersion,
|
|
1172
|
+
getLatestRemoteTag, getLatestRemoteTagOnBranch
|
|
1173
|
+
} = await import('./git-tag-manager.js');
|
|
1174
|
+
const latestTag = baseBranch ? getLatestLocalTagOnBranch(baseBranch) : getLatestLocalTag();
|
|
1162
1175
|
const tagVersion = latestTag ? parseTagVersion(latestTag) : null;
|
|
1163
1176
|
|
|
1164
1177
|
// Get CHANGELOG version
|
|
1165
1178
|
const changelogVersion = readChangelogVersion();
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1179
|
+
let latestRemoteTag;
|
|
1180
|
+
if (baseBranch) {
|
|
1181
|
+
latestRemoteTag = getLatestRemoteTagOnBranch(baseBranch);
|
|
1182
|
+
// Fall back to global if branch-scoped lookup returns nothing
|
|
1183
|
+
if (!latestRemoteTag) {
|
|
1184
|
+
latestRemoteTag = await getLatestRemoteTag();
|
|
1185
|
+
}
|
|
1186
|
+
} else {
|
|
1187
|
+
latestRemoteTag = await getLatestRemoteTag();
|
|
1188
|
+
}
|
|
1170
1189
|
const remoteVersion = latestRemoteTag ? parseTagVersion(latestRemoteTag) : null;
|
|
1171
1190
|
|
|
1172
1191
|
// Collect all local versions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-git-hooks",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.67.3",
|
|
4
4
|
"description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"test:changed": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/unit --changedSince=main --forceExit",
|
|
17
17
|
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
18
18
|
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
19
|
+
"test:e2e": "bash test/manual/sdlc-stability-check.sh",
|
|
19
20
|
"lint": "eslint lib/ bin/claude-hooks .library/librarian/",
|
|
20
21
|
"lint:fix": "eslint lib/ bin/claude-hooks .library/librarian/ --fix",
|
|
21
22
|
"format": "prettier --write \"lib/**/*.js\" \"bin/**\" \"test/**/*.js\"",
|
|
@@ -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,
|
|
@@ -90,6 +96,25 @@
|
|
|
90
96
|
"use_case": "Override the default sonnet model for judge passes"
|
|
91
97
|
},
|
|
92
98
|
|
|
99
|
+
"skillRegistry.enabled": {
|
|
100
|
+
"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).",
|
|
101
|
+
"default": "true",
|
|
102
|
+
"use_case": "Set to false to fully disable the skill-registry integration (it auto-skips anyway when the skill repo isn't found)"
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
"skillRegistry.blockOn": {
|
|
106
|
+
"description": "Severity threshold at which deterministic skill-registry findings block the commit",
|
|
107
|
+
"default": "never",
|
|
108
|
+
"examples": ["never", "critical", "high", "medium", "low"],
|
|
109
|
+
"use_case": "Set to 'critical' or 'high' once the team is ready to enforce the mscope rule catalogue"
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
"skillRegistry.resumeOnCreatePr": {
|
|
113
|
+
"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.",
|
|
114
|
+
"default": "true",
|
|
115
|
+
"use_case": "Set to false if your team captures lessons through another channel"
|
|
116
|
+
},
|
|
117
|
+
|
|
93
118
|
"prAnalysis.model": {
|
|
94
119
|
"description": "Claude model for PR analysis",
|
|
95
120
|
"default": "sonnet",
|