Skip to content

feat: add pre-commit hooks for cycles, dead exports, signature warnings#381

Merged
carlos-alm merged 7 commits intomainfrom
chore/pre-commit-hooks
Mar 9, 2026
Merged

feat: add pre-commit hooks for cycles, dead exports, signature warnings#381
carlos-alm merged 7 commits intomainfrom
chore/pre-commit-hooks

Conversation

@carlos-alm
Copy link
Contributor

Summary

  • Add check-commit.sh: combined cycle detection (blocking) + signature change warning (informational) in a single checkData() call — no duplicate predicate runs
  • Add check-dead-exports.sh: blocks commits when session-edited src/ files have zero-consumer exports — batches all files in one Node.js process
  • Add guard-pr-body.sh: blocks PR creation with "generated with" in body
  • Register all hooks in .claude/settings.json with appropriate timeouts
  • Document hooks in docs/guides/recommended-practices.md

Performance (vs PR #376)

Metric PR #376 design This PR
checkData() calls per commit 2 (cycles + signatures separately) 1 (combined)
DB opens for signature enrichment 2 (checkData + enrichment) 1 (enrichment only, checkData already closed)
Dead export check N child processes (one per file) 1 process (batched)
Estimated hook overhead ~60s ~20s

Boundaries predicate is skipped during commit hooks (rarely needed, expensive).

Splits from #376

Extracted non-hook changes to focused PRs:

  • fix/flow-kind-regression — CORE_SYMBOL_KINDS in flow matching
  • refactor/sequence-try-finally — try/finally in sequenceData

Test plan

  • Verify check-commit.sh blocks on cycles involving session-edited files
  • Verify check-commit.sh warns (non-blocking) on signature changes with risk levels
  • Verify check-dead-exports.sh blocks on zero-consumer exports
  • Verify guard-pr-body.sh blocks PRs with "generated with"
  • Verify hooks pass cleanly on a no-op commit
  • Time total hook overhead on a typical commit

…gs, and PR body guard

Add four Claude Code hooks that run on git commit:

- check-commit.sh: combined cycle detection (blocking) + signature
  change warning (informational). Runs checkData() once with both
  predicates in a single Node.js invocation, skipping boundaries
  for speed. Cycles only block if they involve session-edited files.
  Signature warnings are enriched with role-based risk levels and
  transitive caller counts via hoisted db.prepare() statements.

- check-dead-exports.sh: blocks commits when staged src/ files
  (edited in this session) contain exports with zero consumers.
  Batches all files in one Node.js process with a single DB open.

- guard-pr-body.sh: blocks PR creation if the body contains
  "generated with" (case-insensitive).

Performance: previous design ran checkData() twice (once for cycles,
once for signatures) and spawned per-file CLI processes for dead
exports. This version cuts commit hook overhead from ~60s to ~20s
by combining predicates and batching DB access.
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Pre-commit Hooks Implementation

Todo List:

  • Read and understand all changed files
  • Analyze root cause and problem being solved
  • Review hook implementations for security and correctness
  • Validate performance claims
  • Check configuration and documentation changes
  • Provide final assessment and rating

Configuration and Documentation Review

Hook registration: All three hooks properly added to .claude/settings.json with appropriate timeouts:

  • guard-pr-body.sh: 10s (simple string matching)
  • check-commit.sh: 20s (single checkData call)
  • check-dead-exports.sh: 30s (batch file processing)

Documentation updated: docs/guides/recommended-practices.md includes all three hooks with clear descriptions (lines 355-357)

Timeout ordering: Hooks are ordered by execution time, with the most expensive (check-dead-exports.sh) running last

Root Cause Analysis

Problem Being Solved: Adding pre-commit validation to catch circular dependencies, dead exports, and improper PR descriptions before they enter the repository.

Why This Approach: Leverages codegraph's existing validation predicates with performance optimizations. The session-scoped validation ensures hooks only check files the AI agent actually modified.

Risk Assessment: LOW - Defensive implementation with proper error handling and guard clauses.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Well-implemented hooks with legitimate performance improvements. Code follows established patterns, includes proper security measures, and addresses a real need for commit-time validation. The performance optimizations are technically sound and well-documented.

Minor suggestions: Consider adding examples to the hook documentation showing what the error messages look like, but this doesn't block approval.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR introduces three new Claude Code PreToolUse hooks (check-commit.sh, check-dead-exports.sh, guard-pr-body.sh) that enforce code-quality invariants at commit and PR-creation time, registers them in .claude/settings.json, and documents them in the recommended-practices guide. A minor src/cli.js clarification is also included.

Key changes:

  • check-commit.sh: runs checkData() once for both cycle detection (blocking) and signature-change warnings (non-blocking), with BFS enrichment for transitive caller counts. Cycle check is correctly scoped to session-edited files via the edit log.
  • check-dead-exports.sh: batch-checks all staged session-edited src/ files for zero-consumer exports in a single Node.js process, blocking the commit if any are found.
  • guard-pr-body.sh: blocks gh pr create when "generated with" appears in either the inline --body or a --body-file file, using structured JSON deny output consistent with the other hooks.
  • The signature warning in check-commit.sh is not scoped to session-edited files (unlike the cycle check), which may produce noisy warnings after merges involving unrelated changes. Additionally, signature warnings are silently dropped when a cycle block fires in the same run.

Confidence Score: 3/5

  • Safe to merge for the hooks infrastructure, but two logic gaps in check-commit.sh and one in guard-pr-body.sh should be addressed before the hooks are fully reliable.
  • The hooks are well-structured, follow consistent JSON deny patterns, and handle the most common cases correctly. However, the signature warning in check-commit.sh is not scoped to session-edited files (creating potential false-positive noise after merges), signature data is silently lost when a cycle block fires in the same run, and the --body-file path extraction regex in guard-pr-body.sh silently fails on quoted paths with spaces — undermining the very bypass protection that was added in response to a prior review comment.
  • .claude/hooks/check-commit.sh (signature scoping + warn-on-deny) and .claude/hooks/guard-pr-body.sh (body-file path extraction)

Important Files Changed

Filename Overview
.claude/hooks/check-commit.sh New PreToolUse hook that runs checkData() once for both cycle detection (blocking) and signature change warnings (non-blocking); cycle check is correctly scoped to session-edited files but the signature warning is not, and signature warnings are silently dropped when a cycle block also fires.
.claude/hooks/check-dead-exports.sh New PreToolUse hook that batch-checks staged session-edited src/ files for zero-consumer exports in a single Node.js process; glob pattern was previously fixed to use a regex filter instead of shell globs, and the redundant consumerCount === 0 filter was removed.
.claude/hooks/guard-pr-body.sh New PreToolUse hook blocking PR creation when "generated with" appears in the PR body; correctly handles the --body-file bypass via grep, but the \S+ regex silently truncates quoted paths with spaces, potentially leaving the file-body check unenforced.
.claude/settings.json Registers the three new hooks (guard-pr-body.sh, check-commit.sh, check-dead-exports.sh) under the PreToolUse/Bash matcher with appropriate 10 s and 30 s timeouts.
docs/guides/recommended-practices.md Documents the three new hooks inline and in the example list; risk-level capitalisation now matches the code (HIGH/MEDIUM/LOW) and the DB-connection description was corrected to "single Node.js process".
src/cli.js Minor clarification of --unused flag description and adds a defensive `

Sequence Diagram

sequenceDiagram
    participant CC as Claude Code
    participant GPB as guard-pr-body.sh
    participant CHK as check-commit.sh
    participant CDE as check-dead-exports.sh
    participant DB as graph.db
    participant GL as session-edits.log

    Note over CC,GL: git commit triggered
    CC->>CHK: PreToolUse (Bash: git commit)
    CHK->>GL: read edited files
    CHK->>DB: checkData(staged, cycles+signatures)
    DB-->>CHK: predicates result
    alt cycles involving edited files
        CHK-->>CC: deny (BLOCKED: cycles)
    else signature violations
        CHK->>DB: openReadonlyOrFail() + BFS callers
        DB-->>CHK: role, transitive callerCount
        CHK-->>CC: allow + additionalContext (sig warning)
    else clean
        CHK-->>CC: exit 0
    end

    CC->>CDE: PreToolUse (Bash: git commit)
    CDE->>GL: read edited files
    CDE->>DB: exportsData(file, unused:true) per file
    DB-->>CDE: zero-consumer exports
    alt dead exports found
        CDE-->>CC: deny (BLOCKED: dead exports)
    else clean
        CDE-->>CC: exit 0
    end

    Note over CC,GL: gh pr create triggered
    CC->>GPB: PreToolUse (Bash: gh pr create)
    GPB->>GPB: grep inline body for "generated with"
    GPB->>GPB: read --body-file if present
    alt "generated with" found
        GPB-->>CC: deny (BLOCKED: remove generated-with line)
    else clean
        GPB-->>CC: exit 0
    end
Loading

Last reviewed commit: 9c1cb2a

Comment on lines +51 to +54
case "$file" in
src/*.js|src/*.ts|src/*.tsx) ;;
*) continue ;;
esac
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glob pattern silently skips subdirectories

The case pattern src/*.js|src/*.ts|src/*.tsx only matches files directly under src/ — the shell * glob does not cross directory boundaries. Files in subdirectories (e.g., src/extractors/csharp.js, src/extractors/javascript.js, and the 9 other files under src/extractors/) will silently pass through the filter and never be checked for dead exports.

This means the dead-export check provides incomplete coverage for a significant portion of the codebase today (the entire src/extractors/ subtree), and will silently miss new subdirectories added in the future.

To match nested paths you need a double-glob via extglob or a different approach:

# Option A — use a regex match instead of case/glob
case "$file" in
  src/*.js|src/**/*.js|src/*.ts|src/**/*.ts|src/*.tsx|src/**/*.tsx) ;;
  *) continue ;;
esac

Note: src/**/*.js requires shopt -s globstar to be enabled. A safer, portable alternative is a grep filter:

# Option B — portable substring match
if ! echo "$file" | grep -qE '^src/.*\.(js|ts|tsx)$'; then
  continue
fi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — replaced the \ glob with a \ filter so files in subdirectories (e.g. ) are now matched correctly.

}
}

const risk = role === 'core' ? 'HIGH' : role === 'utility' ? 'MEDIUM' : 'low';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent risk-level capitalisation

HIGH and MEDIUM are uppercased while low is lowercase. If any downstream code or display logic ever parses or compares these risk strings (e.g. to colour-code output, filter by severity, or sort), the mixed casing will cause silent mismatches.

Suggested change
const risk = role === 'core' ? 'HIGH' : role === 'utility' ? 'MEDIUM' : 'low';
const risk = role === 'core' ? 'HIGH' : role === 'utility' ? 'MEDIUM' : 'LOW';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed \ to \ for consistent risk-level capitalisation.

Comment on lines +21 to +23
if echo "$cmd" | grep -qi 'generated with'; then
echo "BLOCK: Remove any 'Generated with ...' line from the PR body." >&2
exit 2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hook uses a different blocking mechanism than the other two new hooks

check-commit.sh and check-dead-exports.sh both output a structured JSON payload with permissionDecision: 'deny' and exit 0. This hook instead writes to stderr and exits with code 2.

The two mechanisms are valid, but the inconsistency means error messages will surface differently in the Claude Code UI. More importantly, if set -euo pipefail is in effect in a parent shell and the exit code is not explicitly handled, unexpected propagation could occur.

Consider also adding the missing exit 0 at the end of the "allowed" path for symmetry with the other hooks, and/or adopting the structured JSON format used by check-commit.sh for a consistent user experience.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — replaced stderr + exit 2 with the structured JSON \ + exit 0 pattern used by the other hooks, and added an explicit \ at the end of the allowed path.

Comment on lines +90 to +126
const db = openReadonlyOrFail();
const stmtNode = db.prepare(
'SELECT id, role FROM nodes WHERE name = ? AND file = ? AND line = ?'
);
const stmtCallers = db.prepare(
'SELECT DISTINCT n.id FROM edges e JOIN nodes n ON e.source_id = n.id WHERE e.target_id = ? AND e.kind = \\'calls\\''
);

const lines = [];
for (const v of sigPred.violations) {
const node = stmtNode.get(v.name, v.file, v.line);
const role = node?.role || 'unknown';

let callerCount = 0;
if (node) {
const visited = new Set([node.id]);
let frontier = [node.id];
for (let d = 0; d < 3; d++) {
const next = [];
for (const fid of frontier) {
for (const c of stmtCallers.all(fid)) {
if (!visited.has(c.id)) {
visited.add(c.id);
next.push(c.id);
callerCount++;
}
}
}
frontier = next;
if (!frontier.length) break;
}
}

const risk = role === 'core' ? 'HIGH' : role === 'utility' ? 'MEDIUM' : 'low';
lines.push(risk + ': ' + v.name + ' (' + v.kind + ') [' + role + '] at ' + v.file + ':' + v.line + ' — ' + callerCount + ' transitive callers');
}
db.close();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DB left open if an exception is thrown during signature enrichment

openReadonlyOrFail() is called at line 90 and db.close() is at line 126, but there is no try/finally (or equivalent) around the violation-processing loop. If any call — e.g., stmtCallers.all(fid) — throws unexpectedly, db.close() is never reached.

For a short-lived hook process this is largely harmless (the OS will clean up), but SQLite better-sqlite3 connections hold a file lock; leaving one open during tests or in parallel hook scenarios could cause SQLITE_BUSY errors.

Wrap the enrichment block:

const db = openReadonlyOrFail();
try {
  // ... prepare statements, loop over violations ...
} finally {
  db.close();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — wrapped the DB enrichment block in try/finally so db.close() is always reached even if an exception is thrown during the BFS loop.

Comment on lines +28 to +31
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/check-commit.sh\"",
"timeout": 20
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check-commit.sh timeout (20 s) may be too tight for large graphs

The hook runs checkData() (cycle detection over all staged files) plus an optional BFS up to depth-3 over transitive callers for every signature violation, all in a single Node.js process. On a graph with tens of thousands of nodes/edges the enrichment loop alone can exceed 20 seconds.

When a PreToolUse hook times out, Claude Code silently allows the operation, so a timeout doesn't cause false blocks — but it does silently skip the cycle/signature checks. Consider bumping the timeout to align with the 30 s already used by check-dead-exports.sh, or at least document the intentional trade-off.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — bumped check-commit.sh timeout from 20s to 30s to match check-dead-exports.sh and avoid silent skips on larger graphs.

- check-dead-exports.sh: use grep filter instead of case glob to match
  files in subdirectories (e.g. src/extractors/*.js)
- check-commit.sh: uppercase 'LOW' for consistent risk-level casing
- check-commit.sh: wrap DB enrichment block in try/finally to prevent
  leaked SQLite connections on unexpected errors
- guard-pr-body.sh: use structured JSON deny + exit 0 consistent with
  the other hooks instead of stderr + exit 2
- settings.json: bump check-commit.sh timeout from 20s to 30s to match
  check-dead-exports and avoid silent skips on larger graphs
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Pre-commit Hooks Implementation

Todo List:

  • Read and understand all changed files
  • Analyze root cause and problem being solved
  • Review hook implementations for security and correctness
  • Validate performance claims
  • Check configuration and documentation changes
  • Provide final assessment and rating

Root Cause Analysis

Problem Being Solved: Adding pre-commit validation to catch circular dependencies, dead exports, and improper PR descriptions before they enter the repository.

Why This Approach: Leverages codegraph's existing validation predicates with performance optimizations. The session-scoped validation ensures hooks only check files the AI agent actually modified.

Risk Assessment: LOW - Defensive implementation with proper error handling and guard clauses.

Hook Implementation Review

Fixed Issues from Previous Review:
Dead exports glob pattern: Fixed the critical bug - now uses grep -qE '^src/.*\.(js|ts|tsx)$' correctly matching subdirectories
DB connection management: Try/finally blocks properly implemented (lines 92-129 in check-commit.sh)

Remaining Issues:
🔴 check-commit.sh risk level casing inconsistency: Line 124 has mixed casing: 'HIGH', 'MEDIUM', 'LOW' instead of consistent casing. This could cause silent failures in downstream parsing.

Security Analysis:
✅ All hooks use set -euo pipefail for safe execution
✅ JSON parsing includes proper error handling with try/catch
✅ No command injection vulnerabilities detected
✅ Proper escaping of user input in JSON output
✅ DB connections properly closed in try/finally blocks

Performance Claims Validation

Single checkData() call: ✅ Confirmed - Line 58-62 in check-commit.sh runs both cycles and signatures predicates in one call instead of two separate calls.

Batched dead export checks: ✅ Confirmed - Lines 65-89 in check-dead-exports.sh processes all files in a single Node.js invocation instead of spawning separate processes.

Performance improvement estimates appear valid based on the architectural changes.

Configuration and Documentation Review

Hook registration: All three hooks properly added to .claude/settings.json with appropriate timeouts:

  • guard-pr-body.sh: 10s (simple string matching)
  • check-commit.sh: 30s (single checkData call)
  • check-dead-exports.sh: 30s (batch file processing)

Documentation updated: docs/guides/recommended-practices.md includes all three hooks with clear descriptions (lines 355-357)

CLI improvement: Minor enhancement in src/cli.js clarifying --unused flag description and normalizing the value to false

Timeout adjustments: The timeout for check-commit.sh was increased from 20s to 30s, addressing Greptile's concern about timeouts on large graphs

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE WITH CONDITIONS
  • Reasoning: Well-implemented hooks with legitimate performance improvements. Code follows established patterns, includes proper security measures, and addresses a real need for commit-time validation. The performance optimizations are technically sound and well-documented.

Condition: Fix the risk level casing inconsistency in check-commit.sh line 124 ('LOW' instead of 'low') to ensure consistent downstream parsing.

Minor suggestions: Consider adding examples to the hook documentation showing what the error messages look like, but this doesn't block approval.

@carlos-alm
Copy link
Contributor Author

@greptileai

done <<< "$STAGED"

if [ -z "$FILES_TO_CHECK" ]; then
exit 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"One DB connection" claim is inaccurate.

The comment states "check all files with one DB connection", but exportsData() in src/queries.js opens and closes its own better-sqlite3 connection on every invocation:

export function exportsData(file, customDbPath, opts = {}) {
  const db = openReadonlyOrFail(customDbPath);   // opens
  // ...
  db.close();                                     // closes
}

Because the hook loops over files and calls exportsData once per file, there will be N separate DB open/close cycles, not one. The batching into a single Node.js process is a real win (avoiding N child-process spawns), but the DB-connection count claim is misleading.

Consider updating the comment to say "single Node.js process" (accurate) instead of "one DB connection":

Suggested change
exit 0
# Single Node.js invocation: check all files (multiple DB opens per file)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b3f6f41 — changed comment to "in one process" (accurate) instead of "one DB connection" (misleading). The real benefit is batching into a single Node.js process, not sharing a DB connection.


> **Windows note:** If your hooks use bash scripts, normalize backslashes inside `node -e` rather than bash (`${VAR//\\//}` fails on Git Bash). See this repo's `.claude/hooks/enrich-context.sh` for the pattern.

**Commit check hook** (PreToolUse on Bash): when Claude runs `git commit`, the hook runs `checkData()` once with cycles + signatures predicates enabled (boundaries skipped for speed). If circular dependencies involve files edited in this session, blocks the commit. If function signatures were modified, injects a risk-rated warning via `additionalContext` — `HIGH` for core symbols, `MEDIUM` for utility, `low` for others — with transitive caller counts. Non-blocking for signatures, blocking for cycles.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk-level capitalisation inconsistency.

The documentation here says low (lowercase), but the code in check-commit.sh now emits 'LOW' (uppercase):

const risk = role === 'core' ? 'HIGH' : role === 'utility' ? 'MEDIUM' : 'LOW';

To avoid confusing readers who might copy-paste these strings for filtering or display logic, update the docs to match the code:

Suggested change
**Commit check hook** (PreToolUse on Bash): when Claude runs `git commit`, the hook runs `checkData()` once with cycles + signatures predicates enabled (boundaries skipped for speed). If circular dependencies involve files edited in this session, blocks the commit. If function signatures were modified, injects a risk-rated warning via `additionalContext``HIGH` for core symbols, `MEDIUM` for utility, `low` for others — with transitive caller counts. Non-blocking for signatures, blocking for cycles.
**Commit check hook** (PreToolUse on Bash): when Claude runs `git commit`, the hook runs `checkData()` once with cycles + signatures predicates enabled (boundaries skipped for speed). If circular dependencies involve files edited in this session, blocks the commit. If function signatures were modified, injects a risk-rated warning via `additionalContext``HIGH` for core symbols, `MEDIUM` for utility, `LOW` for others — with transitive caller counts. Non-blocking for signatures, blocking for cycles.

Also update the Dead export check hook documentation at line 344 to fix the misleading "one DB connection" claim:

Suggested change
**Commit check hook** (PreToolUse on Bash): when Claude runs `git commit`, the hook runs `checkData()` once with cycles + signatures predicates enabled (boundaries skipped for speed). If circular dependencies involve files edited in this session, blocks the commit. If function signatures were modified, injects a risk-rated warning via `additionalContext``HIGH` for core symbols, `MEDIUM` for utility, `low` for others — with transitive caller counts. Non-blocking for signatures, blocking for cycles.
**Dead export check hook** (PreToolUse on Bash): when Claude runs `git commit`, the hook batch-checks all staged `src/` files edited in this session for exports with zero consumers. Batches all files in a single Node.js process for efficiency (not per-file CLI calls). If any export has zero consumers, blocks the commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b3f6f41 — updated docs to use LOW (uppercase) matching the code, and corrected the "one DB connection" claim to "single Node.js process".

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Pre-commit Hooks Implementation

Todo List:

  • Read and understand all changed files
  • Analyze root cause and problem being solved
  • Review hook implementations for security and correctness
  • Validate performance claims
  • Check configuration and documentation changes
  • Provide final assessment and rating

Root Cause Analysis

Problem Being Solved: Adding pre-commit validation to catch circular dependencies, dead exports, and improper PR descriptions before they enter the repository.

Why This Approach: Leverages codegraph's existing validation predicates with performance optimizations. The session-scoped validation ensures hooks only check files the AI agent actually modified.

Risk Assessment: LOW - Defensive implementation with proper error handling and guard clauses.

Hook Implementation Review

Fixed Issues from Previous Review:
Dead exports glob pattern: Fixed the critical bug - now uses grep -qE '^src/.*\.(js|ts|tsx)$' correctly matching subdirectories
DB connection management: Try/finally blocks properly implemented (lines 92-129 in check-commit.sh)
Risk level casing in code: Fixed to consistent uppercase (HIGH, MEDIUM, LOW)
Guard PR body hook: Fixed to use structured JSON output format matching other hooks
Timeout adjustment: Increased check-commit.sh timeout from 20s to 30s

Remaining Documentation Issues (Non-Critical):
🟡 docs/guides/recommended-practices.md:342: Says low (lowercase) but code now uses LOW (uppercase)
🟡 docs/guides/recommended-practices.md:344: Claims "one DB connection for all files" but exportsData() opens/closes its own connection per file. Real benefit is batching into single Node.js process (avoiding N child spawns).

Security Analysis:
✅ All hooks use set -euo pipefail for safe execution
✅ JSON parsing includes proper error handling with try/catch
✅ No command injection vulnerabilities detected
✅ Proper escaping of user input in JSON output
✅ DB connections properly closed in try/finally blocks

Performance Claims Validation

Single checkData() call: ✅ Confirmed - Line 58-62 in check-commit.sh runs both cycles and signatures predicates in one call instead of two separate calls.

Batched dead export checks: ✅ Confirmed - Lines 65-89 in check-dead-exports.sh processes all files in a single Node.js invocation instead of spawning separate processes.

Performance improvement estimates appear valid based on the architectural changes.

Configuration and Documentation Review

Hook registration: All three hooks properly added to .claude/settings.json with appropriate timeouts:

  • guard-pr-body.sh: 10s (simple string matching)
  • check-commit.sh: 30s (single checkData call)
  • check-dead-exports.sh: 30s (batch file processing)

Documentation updated: docs/guides/recommended-practices.md includes all three hooks with clear descriptions (lines 355-357)

CLI improvement: Minor enhancement in src/cli.js clarifying --unused flag description and normalizing the value to false

Timeout adjustments: The timeout for check-commit.sh was increased from 20s to 30s, addressing Greptile's concern about timeouts on large graphs

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE WITH CONDITIONS
  • Reasoning: Well-implemented hooks with legitimate performance improvements. Code follows established patterns, includes proper security measures, and addresses a real need for commit-time validation. The performance optimizations are technically sound and well-documented.

Condition: Fix the two minor documentation inconsistencies in docs/guides/recommended-practices.md (lines 342 and 344) to match the actual implementation behavior.

Minor suggestions: Consider adding examples to the hook documentation showing what the error messages look like, but this doesn't block approval.


@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Pre-commit Hooks Implementation

Todo List:

  • Read and understand all changed files
  • Analyze root cause and problem being solved
  • Review hook implementations for security and correctness
  • Validate performance claims
  • Check configuration and documentation changes
  • Provide final assessment and rating

Root Cause Analysis

Problem Being Solved: Adding pre-commit validation to catch circular dependencies, dead exports, and improper PR descriptions before they enter the repository.

Why This Approach: Leverages codegraph's existing validation predicates with performance optimizations. The session-scoped validation ensures hooks only check files the AI agent actually modified.

Risk Assessment: LOW - Defensive implementation with proper error handling and guard clauses.

Hook Implementation Review

Fixed Issues from Previous Review:
Dead exports glob pattern: Fixed the critical bug - now uses grep -qE '^src/.*\.(js|ts|tsx)$' correctly matching subdirectories
DB connection management: Try/finally blocks properly implemented (lines 92-129 in check-commit.sh)
Risk level casing: Fixed to consistent uppercase (HIGH, MEDIUM, LOW)
Guard-pr-body output format: Now uses structured JSON with permissionDecision: 'deny' format
Timeout adjustments: check-commit.sh timeout increased to 30s to handle large graphs

Security Analysis:
✅ All hooks use set -euo pipefail for safe execution
✅ JSON parsing includes proper error handling with try/catch
✅ No command injection vulnerabilities detected
✅ Proper escaping of user input in JSON output
✅ DB connections properly closed in try/finally blocks

Remaining Documentation Issues (Minor):
🔶 check-dead-exports.sh:61: Comment claims "one DB connection for all files" but exportsData() opens/closes per file. Real benefit is batching into one Node.js process (avoiding N child spawns)
🔶 recommended-practices.md:342: Shows low (lowercase) but code now emits LOW (uppercase) - minor inconsistency

Performance Claims Validation

Single checkData() call: ✅ Confirmed - Line 58-62 in check-commit.sh runs both cycles and signatures predicates in one call instead of two separate calls.

Batched dead export checks: ✅ Confirmed - Lines 65-89 in check-dead-exports.sh processes all files in a single Node.js invocation instead of spawning separate processes.

Performance improvement estimates appear valid based on the architectural changes.

Configuration and Documentation Review

Hook registration: All three hooks properly added to .claude/settings.json with appropriate timeouts:

  • guard-pr-body.sh: 10s (simple string matching)
  • check-commit.sh: 30s (single checkData call)
  • check-dead-exports.sh: 30s (batch file processing)

Documentation updated: docs/guides/recommended-practices.md includes all three hooks with clear descriptions (lines 355-357)

CLI improvement: Minor enhancement in src/cli.js clarifying --unused flag description and normalizing the value to false

Timeout adjustments: The timeout for check-commit.sh was increased from 20s to 30s, addressing Greptile's concern about timeouts on large graphs

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE WITH CONDITIONS
  • Reasoning: Well-implemented hooks with legitimate performance improvements. Code follows established patterns, includes proper security measures, and addresses a real need for commit-time validation. The performance optimizations are technically sound and well-documented.

Conditions: Fix two minor documentation inaccuracies:

  1. Update comment in check-dead-exports.sh:61 to reflect actual behavior (batched Node.js process, not single DB connection)
  2. Fix casing inconsistency in recommended-practices.md:342 (LOW vs low)

Minor suggestions: Consider adding examples to the hook documentation showing what the error messages look like, but this doesn't block approval.

@carlos-alm
Copy link
Contributor Author

@greptileai

echo "$cmd" | grep -qi 'gh pr create' || exit 0

# Block if body contains "generated with"
if echo "$cmd" | grep -qi 'generated with'; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--body-file bypass renders the guard ineffective

The check inspects $cmd (the raw command string) for the literal text generated with. However, when gh pr create is invoked with --body-file /path/to/body.md, the PR body lives in a file — the command string only contains the path, not the body content. If that file contains "generated with", this hook silently allows the PR creation.

Claude Code regularly uses --body-file for longer PR bodies (the body is written to a temp file first). This means the guard is easily bypassed in normal usage.

To close the gap, extract the --body-file path from $cmd and read the file content as well:

# Block if body contains "generated with" — inline body
if echo "$cmd" | grep -qi 'generated with'; then
  # ... deny ...
fi

# Also check --body-file path
BODY_FILE=$(echo "$cmd" | grep -oP '(?<=--body-file\s)\S+' || true)
if [ -n "$BODY_FILE" ] && [ -f "$BODY_FILE" ]; then
  if grep -qi 'generated with' "$BODY_FILE"; then
    # ... deny ...
  fi
fi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9880b4d — the hook now extracts the \ path from the command and inspects that file's content for "generated with", closing the bypass.

const data = exportsData(file, undefined, { noTests: true, unused: true });
if (data && data.results) {
for (const r of data.results) {
if (r.consumerCount === 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant consumerCount === 0 check — unused: true already filters

exportsData is called with unused: true on line 75. Inside exportsFileImpl in src/queries.js (lines 2388–2391), that flag already filters data.results to only zero-consumer exports:

if (unused) {
  filteredResults = results.filter((r) => r.consumerCount === 0);
}

As a result, every entry in data.results is already guaranteed to have consumerCount === 0 when unused: true is passed. The guard here is always true and the inner loop body executes unconditionally.

Suggested change
if (r.consumerCount === 0) {
if (data && data.results) {
for (const r of data.results) {
dead.push(r.name + ' (' + data.file + ':' + r.line + ')');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9880b4d — removed the redundant .filter(r => r.consumerCount === 0) since unused: true already guarantees all results have zero consumers.

#!/usr/bin/env bash
# check-commit.sh — PreToolUse hook for Bash (git commit)
# Combined cycle-detection (blocking) + signature-change warning (informational).
# Runs checkData() ONCE with both predicates, single DB connection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header comment "single DB connection" is inaccurate

checkData() opens and closes its own better-sqlite3 connection internally, then the enrichment block (line 90) opens a second connection via openReadonlyOrFail(). The total is two DB opens per hook run — not one.

The real performance win here is running everything in one Node.js process (not spawning a child process per check), and the enrichment block uses one connection shared across all violations.

Suggested change
# Runs checkData() ONCE with both predicates, single DB connection.
# Runs checkData() ONCE with both predicates, single Node.js process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9880b4d — changed header to "single Node.js process" which accurately reflects the behavior (two DB opens, but one process).

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Pre-commit Hooks Implementation

Todo List:

  • Read and understand all changed files
  • Analyze root cause and problem being solved
  • Review hook implementations for security and correctness
  • Validate performance claims
  • Check configuration and documentation changes
  • Assess Greptile's remaining concerns
  • Provide final assessment and rating

Root Cause Analysis

Problem Being Solved: Adding pre-commit validation to catch circular dependencies, dead exports, and improper PR descriptions before they enter the repository.

Why This Approach: Leverages codegraph's existing validation predicates with performance optimizations. The session-scoped validation ensures hooks only check files the AI agent actually modified.

Risk Assessment: LOW - Defensive implementation with proper error handling and guard clauses.

Hook Implementation Review

Security Analysis:
✅ All hooks use set -euo pipefail for safe execution
✅ JSON parsing includes proper error handling with try/catch
✅ No command injection vulnerabilities detected
✅ Proper escaping of user input in JSON output
✅ DB connections properly closed in try/finally blocks

Fixed Issues from Previous Reviews:
Dead exports glob pattern: Fixed - now uses grep -qE '^src/.*\.(js|ts|tsx)$' correctly matching subdirectories
DB connection management: Try/finally blocks properly implemented (lines 92-129 in check-commit.sh)
Risk level casing: Fixed to consistent uppercase (HIGH, MEDIUM, LOW)
Guard-pr-body output format: Now uses structured JSON with permissionDecision: 'deny' format
Timeout adjustments: check-commit.sh timeout increased to 30s to handle large graphs
Body-file bypass: Fixed - guard-pr-body.sh now checks both inline body and --body-file paths (lines 34-49)

Performance Claims Validation

Single checkData() call: ✅ Confirmed - Line 58-62 in check-commit.sh runs both cycles and signatures predicates in one call instead of two separate calls.

Batched dead export checks: ✅ Confirmed - Lines 65-89 in check-dead-exports.sh processes all files in a single Node.js invocation instead of spawning separate processes.

Performance improvement estimates appear valid based on the architectural changes.

Configuration and Documentation Review

Hook registration: All three hooks properly added to .claude/settings.json with appropriate timeouts:

  • guard-pr-body.sh: 10s (simple string matching)
  • check-commit.sh: 30s (single checkData call)
  • check-dead-exports.sh: 30s (batch file processing)

Documentation updated: docs/guides/recommended-practices.md includes all three hooks with clear descriptions (lines 355-357)

CLI improvement: Minor enhancement in src/cli.js clarifying --unused flag description

Greptile's Remaining Concerns Assessment

🟡 Minor Issues (Non-blocking):

  • check-dead-exports.sh line 78: Redundant r.consumerCount === 0 check since unused: true already filters zero-consumer exports
  • Inaccurate comments: Some header comments don't perfectly reflect implementation details

✅ Previously Critical Issues Now Fixed:

  • Body-file bypassRESOLVED - Hook now properly checks --body-file content
  • Glob pattern bugsRESOLVED - Now correctly matches subdirectories
  • DB connection leaksRESOLVED - Try/finally blocks implemented

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Well-implemented hooks with legitimate performance improvements. Code follows established patterns, includes proper security measures, and addresses a real need for commit-time validation. The performance optimizations are technically sound and well-documented. All critical issues identified in previous reviews have been resolved.

Minor suggestions: The redundant check and comment inaccuracies are style issues that don't affect functionality and can be addressed in future iterations if desired.


View job

@carlos-alm
Copy link
Contributor Author

@greptileai

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Pre-commit Hooks Implementation

Todo List:

  • Read and understand all changed files
  • Analyze root cause and problem being solved
  • Review hook implementations for security and correctness
  • Validate performance claims
  • Check configuration and documentation changes
  • Provide final assessment and rating

Root Cause Analysis

Problem Being Solved: Adding pre-commit validation to catch circular dependencies, dead exports, and improper PR descriptions before they enter the repository.

Why This Approach: Leverages codegraph's existing validation predicates with performance optimizations. The session-scoped validation ensures hooks only check files the AI agent actually modified.

Risk Assessment: LOW - Defensive implementation with proper error handling and guard clauses.

Hook Implementation Review

Security Analysis:
✅ All hooks use set -euo pipefail for safe execution
✅ JSON parsing includes proper error handling with try/catch
✅ No command injection vulnerabilities detected
✅ Proper escaping of user input in JSON output
✅ DB connections properly closed in try/finally blocks

Fixed Issues from Previous Reviews:
Dead exports glob pattern: Fixed - now uses grep -qE '^src/.*\.(js|ts|tsx)$' correctly matching subdirectories
DB connection management: Try/finally blocks properly implemented (lines 92-129 in check-commit.sh)
Risk level casing: Fixed to consistent uppercase (HIGH, MEDIUM, LOW)
Guard-pr-body output format: Now uses structured JSON with permissionDecision: 'deny' format
Timeout adjustments: check-commit.sh timeout increased to 30s to handle large graphs
Body-file bypass: Fixed - guard-pr-body.sh now checks both inline body and --body-file paths (lines 34-49)

Performance Claims Validation

Single checkData() call: ✅ Confirmed - Line 58-62 in check-commit.sh runs both cycles and signatures predicates in one call instead of two separate calls.

Batched dead export checks: ✅ Confirmed - Lines 65-89 in check-dead-exports.sh processes all files in a single Node.js invocation instead of spawning separate processes.

Performance improvement estimates appear valid based on the architectural changes.

Configuration and Documentation Review

Hook registration: All three hooks properly added to .claude/settings.json with appropriate timeouts:

  • guard-pr-body.sh: 10s (simple string matching)
  • check-commit.sh: 30s (single checkData call)
  • check-dead-exports.sh: 30s (batch file processing)

Documentation updated: docs/guides/recommended-practices.md includes all three hooks with clear descriptions (lines 355-357)

CLI improvement: Minor enhancement in src/cli.js clarifying --unused flag description

Greptile's Remaining Concerns Assessment

🟡 Minor Issues (Non-blocking):

  • check-dead-exports.sh line 78: Redundant r.consumerCount === 0 check since unused: true already filters zero-consumer exports
  • Inaccurate comments: Some header comments don't perfectly reflect implementation details

✅ Previously Critical Issues Now Fixed:

  • Body-file bypassRESOLVED - Hook now properly checks --body-file content
  • Glob pattern bugsRESOLVED - Now correctly matches subdirectories
  • DB connection leaksRESOLVED - Try/finally blocks implemented

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Well-implemented hooks with legitimate performance improvements. Code follows established patterns, includes proper security measures, and addresses a real need for commit-time validation. The performance optimizations are technically sound and well-documented. All critical issues identified in previous reviews have been resolved.

Minor suggestions: The redundant check and comment inaccuracies are style issues that don't affect functionality and can be addressed in future iterations if desired.


Comment on lines +87 to +133
const sigPred = data.predicates.find(p => p.name === 'signatures');
if (sigPred && !sigPred.passed && sigPred.violations?.length) {
// Enrich with role + transitive caller count using a single DB connection
const db = openReadonlyOrFail();
const lines = [];
try {
const stmtNode = db.prepare(
'SELECT id, role FROM nodes WHERE name = ? AND file = ? AND line = ?'
);
const stmtCallers = db.prepare(
'SELECT DISTINCT n.id FROM edges e JOIN nodes n ON e.source_id = n.id WHERE e.target_id = ? AND e.kind = \\'calls\\''
);

for (const v of sigPred.violations) {
const node = stmtNode.get(v.name, v.file, v.line);
const role = node?.role || 'unknown';

let callerCount = 0;
if (node) {
const visited = new Set([node.id]);
let frontier = [node.id];
for (let d = 0; d < 3; d++) {
const next = [];
for (const fid of frontier) {
for (const c of stmtCallers.all(fid)) {
if (!visited.has(c.id)) {
visited.add(c.id);
next.push(c.id);
callerCount++;
}
}
}
frontier = next;
if (!frontier.length) break;
}
}

const risk = role === 'core' ? 'HIGH' : role === 'utility' ? 'MEDIUM' : 'LOW';
lines.push(risk + ': ' + v.name + ' (' + v.kind + ') [' + role + '] at ' + v.file + ':' + v.line + ' — ' + callerCount + ' transitive callers');
}
} finally {
db.close();
}

if (lines.length > 0) {
output.sigWarning = lines.join('\n');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signature warning not scoped to session-edited files

The cycle check (lines 68–84) explicitly filters to cycles that involve files from the session edit log. The signature warning block here has no equivalent filter — it processes sigPred.violations unconditionally for all staged files, regardless of whether you authored those changes.

In practice this means that after a git merge or git cherry-pick, any signature changes introduced by a teammate will also fire warnings, even though the current session author has no context about them. The cycle check avoids this noise by scoping to edited; the sig check should too:

// ── Signature warning (informational, never blocks) ──
const sigPred = data.predicates.find(p => p.name === 'signatures');
if (sigPred && !sigPred.passed && sigPred.violations?.length) {
  const edited = new Set(editedRaw.split('\n').filter(Boolean));
  // Scope to violations in session-edited files only
  const scopedViolations = edited.size > 0
    ? sigPred.violations.filter(v => edited.has(v.file))
    : sigPred.violations;
  if (scopedViolations.length === 0) {
    // nothing to do
  } else {
    const db = openReadonlyOrFail();
    ...
  }
}

Comment on lines +145 to +157
if [ "$ACTION" = "deny" ]; then
REASON=$(echo "$RESULT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).reason||'')}catch{}})" 2>/dev/null) || true
node -e "
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: process.argv[1]
}
}));
" "$REASON"
exit 0
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signature warnings silently dropped when a cycle block also fires

When ACTION === 'deny', the script emits the deny JSON and exits without ever reaching the SIG_WARNING extraction block (lines 159–174). If the same commit has both cycle violations and signature changes, the developer sees only the cycle error and loses all visibility into the signature risk information that would otherwise have been surfaced.

Consider including any sigWarning payload in the permissionDecisionReason of the deny response so it isn't silently discarded:

output.action = 'deny';
output.reason = 'BLOCKED: Circular dependencies detected ...\n'
  + (output.sigWarning ? '\n--- Signature warnings ---\n' + output.sigWarning : '');

fi

# Also check --body-file path
BODY_FILE=$(echo "$cmd" | grep -oP '(?<=--body-file\s)\S+' || true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\S+ silently truncates --body-file paths that contain spaces

The lookbehind regex (?<=--body-file\s)\S+ matches only non-whitespace characters after --body-file, so a quoted path like --body-file "/home/user name/tmp/body.md" would yield /home/user — a path that almost certainly doesn't exist. When the resolved path doesn't exist, the [ -f "$BODY_FILE" ] guard silently skips the file check, leaving the guard ineffective for that invocation.

A more robust extraction handles optional quotes:

BODY_FILE=$(echo "$cmd" | grep -oP '(?<=--body-file\s)["\x27]?\S+["\x27]?' | tr -d "'\"" || true)

Or, using a POSIX-safe sed approach:

BODY_FILE=$(echo "$cmd" | sed -nE "s/.*--body-file[[:space:]]+'?\"?([^'\" ]+).*/\1/p" || true)

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Pre-commit Hooks Implementation

Todo List:

  • Read and understand all changed files
  • Analyze root cause and problem being solved
  • Review hook implementations for security and correctness
  • Validate performance claims
  • Check configuration and documentation changes
  • Provide final assessment and rating

Root Cause Analysis

Problem Being Solved: Adding pre-commit validation to catch circular dependencies, dead exports, and improper PR descriptions before they enter the repository.

Why This Approach: Leverages codegraph's existing validation predicates with performance optimizations. The session-scoped validation ensures hooks only check files the AI agent actually modified.

Risk Assessment: LOW - Defensive implementation with proper error handling and guard clauses.

Hook Implementation Review

Security Analysis:
✅ All hooks use set -euo pipefail for safe execution
✅ JSON parsing includes proper error handling with try/catch
✅ No command injection vulnerabilities detected
✅ Proper escaping of user input in JSON output
✅ DB connections properly closed in try/finally blocks

Fixed Issues from Previous Reviews:
Dead exports glob pattern: Fixed - now uses grep -qE '^src/.*\.(js|ts|tsx)$' correctly matching subdirectories
DB connection management: Try/finally blocks properly implemented (lines 92-129 in check-commit.sh)
Risk level casing: Fixed to consistent uppercase (HIGH, MEDIUM, LOW)
Guard-pr-body output format: Now uses structured JSON with permissionDecision: 'deny' format
Timeout adjustments: check-commit.sh timeout increased to 30s to handle large graphs
Body-file bypass: Fixed - guard-pr-body.sh now checks both inline body and --body-file paths (lines 34-49)

Performance Claims Validation

Single checkData() call: ✅ Confirmed - Line 58-62 in check-commit.sh runs both cycles and signatures predicates in one call instead of two separate calls.

Batched dead export checks: ✅ Confirmed - Lines 65-89 in check-dead-exports.sh processes all files in a single Node.js invocation instead of spawning separate processes.

Performance improvement estimates appear valid based on the architectural changes.

Configuration and Documentation Review

Hook registration: All three hooks properly added to .claude/settings.json with appropriate timeouts:

  • guard-pr-body.sh: 10s (simple string matching)
  • check-commit.sh: 30s (single checkData call)
  • check-dead-exports.sh: 30s (batch file processing)

Documentation updated: docs/guides/recommended-practices.md includes all three hooks with clear descriptions (lines 355-357)

CLI improvement: Minor enhancement in src/cli.js clarifying --unused flag description

Greptile's Remaining Concerns Assessment

🟡 Minor Issues (Non-blocking):

  • check-dead-exports.sh line 78: Redundant r.consumerCount === 0 check since unused: true already filters zero-consumer exports
  • Inaccurate comments: Some header comments don't perfectly reflect implementation details

✅ Previously Critical Issues Now Fixed:

  • Body-file bypassRESOLVED - Hook now properly checks --body-file content
  • Glob pattern bugsRESOLVED - Now correctly matches subdirectories
  • DB connection leaksRESOLVED - Try/finally blocks implemented

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Well-implemented hooks with legitimate performance improvements. Code follows established patterns, includes proper security measures, and addresses a real need for commit-time validation. The performance optimizations are technically sound and well-documented. All critical issues identified in previous reviews have been resolved.

Minor suggestions: The redundant check and comment inaccuracies are style issues that don't affect functionality and can be addressed in future iterations if desired.

@carlos-alm carlos-alm merged commit d1b89e1 into main Mar 9, 2026
16 checks passed
@carlos-alm carlos-alm deleted the chore/pre-commit-hooks branch March 9, 2026 04:29
@github-actions github-actions bot locked and limited conversation to collaborators Mar 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant