claude-git-hooks 2.34.0 → 2.35.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 CHANGED
@@ -5,6 +5,21 @@ 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.35.0] - 2026-03-27
9
+
10
+ ### ✨ Added
11
+ - Prettier formatting support — auto-formats JS/TS/CSS/HTML/JSON/YAML/MD files before linting (format first, then lint)
12
+ - Remote formatter configuration — preset-to-tools mapping fetched from centralized git-hooks-config repo, allowing team-wide control without releasing new versions
13
+ - New `parsePrettierOutput()` function for parsing Prettier --check output into structured issues
14
+
15
+ ### 🔧 Changed
16
+ - Linting pipeline now runs formatters (Prettier) before linters (ESLint) for consistent code style
17
+ - Updated preset-to-tools mapping: frontend, fullstack, ai, and default presets now include Prettier
18
+ - `getLinterToolsForPreset()` now fetches remote config with local fallback instead of using hardcoded mapping only
19
+ - `runLinters()` and `checkLinterAvailability()` converted to async functions to support remote config fetching
20
+ - Unfixable linting issues are now forwarded to the Claude judge for semantic resolution instead of blocking directly
21
+
22
+
8
23
  ## [2.34.0] - 2026-03-27
9
24
 
10
25
  ### ✨ Added
package/CLAUDE.md CHANGED
@@ -6,14 +6,14 @@
6
6
 
7
7
  **Main use cases:**
8
8
 
9
- 1. **Pre-commit linting**: Runs linters (ESLint, Spotless, sqlfluff) on staged files before Claude analysis — fast, deterministic, auto-fix enabled by default
9
+ 1. **Pre-commit linting**: Runs formatters and linters (Prettier, ESLint, Spotless, sqlfluff) on staged files before Claude analysis — fast, deterministic, auto-fix enabled by default
10
10
  2. **Pre-commit analysis**: Detects security issues, bugs, and code smells before each commit (blocks on CRITICAL/BLOCKER only)
11
11
  3. **Interactive analysis**: `claude-hooks analyze` - review all issues (INFO to BLOCKER) interactively before committing
12
12
  4. **Automatic messages**: Write `git commit -m "auto"` and Claude generates the message in Conventional Commits format with task-id extracted from branch
13
13
  5. **PR analysis**: `claude-hooks analyze-diff [branch]` generates title, description, and test plan for PRs
14
14
  6. **PR review**: `claude-hooks analyze-pr <url>` analyzes a GitHub PR with preset guidelines, Linear ticket enrichment, and posts review comments
15
15
  7. **PR creation**: `claude-hooks create-pr [branch]` creates the PR on GitHub with automatic metadata (reviewers from CODEOWNERS, labels by preset, merge strategy auto-detected from branch naming)
16
- 8. **Linting**: `claude-hooks lint [paths...]` runs linters on staged files, directories, or specific files — supports ESLint, Spotless, sqlfluff per preset
16
+ 8. **Linting**: `claude-hooks lint [paths...]` runs formatters and linters on staged files, directories, or specific files — supports Prettier, ESLint, Spotless, sqlfluff per preset (remote config priority with local fallback)
17
17
  9. **Coupling detection**: `claude-hooks check-coupling` scans open PRs targeting a base branch, computes file overlap, and reports which features are coupled (share modified files) — helps TL make informed decisions before cutting a release
18
18
  10. **Shadow management**: `claude-hooks shadow <analyze|reset|sync>` manages the shadow branch lifecycle — analyze divergence vs main and active RC, reset shadow to a clean copy of main, or sync shadow with a source branch (RC, develop, feature)
19
19
  11. **Release creation**: `claude-hooks create-release <major|minor|patch>` creates a release-candidate branch from develop, bumps version files, commits, pushes, and deploys to shadow — replaces the 8 manual steps executed every Tuesday by the Tech Lead
@@ -86,7 +86,7 @@ claude-git-hooks/
86
86
  │ │ └── prepare-commit-msg.js # Message generation - auto commit messages
87
87
  │ └── utils/ # Reusable modules - shared logic
88
88
  │ ├── tool-runner.js # Generic tool executor - resolve, spawn, parse, auto-fix (v2.34.0)
