claude-git-hooks 2.18.0 → 2.18.1

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 CHANGED
@@ -5,6 +5,20 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
5
5
  El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.18.1] - 2026-03-04
9
+
10
+ ### ✨ Added
11
+ - Monorepo support for CHANGELOG discovery - discovers all CHANGELOG.md files and prompts to select when multiple are found (#77)
12
+ - AI-powered help and issue creation system (#79)
13
+
14
+ ### 🔧 Changed
15
+ - Enhanced `walkDirectoryTree` utility function in file-utils.js for reusable directory traversal
16
+ - Updated documentation to reflect monorepo CHANGELOG discovery capabilities
17
+
18
+ ### 🐛 Fixed
19
+ - Cross-platform path handling in git hooks directory resolution (normalized path separators)
20
+
21
+
8
22
  ## [2.18.0] - 2026-03-03
9
23
 
10
24
  ### ✨ Added
package/CLAUDE.md CHANGED
@@ -240,10 +240,11 @@ Wait for both → consolidate results
240
240
  | `claude-client.js` | Claude CLI wrapper | `analyzeCode()`, `analyzeCodeParallel()`, `executeClaudeWithRetry()` |
241
241
  | `prompt-builder.js` | Prompt construction | `buildAnalysisPrompt()`, `loadPrompt()` |
242
242
  | `git-operations.js` | Git abstractions | `getStagedFiles()`, `getUnstagedFiles()`, `getAllTrackedFiles()`, `getDiff()`, `getRepoRoot()`, `getBranchPushStatus()`, `pushBranch()`, `createCommit()`, `fetchRemote()`, `branchExists()`, `getRemoteBranches()`, `resolveBaseBranch()`, `getChangedFilesBetweenRefs()`, `getDiffBetweenRefs()`, `getCommitsBetweenRefs()` |
243
+ | `file-utils.js` | File operations | `ensureDir()`, `ensureOutputDir()`, `writeOutputFile()`, `walkDirectoryTree()` |
243
244
  | `pr-metadata-engine.js` | PR metadata generation | `getBranchContext()`, `buildDiffPayload()`, `generatePRMetadata()`, `analyzeBranchForPR()` (v2.14.0) |
244
245
  | `git-tag-manager.js` | Git tag operations | `createTag()`, `pushTags()`, `getLocalTags()`, `getRemoteTags()`, `compareLocalAndRemoteTags()`, `tagExists()` (v2.12.0) |
245
246
  | `version-manager.js` | Version management | `discoverVersionFiles()`, `getDiscoveryResult()`, `readVersionFromFile()`, `writeVersionToFile()`, `updateVersionFiles()`, `modifySuffix()`, `incrementVersion()`, `parseVersion()`, `validateVersionFormat()`, `compareVersions()`, `validateVersionAlignment()` (v2.15.5) |
246
- | `changelog-generator.js` | CHANGELOG generation | `generateChangelogEntry()`, `updateChangelogFile()`, `getLastFinalVersionTag()`, `getCommitsSinceTag()` (v2.12.0) |
247
+ | `changelog-generator.js` | CHANGELOG generation | `generateChangelogEntry()`, `updateChangelogFile()`, `getLastFinalVersionTag()`, `getCommitsSinceTag()`, `discoverChangelogFiles()`, `selectChangelogFile()` (v2.12.0) |
247
248
  | `github-api.js` | Octokit integration | `createPullRequest()`, `validateToken()`, `saveGitHubToken()`, `fetchFileContent()`, `fetchDirectoryListing()`, `createIssue()` |
248
249
  | `github-client.js` | GitHub helpers | `getReviewersForFiles()`, `parseGitHubRepo()` |
249
250
  | `preset-loader.js` | Preset system | `loadPreset()`, `listPresets()` |
package/README.md CHANGED
@@ -157,7 +157,8 @@ claude-hooks generate-changelog --base-branch develop
157
157
  - Analyzes commits since last tag using Claude
158
158
  - Categorizes by Conventional Commits types (feat, fix, refactor, etc.)
159
159
  - Generates Keep a Changelog format entries
160
- - Updates CHANGELOG.md automatically
160
+ - Discovers all CHANGELOG.md files (monorepo aware); prompts to select when multiple found
161
+ - Updates the selected CHANGELOG.md automatically
161
162
  - Useful when `bump-version --update-changelog` fails
162
163
 
163
164
  ### Disable/Enable Hooks
@@ -35,7 +35,8 @@ import {
35
35
  } from '../utils/git-tag-manager.js';
36
36
  import {
37
37
  generateChangelogEntry,
38
- updateChangelogFile
38
+ updateChangelogFile,
39
+ selectChangelogFile
39
40
  } from '../utils/changelog-generator.js';
40
41
  import {
41
42
  getRepoRoot,
@@ -582,6 +583,10 @@ export async function runBumpVersion(args) {
582
583
  process.exit(1);
583
584
  }
584
585
 
586
+ // selectedChangelogPath: set in Step 9 when multiple CHANGELOGs found;
587
+ // used in Step 10 to stage the correct file.
588
+ let selectedChangelogPath = null;
589
+
585
590
  // Step 9: Generate CHANGELOG (if requested)
586
591
  if (options.updateChangelog) {
587
592
  logger.debug('bump-version', 'Step 9: Generating CHANGELOG');
@@ -597,7 +602,9 @@ export async function runBumpVersion(args) {
597
602
  });
598
603
 
599
604
  if (changelogResult.content) {
600
- const updated = updateChangelogFile(changelogResult.content);
605
+ selectedChangelogPath = await selectChangelogFile();
606
+
607
+ const updated = updateChangelogFile(changelogResult.content, selectedChangelogPath);
601
608
  if (updated) {
602
609
  showSuccess('✓ CHANGELOG.md updated');
603
610
  } else {
@@ -620,9 +627,9 @@ export async function runBumpVersion(args) {
620
627
  // Collect files to stage
621
628
  const filesToStage = selectedFiles.map(f => f.path);
622
629
 
623
- // Add CHANGELOG if it was updated
630
+ // Add CHANGELOG if it was updated (use selected path from Step 9, or default root)
624
631
  if (options.updateChangelog) {
625
- const changelogPath = path.join(getRepoRoot(), 'CHANGELOG.md');
632
+ const changelogPath = selectedChangelogPath || path.join(getRepoRoot(), 'CHANGELOG.md');
626
633
  if (fs.existsSync(changelogPath)) {
627
634
  filesToStage.push(changelogPath);
628
635
  }
@@ -7,7 +7,11 @@
7
7
  */
8
8
 
9
9
  import { discoverVersionFiles } from '../utils/version-manager.js';
10
- import { generateChangelogEntry, updateChangelogFile } from '../utils/changelog-generator.js';
10
+ import {
11
+ generateChangelogEntry,
12
+ updateChangelogFile,
13
+ selectChangelogFile
14
+ } from '../utils/changelog-generator.js';
11
15
  import { getConfig } from '../config.js';
12
16
  import { showInfo, showSuccess, showWarning, showError } from '../utils/interactive-ui.js';
13
17
  import logger from '../utils/logger.js';
@@ -116,7 +120,9 @@ export async function runGenerateChangelog(args) {
116
120
  });
117
121
 
118
122
  if (changelogResult.content) {
119
- const updated = updateChangelogFile(changelogResult.content);
123
+ const selectedPath = await selectChangelogFile();
124
+
125
+ const updated = updateChangelogFile(changelogResult.content, selectedPath);
120
126
  if (updated) {
121
127
  showSuccess('✓ CHANGELOG.md updated');
122
128
  } else {
@@ -13,6 +13,8 @@ import { getRepoRoot } from './git-operations.js';
13
13
  import { executeClaudeWithRetry, extractJSON } from './claude-client.js';
14
14
  import { loadPrompt } from './prompt-builder.js';
15
15
  import logger from './logger.js';
16
+ import { walkDirectoryTree } from './file-utils.js';
17
+ import { showInfo, promptMenu } from './interactive-ui.js';
16
18
 
17
19
  /**
18
20
  * Gets the last final version tag (without suffix)
@@ -256,13 +258,30 @@ export async function generateChangelogEntry(options) {
256
258
  .map(c => `${c.sha.substring(0, 7)} - ${c.message} (${c.author}, ${c.date})`)
257
259
  .join('\n');
258
260
 
261
+ if (!fromTag) {
262
+ logger.debug(
263
+ 'changelog-generator - generateChangelogEntry',
264
+ 'No version tags found — using full commit history for first CHANGELOG entry'
265
+ );
266
+ }
267
+
259
268
  // Get diff for context
260
269
  const compareWith = fromTag ? `${fromTag}..HEAD` : `origin/${baseBranch}...HEAD`;
261
270
  let fullDiff;
262
271
  try {
263
272
  fullDiff = execSync(`git diff ${compareWith}`, { encoding: 'utf8' });
264
273
  } catch (error) {
265
- logger.warning('changelog-generator - generateChangelogEntry', 'Could not get diff', error);
274
+ if (fromTag) {
275
+ // Unexpected: a tag exists but diff failed
276
+ logger.warning('changelog-generator - generateChangelogEntry', 'Could not get diff', error);
277
+ } else {
278
+ // Expected: no remote or remote branch not yet pushed — commit messages are primary context
279
+ logger.debug(
280
+ 'changelog-generator - generateChangelogEntry',
281
+ 'Diff not available — no remote or no version tags yet, using commit messages as primary context',
282
+ { error: error.message }
283
+ );
284
+ }
266
285
  fullDiff = '(diff not available)';
267
286
  }
268
287
 
@@ -322,18 +341,20 @@ export async function generateChangelogEntry(options) {
322
341
  * Why: Inserts new version section at the top
323
342
  *
324
343
  * @param {string} newContent - New CHANGELOG section
344
+ * @param {string|null} changelogPath - Absolute path to target CHANGELOG file.
345
+ * If null, defaults to CHANGELOG.md at repo root (backward compatible).
325
346
  * @returns {boolean} True if updated successfully
326
347
  */
327
- export function updateChangelogFile(newContent) {
348
+ export function updateChangelogFile(newContent, changelogPath = null) {
328
349
  logger.debug('changelog-generator - updateChangelogFile', 'Updating CHANGELOG.md');
329
350
 
330
351
  try {
331
352
  const repoRoot = getRepoRoot();
332
- const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
353
+ const targetPath = changelogPath || path.join(repoRoot, 'CHANGELOG.md');
333
354
 
334
355
  let existingContent = '';
335
- if (fs.existsSync(changelogPath)) {
336
- existingContent = fs.readFileSync(changelogPath, 'utf8');
356
+ if (fs.existsSync(targetPath)) {
357
+ existingContent = fs.readFileSync(targetPath, 'utf8');
337
358
  } else {
338
359
  // Create new CHANGELOG with header
339
360
  existingContent = `# Changelog
@@ -367,7 +388,7 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.h
367
388
  updatedContent = `${newContent }\n${ existingContent}`;
368
389
  }
369
390
 
370
- fs.writeFileSync(changelogPath, updatedContent, 'utf8');
391
+ fs.writeFileSync(targetPath, updatedContent, 'utf8');
371
392
 
372
393
  logger.debug('changelog-generator - updateChangelogFile', 'CHANGELOG.md updated successfully');
373
394
 
@@ -378,3 +399,84 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.h
378
399
  return false;
379
400
  }
380
401
  }
402
+
403
+ /**
404
+ * Discovers all CHANGELOG.md files in the repository
405
+ * Why: Support monorepos where each module has its own changelog
406
+ *
407
+ * Reuses the same maxDepth and ignoreSet patterns as discoverVersionFiles()
408
+ * in version-manager.js for consistency.
409
+ *
410
+ * @param {Object} options - Discovery options
411
+ * @param {number} options.maxDepth - Maximum directory depth to search (default: 3)
412
+ * @param {string[]} options.ignoreDirs - Additional directories to ignore
413
+ * @returns {string[]} Absolute paths to discovered CHANGELOG.md files, root-first
414
+ */
415
+ export function discoverChangelogFiles(options = {}) {
416
+ const { maxDepth = 3, ignoreDirs = [] } = options;
417
+
418
+ logger.debug('changelog-generator - discoverChangelogFiles', 'Starting discovery', { maxDepth });
419
+
420
+ const repoRoot = getRepoRoot();
421
+
422
+ const defaultIgnore = [
423
+ 'node_modules', 'dist', 'build', 'target', 'vendor',
424
+ '.next', '.nuxt', 'coverage'
425
+ ];
426
+ const ignoreSet = new Set([...defaultIgnore, ...ignoreDirs]);
427
+
428
+ const found = [];
429
+
430
+ walkDirectoryTree(repoRoot, {
431
+ maxDepth,
432
+ ignoreSet,
433
+ onFile: (entry, fullPath) => {
434
+ if (entry.name.toLowerCase() === 'changelog.md') found.push(fullPath);
435
+ },
436
+ onError: (dir, error) => {
437
+ logger.debug('changelog-generator - discoverChangelogFiles', 'Error reading directory', {
438
+ dir,
439
+ error: error.message
440
+ });
441
+ }
442
+ });
443
+
444
+ // Sort: root-level first, then by depth, then alphabetically
445
+ found.sort((a, b) => {
446
+ const depthA = path.relative(repoRoot, a).split(path.sep).length;
447
+ const depthB = path.relative(repoRoot, b).split(path.sep).length;
448
+ if (depthA !== depthB) return depthA - depthB;
449
+ return a.localeCompare(b);
450
+ });
451
+
452
+ logger.debug('changelog-generator - discoverChangelogFiles', 'Discovery complete', {
453
+ count: found.length,
454
+ files: found.map(f => path.relative(repoRoot, f))
455
+ });
456
+
457
+ return found;
458
+ }
459
+
460
+ /**
461
+ * Discovers CHANGELOG files and prompts the user to select one if multiple exist
462
+ * Why: Shared interactive selection used by both generate-changelog and bump-version
463
+ *
464
+ * @returns {Promise<string|null>} Absolute path to selected CHANGELOG, or null if none found
465
+ * (null → updateChangelogFile falls back to repo-root CHANGELOG.md)
466
+ */
467
+ export async function selectChangelogFile() {
468
+ const files = discoverChangelogFiles();
469
+
470
+ if (files.length > 1) {
471
+ const repoRoot = getRepoRoot();
472
+ const menuOptions = files.map((f, i) => ({
473
+ key: String(i + 1),
474
+ label: path.relative(repoRoot, f)
475
+ }));
476
+ showInfo('Multiple CHANGELOG files detected:');
477
+ const selected = await promptMenu('Select CHANGELOG to update:', menuOptions, '1');
478
+ return files[parseInt(selected) - 1];
479
+ }
480
+
481
+ return files[0] || null;
482
+ }
@@ -3,7 +3,8 @@
3
3
  * Purpose: Utility functions for file system operations
4
4
  */
5
5
 
6
- import fs from 'fs/promises';
6
+ import fsPromises from 'fs/promises';
7
+ import fs from 'fs';
7
8
  import path from 'path';
8
9
  import { getRepoRoot } from './git-operations.js';
9
10
  import logger from './logger.js';
@@ -20,7 +21,7 @@ export const ensureDir = async (dirPath) => {
20
21
  : path.join(getRepoRoot(), dirPath);
21
22
 
22
23
  try {
23
- await fs.mkdir(absolutePath, { recursive: true });
24
+ await fsPromises.mkdir(absolutePath, { recursive: true });
24
25
  logger.debug('file-utils - ensureDir', 'Directory ensured', { path: absolutePath });
25
26
  } catch (error) {
26
27
  logger.error('file-utils - ensureDir', 'Failed to create directory', error);
@@ -55,10 +56,47 @@ export const writeOutputFile = async (filePath, content, config) => {
55
56
  ? filePath
56
57
  : path.join(getRepoRoot(), filePath);
57
58
 
58
- await fs.writeFile(absolutePath, content, 'utf8');
59
+ await fsPromises.writeFile(absolutePath, content, 'utf8');
59
60
 
60
61
  logger.debug('file-utils - writeOutputFile', 'File written', {
61
62
  path: absolutePath,
62
63
  size: content.length
63
64
  });
64
65
  };
66
+
67
+ /**
68
+ * Walks a directory tree recursively, invoking callbacks for each file
69
+ * Why: Shared traversal logic for discoverVersionFiles and discoverChangelogFiles
70
+ *
71
+ * @param {string} rootDir - Starting directory path
72
+ * @param {Object} options
73
+ * @param {number} options.maxDepth - Maximum recursion depth (default: 3)
74
+ * @param {Set<string>} options.ignoreSet - Directory names to skip
75
+ * @param {Function} options.onFile - Called as onFile(entry, fullPath) for each file entry
76
+ * @param {Function} options.onError - Called as onError(dir, error) on readdirSync failures
77
+ */
78
+ export function walkDirectoryTree(rootDir, { maxDepth = 3, ignoreSet = new Set(), onFile, onError } = {}) {
79
+ function walk(dir, depth) {
80
+ if (depth > maxDepth) return;
81
+
82
+ try {
83
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
84
+
85
+ for (const entry of entries) {
86
+ if (entry.name.startsWith('.') || ignoreSet.has(entry.name)) continue;
87
+
88
+ const fullPath = path.join(dir, entry.name);
89
+
90
+ if (entry.isDirectory()) {
91
+ walk(fullPath, depth + 1);
92
+ } else if (entry.isFile()) {
93
+ onFile?.(entry, fullPath);
94
+ }
95
+ }
96
+ } catch (error) {
97
+ onError?.(dir, error);
98
+ }
99
+ }
100
+
101
+ walk(rootDir, 0);
102
+ }
@@ -14,6 +14,7 @@ import fs from 'fs';
14
14
  import path from 'path';
15
15
  import { getRepoRoot } from './git-operations.js';
16
16
  import logger from './logger.js';
17
+ import { walkDirectoryTree } from './file-utils.js';
17
18
 
18
19
  /**
19
20
  * Cache for discovery result
@@ -232,69 +233,38 @@ export function discoverVersionFiles(options = {}) {
232
233
  ];
233
234
  const ignoreSet = new Set([...defaultIgnore, ...ignoreDirs]);
234
235
 
235
- /**
236
- * Recursively walks directory tree
237
- * @param {string} dir - Current directory path
238
- * @param {number} depth - Current depth level
239
- */
240
- function walkDirectory(dir, depth) {
241
- if (depth > maxDepth) {
242
- return;
243
- }
244
-
245
- try {
246
- const entries = fs.readdirSync(dir, { withFileTypes: true });
247
-
248
- for (const entry of entries) {
249
- // Skip hidden directories and ignored patterns
250
- if (entry.name.startsWith('.') || ignoreSet.has(entry.name)) {
251
- continue;
252
- }
253
-
254
- const fullPath = path.join(dir, entry.name);
255
-
256
- if (entry.isDirectory()) {
257
- // Recurse into subdirectory
258
- walkDirectory(fullPath, depth + 1);
259
- } else if (entry.isFile()) {
260
- // Check if this file matches any registered type
261
- for (const fileType of fileTypes) {
262
- const registry = VERSION_FILE_TYPES[fileType];
263
- if (registry && entry.name === registry.filename) {
264
- // Read version from file
265
- const version = registry.readVersion(fullPath);
266
-
267
- // Create descriptor
268
- const descriptor = {
269
- path: fullPath,
270
- relativePath: path.relative(repoRoot, fullPath),
271
- type: fileType,
272
- projectLabel: registry.projectLabel,
273
- version,
274
- selected: true // Default: all files selected
275
- };
276
-
277
- discoveredFiles.push(descriptor);
278
-
279
- logger.debug('version-manager - discoverVersionFiles', 'Found version file', {
280
- relativePath: descriptor.relativePath,
281
- type: fileType,
282
- version
283
- });
284
- }
285
- }
236
+ walkDirectoryTree(repoRoot, {
237
+ maxDepth,
238
+ ignoreSet,
239
+ onFile: (entry, fullPath) => {
240
+ for (const fileType of fileTypes) {
241
+ const registry = VERSION_FILE_TYPES[fileType];
242
+ if (registry && entry.name === registry.filename) {
243
+ const version = registry.readVersion(fullPath);
244
+ const descriptor = {
245
+ path: fullPath,
246
+ relativePath: path.relative(repoRoot, fullPath),
247
+ type: fileType,
248
+ projectLabel: registry.projectLabel,
249
+ version,
250
+ selected: true
251
+ };
252
+ discoveredFiles.push(descriptor);
253
+ logger.debug('version-manager - discoverVersionFiles', 'Found version file', {
254
+ relativePath: descriptor.relativePath,
255
+ type: fileType,
256
+ version
257
+ });
286
258
  }
287
259
  }
288
- } catch (error) {
260
+ },
261
+ onError: (dir, error) => {
289
262
  logger.debug('version-manager - discoverVersionFiles', 'Error reading directory', {
290
263
  dir,
291
264
  error: error.message
292
265
  });
293
266
  }
294
- }
295
-
296
- // Start walking from repo root at depth 0
297
- walkDirectory(repoRoot, 0);
267
+ });
298
268
 
299
269
  // Sort results: root files first, then by depth, then alphabetically
300
270
  discoveredFiles.sort((a, b) => {
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "claude-git-hooks",
3
- "version": "2.18.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
- "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.18.1",
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
+ }