claude-git-hooks 2.67.3 → 2.68.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,33 @@ 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.68.1] - 2026-06-12
9
+
10
+ ### 🐛 Fixed
11
+ - Fixed gotcha solicitation receiving empty change context during PR creation, causing Claude to return no candidates for every book — the pipeline now attributes source files, commit messages, and modified comments to each changed book (#197)
12
+
13
+
14
+ ## [2.68.0] - 2026-06-12
15
+
16
+ ### ✨ Added
17
+ - Silent, throttled pre-command auto-update guard that checks for newer versions before running commands and self-updates automatically (#196)
18
+ - New `autoUpdate` configuration section with `enabled` (default: true) and `intervalHours` (default: 24) settings to control automatic update behavior
19
+ - Centralized `lib/utils/auto-update.js` module providing shared update logic (`getUpdateStatus`, `performUpdate`, `shouldAutoCheck`, `maybeAutoUpdate`) for all update paths
20
+
21
+ ### 🔧 Changed
22
+ - Refactored `update` command into a thin wrapper over the shared auto-update module, eliminating duplicated version-check and npm-install logic
23
+ - Refactored install-time update prompt to use shared `getUpdateStatus()` and `performUpdate()` instead of inline implementation
24
+ - Auto-update is fully fail-open and skipped in `--headless` mode — never blocks the real command on failure
25
+ - Excluded fast/offline commands (`update`, `install`, `uninstall`, `help`, `version`, `migrate-config`) from the pre-command auto-update check to avoid recursion and unnecessary latency
26
+
27
+ ### 🐛 Fixed
28
+ - Fixed install-time update prompt incorrectly offering to "update" dev builds that are ahead of the latest published npm version
29
+
30
+ ### 🗑️ Removed
31
+ - Removed `check-version.sh` template installation from the install command (replaced by the integrated auto-update guard)
32
+ - Removed inline `npm install -g` and `runInstall(['--force'])` calls from `update.js` and `install.js` in favor of centralized `performUpdate()`
33
+
34
+
8
35
  ## [2.67.3] - 2026-06-12
9
36
 
10
37
  ### ✨ Added
package/bin/claude-hooks CHANGED
@@ -131,6 +131,25 @@ async function main() {
131
131
  logger.debug('bin - main', 'Authorization guard bypassed (--headless)', { command: entry.name });
132
132
  }
133
133
 
134
+ // --- Pre-command auto-update guard (silent, throttled) ---
135
+ // Single choke-point: every command flows through here, so auto-update is added
136
+ // once instead of in each command file. Mirrors the authorization guard above.
137
+ // Fully fail-open — never blocks the real command.
138
+ if (!isHeadless) {
139
+ try {
140
+ const { maybeAutoUpdate } = await import('../lib/utils/auto-update.js');
141
+ const updated = await maybeAutoUpdate(entry.name);
142
+ if (updated) {
143
+ // The running binary was just replaced; user must re-run their command.
144
+ process.exit(0);
145
+ }
146
+ } catch (err) {
147
+ logger.debug('bin - main', 'Pre-command auto-update skipped (non-fatal)', {
148
+ error: err.message
149
+ });
150
+ }
151
+ }
152
+
134
153
  // --- Commands with special argument handling ---
135
154
 
136
155
  // analyze: translate flags to options object
@@ -22,7 +22,7 @@ import {
22
22
  showWarning,
23
23
  promptConfirmation
24
24
  } from '../utils/interactive-ui.js';
25
- import { getBranchPushStatus, pushBranch, stageFiles, createCommit, getRepoRoot } from '../utils/git-operations.js';
25
+ import { getBranchPushStatus, pushBranch, stageFiles, createCommit, getRepoRoot, getCommitsBetweenRefs } from '../utils/git-operations.js';
26
26
  import logger from '../utils/logger.js';
27
27
  import { libraryModuleUrl } from '../utils/library-resolver.js';
28
28
  import { resolveLabels } from '../utils/label-resolver.js';
@@ -468,7 +468,25 @@ export async function runCreatePr(args) {
468
468
  showInfo('Running Library maintenance pipeline...');
469
469
  const { createPrPipeline } = await import(libraryModuleUrl('librarian/index.js'));
470
470
 
471
- const pipelineSummary = await createPrPipeline({ repoRoot: root });
471
+ // Gather change context for gotcha solicitation. Without this the
472
+ // generator gets empty changedFiles + no commit context and Claude
473
+ // returns no candidate for every book. Best-effort — git failures
474
+ // here must never block PR creation.
475
+ let pipelineCommitSha = '';
476
+ let pipelineCommitMessages = [];
477
+ try {
478
+ pipelineCommitSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
479
+ } catch { /* commitSha only keys the gotcha cache — non-fatal */ }
480
+ try {
481
+ const log = getCommitsBetweenRefs(baseBranch, 'HEAD', { format: '%s' });
482
+ pipelineCommitMessages = log.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
483
+ } catch { /* commit messages are optional gotcha context — non-fatal */ }
484
+
485
+ const pipelineSummary = await createPrPipeline({
486
+ repoRoot: root,
487
+ commitSha: pipelineCommitSha,
488
+ commitMessages: pipelineCommitMessages,
489
+ });
472
490
  const {
473
491
  modifiedFiles: libraryFiles,
474
492
  perStep,
@@ -27,10 +27,9 @@ import {
27
27
  getGitHooksPath,
28
28
  isWindows,
29
29
  getClaudeCommand,
30
- getPackageJson,
31
- getLatestVersion,
32
30
  Entertainment
33
31
  } from './helpers.js';
32
+ import { getUpdateStatus, performUpdate } from '../utils/auto-update.js';
34
33
  import { runSetupGitHub } from './setup-github.js';
35
34
  import { generateCompletionData } from '../cli-metadata.js';
36
35
  import { getConfig } from '../config.js';
@@ -41,11 +40,12 @@ import { getConfig } from '../config.js';
41
40
  */
42
41
  async function checkVersionAndPromptUpdate() {
43
42
  try {
44
- const currentVersion = getPackageJson().version;
45
- const latestVersion = await getLatestVersion('claude-git-hooks');
43
+ const { currentVersion, latestVersion, isNewer } = await getUpdateStatus();
46
44
 
47
- if (currentVersion === latestVersion) {
48
- return true; // Already updated
45
+ // isNewer guards against the `===` downgrade bug: a dev build ahead of npm
46
+ // (current > latest) must NOT prompt to "update" to an older published version.
47
+ if (!isNewer) {
48
+ return true; // Already on latest (or a dev build ahead of npm)
49
49
  }
50
50
 
51
51
  console.log('');
@@ -58,13 +58,13 @@ async function checkVersionAndPromptUpdate() {
58
58
  });
59
59
 
60
60
  return new Promise((resolve) => {
61
- rl.question('Do you want to update now? (y/n): ', (answer) => {
61
+ rl.question('Do you want to update now? (y/n): ', async (answer) => {
62
62
  rl.close();
63
63
 
64
64
  if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
65
- info('Updating claude-git-hooks...');
66
65
  try {
67
- execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
66
+ // install is about to run, so skip the hook reinstall here
67
+ await performUpdate({ reinstallHooks: false, silent: false });
68
68
  success('Update completed. Please run your command again.');
69
69
  process.exit(0); // Exit so user restarts the process
70
70
  } catch (e) {
@@ -463,16 +463,6 @@ export async function runInstall(args) {
463
463
  success(`${hook} installed`);
464
464
  });
465
465
 
466
- // Copy version verification script with LF line endings
467
- const checkVersionSource = path.join(templatesPath, 'check-version.sh');
468
- const checkVersionDest = path.join(hooksPath, 'check-version.sh');
469
-
470
- if (fs.existsSync(checkVersionSource)) {
471
- copyWithLF(checkVersionSource, checkVersionDest);
472
- fs.chmodSync(checkVersionDest, '755');
473
- success('Version verification script installed');
474
- }
475
-
476
466
  // Create .claude directory if it doesn't exist
477
467
  const claudeDir = '.claude';
478
468
  if (!fs.existsSync(claudeDir)) {
@@ -1,19 +1,13 @@
1
1
  /**
2
2
  * File: update.js
3
3
  * Purpose: Update command - update to the latest version
4
+ *
5
+ * Thin wrapper over the shared auto-update logic in lib/utils/auto-update.js.
6
+ * Runs verbosely (silent: false) — this is a user-initiated update.
4
7
  */
5
8
 
6
- import { execSync } from 'child_process';
7
- import {
8
- error,
9
- success,
10
- info,
11
- warning,
12
- getPackageJson,
13
- getLatestVersion,
14
- compareVersions
15
- } from './helpers.js';
16
- import { runInstall } from './install.js';
9
+ import { error, success, info, warning } from './helpers.js';
10
+ import { getUpdateStatus, performUpdate } from '../utils/auto-update.js';
17
11
 
18
12
  /**
19
13
  * Update command - update to the latest version
@@ -22,33 +16,26 @@ export async function runUpdate() {
22
16
  info('Checking latest available version...');
23
17
 
24
18
  try {
25
- const currentVersion = getPackageJson().version;
26
- const latestVersion = await getLatestVersion('claude-git-hooks');
19
+ const { currentVersion, latestVersion, isNewer, isDev } = await getUpdateStatus();
27
20
 
28
- const comparison = compareVersions(currentVersion, latestVersion);
29
-
30
- if (comparison === 0) {
31
- success(`You already have the latest version installed (${currentVersion})`);
32
- return;
33
- } else if (comparison > 0) {
21
+ if (isDev) {
34
22
  info(`You are using a development version (${currentVersion})`);
35
23
  info(`Latest published version: ${latestVersion}`);
36
24
  success(`You already have the latest version installed (${currentVersion})`);
37
25
  return;
38
26
  }
39
27
 
28
+ if (!isNewer) {
29
+ success(`You already have the latest version installed (${currentVersion})`);
30
+ return;
31
+ }
32
+
40
33
  info(`Current version: ${currentVersion}`);
41
34
  info(`Available version: ${latestVersion}`);
42
35
 
43
- // Update the package
44
- info('Updating claude-git-hooks...');
45
36
  try {
46
- execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
37
+ await performUpdate({ reinstallHooks: true, silent: false });
47
38
  success(`Successfully updated to version ${latestVersion}`);
48
-
49
- // Reinstall hooks with the new version
50
- info('Reinstalling hooks with the new version...');
51
- await runInstall(['--force']);
52
39
  } catch (updateError) {
53
40
  error('Error updating. Try running: npm install -g claude-git-hooks@latest');
54
41
  }
@@ -56,9 +43,8 @@ export async function runUpdate() {
56
43
  warning('Could not check the latest available version');
57
44
  warning('Trying to update anyway...');
58
45
  try {
59
- execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
46
+ await performUpdate({ reinstallHooks: true, silent: false });
60
47
  success('Update completed');
61
- await runInstall(['--force']);
62
48
  } catch (updateError) {
63
49
  error(`Error updating: ${updateError.message}`);
64
50
  }
package/lib/defaults.json CHANGED
@@ -33,6 +33,10 @@
33
33
  "debug": false,
34
34
  "wslCheckTimeout": 15000
35
35
  },
36
+ "autoUpdate": {
37
+ "enabled": true,
38
+ "intervalHours": 24
39
+ },
36
40
  "git": {
37
41
  "diffFilter": "ACM"
38
42
  },
@@ -0,0 +1,198 @@
1
+ /**
2
+ * File: auto-update.js
3
+ * Purpose: Centralized self-update logic shared by the manual `update` command,
4
+ * the install-time prompt, and the pre-command guard in bin/claude-hooks.
5
+ *
6
+ * Single source of truth for: resolving current-vs-latest version, performing the
7
+ * `npm install -g` update + hook reinstall, throttling automatic checks, and the
8
+ * silent pre-command auto-update guard.
9
+ */
10
+
11
+ import { execSync } from 'child_process';
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import {
15
+ info,
16
+ warning,
17
+ success,
18
+ getPackageJson,
19
+ getLatestVersion,
20
+ compareVersions
21
+ } from '../commands/helpers.js';
22
+ import { getRepoRoot } from './git-operations.js';
23
+ import { getConfig } from '../config.js';
24
+ import logger from './logger.js';
25
+
26
+ const PACKAGE_NAME = 'claude-git-hooks';
27
+ const CHECK_FILE = '.last-update-check';
28
+ const DEFAULT_INTERVAL_HOURS = 24;
29
+
30
+ // Commands that must NOT trigger the pre-command auto-update guard.
31
+ // Why: avoids recursion (update → install --force), and skips fast/offline
32
+ // commands where a network round-trip would only add latency.
33
+ const EXCLUDED_COMMANDS = new Set([
34
+ 'update',
35
+ 'install',
36
+ 'uninstall',
37
+ 'help',
38
+ 'version',
39
+ 'migrate-config'
40
+ ]);
41
+
42
+ /**
43
+ * Resolve current vs latest version and derived flags.
44
+ * @returns {Promise<{currentVersion:string, latestVersion:string, comparison:number, isNewer:boolean, isDev:boolean}>}
45
+ */
46
+ export async function getUpdateStatus() {
47
+ const currentVersion = getPackageJson().version;
48
+ const latestVersion = await getLatestVersion(PACKAGE_NAME);
49
+ const comparison = compareVersions(currentVersion, latestVersion);
50
+
51
+ return {
52
+ currentVersion,
53
+ latestVersion,
54
+ comparison,
55
+ isNewer: comparison < 0,
56
+ isDev: comparison > 0
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Update the globally-installed package and (optionally) reinstall hooks in the
62
+ * current repo. Implements the approved flow:
63
+ * npm install -g claude-git-hooks@latest
64
+ * → locate repo root; if none, warn and stop; else reinstall hooks (--force)
65
+ *
66
+ * @param {Object} [opts]
67
+ * @param {boolean} [opts.reinstallHooks=true] - reinstall hooks at repo root after update
68
+ * @param {boolean} [opts.silent=false] - suppress sub-process output and progress chatter
69
+ * @returns {Promise<{updated:boolean, hooksReinstalled:boolean}>}
70
+ */
71
+ export async function performUpdate({ reinstallHooks = true, silent = false } = {}) {
72
+ const stdio = silent ? 'ignore' : 'inherit';
73
+
74
+ if (!silent) {
75
+ info('Updating claude-git-hooks...');
76
+ }
77
+ execSync(`npm install -g ${PACKAGE_NAME}@latest`, { stdio });
78
+
79
+ if (!reinstallHooks) {
80
+ return { updated: true, hooksReinstalled: false };
81
+ }
82
+
83
+ // Locate the repo root: this is the "look for root" step. getRepoRoot()
84
+ // throws (GitError) outside a git repo — treat that as "no root → warn & stop".
85
+ try {
86
+ const root = getRepoRoot();
87
+ logger.debug('auto-update - performUpdate', 'Repo root located for hook reinstall', { root });
88
+ } catch {
89
+ warning(
90
+ 'Package updated, but you are not inside a git repository — hooks were not reinstalled.\n' +
91
+ ' cd into your repo and run: claude-hooks install --force'
92
+ );
93
+ return { updated: true, hooksReinstalled: false };
94
+ }
95
+
96
+ if (!silent) {
97
+ info('Reinstalling hooks with the new version...');
98
+ }
99
+ // Dynamic import breaks the static cycle (install.js imports this module).
100
+ const { runInstall } = await import('../commands/install.js');
101
+ await runInstall(silent ? ['--force', '--headless'] : ['--force']);
102
+
103
+ return { updated: true, hooksReinstalled: true };
104
+ }
105
+
106
+ /**
107
+ * Whether enough time has elapsed since the last auto-update check.
108
+ * Throttle state lives in `<repoRoot>/.claude/.last-update-check` (.claude is gitignored).
109
+ * Fail-open: any FS/parse error returns true (allow the check).
110
+ *
111
+ * @param {number} [intervalHours=24]
112
+ * @returns {boolean}
113
+ */
114
+ export function shouldAutoCheck(intervalHours = DEFAULT_INTERVAL_HOURS) {
115
+ try {
116
+ const file = path.join(getRepoRoot(), '.claude', CHECK_FILE);
117
+ if (!fs.existsSync(file)) return true;
118
+
119
+ const last = Number(fs.readFileSync(file, 'utf8').trim());
120
+ if (!Number.isFinite(last)) return true;
121
+
122
+ const elapsedHours = (Date.now() - last) / (1000 * 60 * 60);
123
+ return elapsedHours >= intervalHours;
124
+ } catch {
125
+ return true;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Record the current time as the last auto-update check. Best-effort (fail-open).
131
+ */
132
+ export function recordCheckTimestamp() {
133
+ try {
134
+ const dir = path.join(getRepoRoot(), '.claude');
135
+ if (!fs.existsSync(dir)) return;
136
+ fs.writeFileSync(path.join(dir, CHECK_FILE), String(Date.now()), 'utf8');
137
+ } catch {
138
+ // best effort — throttling is an optimization, not a correctness requirement
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Pre-command auto-update guard. Runs SILENTLY before a command in bin/claude-hooks.
144
+ * Returns true when an update was performed (the running binary changed, so the
145
+ * caller should exit and ask the user to re-run their command).
146
+ *
147
+ * Fully fail-open: any error is swallowed (debug-logged) and returns false so the
148
+ * real command always proceeds.
149
+ *
150
+ * @param {string} command - the resolved command name (entry.name)
151
+ * @returns {Promise<boolean>}
152
+ */
153
+ export async function maybeAutoUpdate(command) {
154
+ // Defensive recursion guard (in-process runInstall does not re-enter the router,
155
+ // but this is cheap insurance against any future re-spawn).
156
+ if (process.env.CLAUDE_HOOKS_UPDATE_IN_PROGRESS) return false;
157
+ if (EXCLUDED_COMMANDS.has(command)) return false;
158
+
159
+ let autoUpdate = { enabled: true, intervalHours: DEFAULT_INTERVAL_HOURS };
160
+ try {
161
+ const config = await getConfig();
162
+ autoUpdate = { ...autoUpdate, ...(config.autoUpdate || {}) };
163
+ } catch {
164
+ // Config load failure → use defaults (enabled)
165
+ }
166
+ if (autoUpdate.enabled === false) return false;
167
+
168
+ // Must be inside a repo: throttle state + hook reinstall target live there.
169
+ try {
170
+ getRepoRoot();
171
+ } catch {
172
+ return false;
173
+ }
174
+
175
+ if (!shouldAutoCheck(autoUpdate.intervalHours)) return false;
176
+ // Throttle the next attempt regardless of network outcome (avoids offline spam).
177
+ recordCheckTimestamp();
178
+
179
+ try {
180
+ process.env.CLAUDE_HOOKS_UPDATE_IN_PROGRESS = '1';
181
+
182
+ const status = await getUpdateStatus();
183
+ if (!status.isNewer) return false;
184
+
185
+ await performUpdate({ reinstallHooks: true, silent: true });
186
+ success(
187
+ `⬆️ Updated claude-git-hooks to v${status.latestVersion} — please re-run your command.`
188
+ );
189
+ return true;
190
+ } catch (e) {
191
+ logger.debug('auto-update - maybeAutoUpdate', 'Auto-update check skipped (non-fatal)', {
192
+ error: e.message
193
+ });
194
+ return false;
195
+ } finally {
196
+ delete process.env.CLAUDE_HOOKS_UPDATE_IN_PROGRESS;
197
+ }
198
+ }
package/package.json CHANGED
@@ -1,84 +1,84 @@
1
- {
2
- "name": "claude-git-hooks",
3
- "version": "2.67.3",
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
- }
1
+ {
2
+ "name": "claude-git-hooks",
3
+ "version": "2.68.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
+ "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
+ }
@@ -61,6 +61,11 @@
61
61
  "failOnError": true,
62
62
  "failOnWarning": false,
63
63
  "timeout": 30000
64
+ },
65
+
66
+ "autoUpdate": {
67
+ "enabled": true,
68
+ "intervalHours": 24
64
69
  }
65
70
  },
66
71
 
@@ -168,6 +173,18 @@
168
173
  "description": "Timeout in milliseconds for each linter execution",
169
174
  "default": "30000",
170
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"
171
188
  }
172
189
  },
173
190