89
- │ ├── linter-runner.js # Linter orchestration - preset mapping, ESLint/Spotless/sqlfluff (v2.34.0)
89
+ │ ├── linter-runner.js # Linter orchestration - preset mapping, Prettier/ESLint/Spotless/sqlfluff, remote config (v2.34.0)
90
90
  │ ├── analysis-engine.js # Shared analysis logic - file data, 3-tier routing, results (v2.13.0+)
91
91
  │ ├── diff-analysis-orchestrator.js # Intelligent batch orchestration via Opus (v2.20.0)
92
92
  │ ├── claude-client.js # Claude CLI wrapper - spawn, retry, model override
@@ -183,6 +183,7 @@ preset config (.claude/presets/{name}/config.json) ← HIGHEST PRIORITY
183
183
  **Team-wide remote config** ([`mscope-S-L/git-hooks-config`](https://github.com/mscope-S-L/git-hooks-config)):
184
184
 
185
185
  - `labels.json` — PR label rules (fetched by `remote-config.js`, consumed by `label-resolver.js`)
186
+ - `formatters.json` — preset-to-tools mapping for linting/formatting (fetched by `remote-config.js`, consumed by `linter-runner.js`)
186
187
  - `permissions.json` — role-based authorization (fetched directly by `authorization.js`, fail-closed)
187
188
  - Changes take effect immediately across all governed repos — no tool update needed
188
189
 
@@ -319,7 +320,7 @@ consolidateResults()
319
320
  | `lib/cli-metadata.js` | Command registry | `commands`, `buildCommandMap()`, `generateCompletionData()`, `PRESET_NAMES`, `HOOK_NAMES`, `BUMP_TYPES` |
320
321
  | `lib/config.js` | Config system | `getConfig()` |
321
322
  | `tool-runner.js` | Generic tool executor | `isToolAvailable()`, `filterFilesByTool()`, `runTool()`, `runToolFix()`, `runToolWithAutoFix()`, `displayToolResult()` (v2.34.0) |
322
- | `linter-runner.js` | Linter orchestration | `runLinters()`, `displayLintResults()`, `checkLinterAvailability()`, `getLinterToolsForPreset()`, `LINTER_TOOLS`, `PRESET_LINTERS`, `parseEslintOutput()`, `parseSpotlessOutput()`, `parseSqlfluffOutput()`, `filesToSpotlessRegex()` (v2.34.0) |
323
+ | `linter-runner.js` | Linter orchestration | `runLinters()`, `displayLintResults()`, `checkLinterAvailability()`, `getLinterToolsForPreset()`, `LINTER_TOOLS`, `PRESET_LINTERS`, `parsePrettierOutput()`, `parseEslintOutput()`, `parseSpotlessOutput()`, `parseSqlfluffOutput()`, `filesToSpotlessRegex()` (v2.34.0) |
323
324
  | `analysis-engine.js` | Shared analysis logic | `buildFileData()`, `buildFilesData()`, `runAnalysis()`, `consolidateResults()`, `hasBlockingIssues()`, `hasAnyIssues()`, `displayResults()`, `displayIssueSummary()` (v2.13.0+) |
324
325
  | `diff-analysis-orchestrator.js` | Intelligent batch orchestration | `orchestrateBatches()`, `buildFileOverview()`, `detectDependencies()` (v2.20.0) |
325
326
  | `claude-client.js` | Claude CLI wrapper | `analyzeCode()`, `executeClaudeWithRetry()`, `extractJSON()` — spawn, retry, model override |
@@ -359,8 +360,8 @@ consolidateResults()
359
360
  7. **Adapter Pattern**: `git-operations.js` abstracts git commands into JS functions
360
361
  8. **Singleton Pattern**: `config.js` loads configuration once per execution
361
362
  9. **Guard Pattern**: `authorization.js` — fail-closed gate in `bin/claude-hooks` before command dispatch; static `PROTECTED_COMMANDS` set avoids API calls for unprotected commands; permissions sourced from `mscope-S-L/git-hooks-config/permissions.json`
362
- 10. **Remote Config Pattern**: `remote-config.js` — fetches JSON from `mscope-S-L/git-hooks-config`, caches per-process (including nulls), graceful degradation (warn + return null); `label-resolver.js` — callers receive config via dependency injection and decide fallback
363
- 11. **Pipeline Pattern**: `tool-runner.js` + `linter-runner.js` — generic tool execution infrastructure; linters (and future formatters) share the same resolve → spawn → parse → fix → re-stage pipeline. Tool definitions are data objects, not classes.
363
+ 10. **Remote Config Pattern**: `remote-config.js` — fetches JSON from `mscope-S-L/git-hooks-config`, caches per-process (including nulls), graceful degradation (warn + return null); `label-resolver.js` and `linter-runner.js` — callers fetch remote config and decide fallback (`labels.json` for PR labels, `formatters.json` for preset-to-tools mapping)
364
+ 11. **Pipeline Pattern**: `tool-runner.js` + `linter-runner.js` — generic tool execution infrastructure; formatters (Prettier) and linters (ESLint, Spotless, sqlfluff) share the same resolve → spawn → parse → fix → re-stage pipeline. Tool definitions are data objects, not classes. Preset-to-tools mapping fetched from `mscope-S-L/git-hooks-config/formatters.json` (remote config priority, local fallback).
364
365
 
365
366
  ### Key Data Flows
366
367
 
@@ -372,13 +373,14 @@ git commit
372
373
  → filters by preset extensions + size
373
374
 
374
375
  → LINTING STEP (fast, deterministic)
375
- → getLinterToolsForPreset(presetName) → applicable linters
376
- for each linter:
376
+ → getLinterToolsForPreset(presetName):
377
+ 1. fetchRemoteConfig('formatters.json') remote presetTools mapping
378
+ 2. fallback to local PRESET_LINTERS if remote unavailable
379
+ → for each tool (formatters first, then linters):
377
380
  isToolAvailable() → not found? warn + install hint → skip
378
381
  filterFilesByTool() → matching files
379
382
  runToolWithAutoFix() → check → auto-fix → re-stage → re-check
380
- if failOnError && errors remain → exit 1 (COMMIT BLOCKED)
381
- → if failOnWarning && warnings → exit 1 (COMMIT BLOCKED)
383
+ unfixable issues forwarded to judge
382
384
 
383
385
  → continues to Claude analysis
384
386
  ```
package/README.md CHANGED
@@ -82,6 +82,43 @@ export GITHUB_TOKEN="ghp_..."
82
82
 
83
83
  Create token at https://github.com/settings/tokens with scopes: `repo`, `read:org`
84
84
 
85
+ ### Linting & Formatting
86
+
87
+ Runs formatters and linters on staged files automatically during pre-commit, or on demand:
88
+
89
+ ```bash
90
+ # Lint staged files (default)
91
+ claude-hooks lint
92
+
93
+ # Lint all files in a directory
94
+ claude-hooks lint src/
95
+
96
+ # Lint specific files
97
+ claude-hooks lint file1.js file2.java
98
+
99
+ # Mix of directories and files
100
+ claude-hooks lint src/ lib/utils/ file.js
101
+ ```
102
+
103
+ **Tools per preset** (configured via [remote config](https://github.com/mscope-S-L/git-hooks-config)):
104
+
105
+ | Preset | Tools |
106
+ | ----------- | ----------------------------- |
107
+ | `frontend` | Prettier, ESLint |
108
+ | `backend` | Spotless |
109
+ | `fullstack` | Prettier, ESLint, Spotless |
110
+ | `database` | sqlfluff |
111
+ | `ai` | Prettier, ESLint |
112
+ | `default` | Prettier, ESLint |
113
+
114
+ **Behavior:**
115
+
116
+ - Formatters run first (Prettier), then linters (ESLint) — format before lint
117
+ - Auto-fix enabled by default — fixes and re-stages files automatically
118
+ - Missing tools are skipped with install instructions (never blocks)
119
+ - Unfixable issues are forwarded to the Claude judge for semantic resolution
120
+ - Tool-to-preset mapping is fetched from remote config (team-controlled, no release needed)
121
+
85
122
  ### Analyze Code (Interactive Review)
86
123
 
87
124
  Run interactive code analysis before committing:
@@ -673,7 +673,7 @@ export async function runInstall(args) {
673
673
  const presetName = config.preset || 'default';
674
674
  if (config.linting?.enabled !== false) {
675
675
  const { checkLinterAvailability } = await import('../utils/linter-runner.js');
676
- checkLinterAvailability(presetName);
676
+ await checkLinterAvailability(presetName);
677
677
  }
678
678
  } catch {
679
679
  // Non-fatal — linter check failure should not block installation
@@ -170,7 +170,7 @@ export async function runLint(args = []) {
170
170
 
171
171
  info(`🎯 Linting ${filesToLint.length} file(s) with '${metadata.displayName}' preset`);
172
172
 
173
- const lintResult = runLinters(filesToLint, config, presetName);
173
+ const lintResult = await runLinters(filesToLint, config, presetName);
174
174
  displayLintResults(lintResult);
175
175
 
176
176
  // Exit with error code if linting failed
@@ -152,7 +152,7 @@ const main = async () => {
152
152
  logger.info('🔍 Running linters...');
153
153
  const lintStartTime = Date.now();
154
154
  const filePaths = validFiles.map((f) => (typeof f === 'string' ? f : f.path));
155
- const lintResult = runLinters(filePaths, config, presetName);
155
+ const lintResult = await runLinters(filePaths, config, presetName);
156
156
  displayLintResults(lintResult);
157
157
 
158
158
  // Record lint metric
@@ -23,6 +23,7 @@ import {
23
23
  displayToolResult
24
24
  } from './tool-runner.js';
25
25
  import { getRepoRoot } from './git-operations.js';
26
+ import { fetchRemoteConfig } from './remote-config.js';
26
27
  import logger from './logger.js';
27
28
 
28
29
  /**
@@ -157,6 +158,46 @@ export function parseSqlfluffOutput(stdout) {
157
158
  return { errors, warnings };
158
159
  }
159
160
 
161
+ /**
162
+ * Parse Prettier --check output into structured issues
163
+ * Prettier outputs [warn] lines for files that need formatting
164
+ *
165
+ * @param {string} stdout - Prettier --check stdout
166
+ * @returns {{ errors: Array, warnings: Array }}
167
+ */
168
+ export function parsePrettierOutput(stdout) {
169
+ const errors = [];
170
+ const warnings = [];
171
+
172
+ const lines = stdout.split('\n');
173
+
174
+ for (const line of lines) {
175
+ const trimmed = line.trim();
176
+
177
+ // Match [warn] <filepath> lines — skip summary lines
178
+ const match = trimmed.match(/^\[warn\]\s+(.+)$/);
179
+ if (!match) continue;
180
+
181
+ const content = match[1];
182
+
183
+ // Skip Prettier's summary lines (not file paths)
184
+ if (
185
+ content.includes('Code style issues') ||
186
+ content.includes('Forgot to run Prettier')
187
+ ) {
188
+ continue;
189
+ }
190
+
191
+ errors.push({
192
+ file: content,
193
+ severity: 'error',
194
+ message: 'File is not properly formatted'
195
+ });
196
+ }
197
+
198
+ return { errors, warnings };
199
+ }
200
+
160
201
  /**
161
202
  * Convert file paths to Spotless-compatible regex pattern
162
203
  * Spotless -DspotlessFiles accepts a regex matching absolute file paths
@@ -176,6 +217,25 @@ export function filesToSpotlessRegex(files) {
176
217
  * @type {Object<string, import('./tool-runner.js').ToolDefinition>}
177
218
  */
178
219
  export const LINTER_TOOLS = {
220
+ prettier: {
221
+ name: 'prettier',
222
+ command: 'npx',
223
+ args: (files) => ['prettier', '--check', ...files],
224
+ fixArgs: (files) => ['prettier', '--write', ...files],
225
+ detectCommand: 'prettier',
226
+ detectInProjectFile: {
227
+ filename: 'package.json',
228
+ check: (content) => {
229
+ const pkg = JSON.parse(content);
230
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
231
+ return 'prettier' in allDeps;
232
+ }
233
+ },
234
+ installHint: 'npm install --save-dev prettier',
235
+ extensions: ['.js', '.jsx', '.ts', '.tsx', '.css', '.scss', '.html', '.json', '.md', '.yaml', '.yml'],
236
+ parseOutput: parsePrettierOutput,
237
+ timeout: 30000
238
+ },
179
239
  eslint: {
180
240
  name: 'eslint',
181
241
  command: 'npx',
@@ -228,27 +288,56 @@ export const LINTER_TOOLS = {
228
288
  };
229
289
 
230
290
  /**
231
- * Preset-to-linter mapping
232
- * Each preset lists the linter tool names it uses
291
+ * Local fallback preset-to-tool mapping
292
+ * Used when remote formatters.json is unavailable
233
293
  * @type {Object<string, string[]>}
234
294
  */
235
295
  export const PRESET_LINTERS = {
236
- frontend: ['eslint'],
296
+ frontend: ['prettier', 'eslint'],
237
297
  backend: ['spotless'],
238
- fullstack: ['eslint', 'spotless'],
298
+ fullstack: ['prettier', 'eslint', 'spotless'],
239
299
  database: ['sqlfluff'],
240
- ai: ['eslint'],
241
- default: ['eslint']
300
+ ai: ['prettier', 'eslint'],
301
+ default: ['prettier', 'eslint']
242
302
  };
243
303
 
244
304
  /**
245
- * Get linter tool definitions for a preset
305
+ * Get linter/formatter tool definitions for a preset.
306
+ * Fetches preset-to-tools mapping from remote config (formatters.json),
307
+ * falls back to local PRESET_LINTERS when remote is unavailable.
246
308
  *
247
309
  * @param {string} presetName - Preset name
248
- * @returns {import('./tool-runner.js').ToolDefinition[]} Tool definitions
310
+ * @returns {Promise<import('./tool-runner.js').ToolDefinition[]>} Tool definitions
249
311
  */
250
- export function getLinterToolsForPreset(presetName) {
251
- const toolNames = PRESET_LINTERS[presetName] || PRESET_LINTERS.default;
312
+ export async function getLinterToolsForPreset(presetName) {
313
+ // Try remote config first
314
+ const remoteConfig = await fetchRemoteConfig('formatters.json');
315
+
316
+ let toolNames;
317
+
318
+ if (remoteConfig?.presetTools?.[presetName]) {
319
+ toolNames = remoteConfig.presetTools[presetName];
320
+ logger.debug('linter-runner - getLinterToolsForPreset', 'Using remote preset tools', {
321
+ preset: presetName,
322
+ tools: toolNames
323
+ });
324
+ } else if (remoteConfig?.presetTools?.default) {
325
+ toolNames = remoteConfig.presetTools.default;
326
+ logger.debug('linter-runner - getLinterToolsForPreset',
327
+ 'Preset not in remote config, using remote default', {
328
+ preset: presetName,
329
+ tools: toolNames
330
+ });
331
+ } else {
332
+ // Fall back to local
333
+ toolNames = PRESET_LINTERS[presetName] || PRESET_LINTERS.default;
334
+ logger.debug('linter-runner - getLinterToolsForPreset', 'Using local fallback', {
335
+ preset: presetName,
336
+ tools: toolNames,
337
+ remoteAvailable: remoteConfig !== null
338
+ });
339
+ }
340
+
252
341
  return toolNames.map((name) => LINTER_TOOLS[name]).filter(Boolean);
253
342
  }
254
343
 
@@ -260,12 +349,12 @@ export function getLinterToolsForPreset(presetName) {
260
349
  * @param {string} [presetName] - Preset name (default: 'default')
261
350
  * @returns {{ results: ToolRunResult[], totalErrors: number, totalWarnings: number, totalFixed: number }}
262
351
  */
263
- export function runLinters(files, config, presetName = 'default') {
352
+ export async function runLinters(files, config, presetName = 'default') {
264
353
  const lintConfig = config.linting || {};
265
354
  const autoFix = lintConfig.autoFix !== false;
266
355
  const timeout = lintConfig.timeout || 30000;
267
356
 
268
- const tools = getLinterToolsForPreset(presetName);
357
+ const tools = await getLinterToolsForPreset(presetName);
269
358
  const results = [];
270
359
 
271
360
  logger.debug('linter-runner - runLinters', 'Starting linters', {
@@ -415,8 +504,8 @@ export function lintIssuesToAnalysisDetails(lintResult) {
415
504
  *
416
505
  * @param {string} presetName - Preset name
417
506
  */
418
- export function checkLinterAvailability(presetName) {
419
- const tools = getLinterToolsForPreset(presetName);
507
+ export async function checkLinterAvailability(presetName) {
508
+ const tools = await getLinterToolsForPreset(presetName);
420
509
 
421
510
  if (tools.length === 0) {
422
511
  return;
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "claude-git-hooks",
3
- "version": "2.34.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.1",
66
- "jest": "^29.7.0",
67
- "prettier": "^3.2.0"
68
- }
69
- }
1
+ {
2
+ "name": "claude-git-hooks",
3
+ "version": "2.35.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.1",
66
+ "jest": "^29.7.0",
67
+ "prettier": "^3.2.0"
68
+ }
69
+ }