Skip to content

feat: prepare-commit-msg hook for passive AI-Agent trailer injection (GIT-70)#41

Merged
TonyCasey merged 2 commits intomainfrom
git-70
Feb 13, 2026
Merged

feat: prepare-commit-msg hook for passive AI-Agent trailer injection (GIT-70)#41
TonyCasey merged 2 commits intomainfrom
git-70

Conversation

@TonyCasey
Copy link
Copy Markdown
Owner

@TonyCasey TonyCasey commented Feb 13, 2026

Summary

  • Adds src/hooks/prepare-commit-msg.ts with installHook() / uninstallHook() functions
  • Shell hook script detects AI sessions via $GIT_MEM_AGENT or $CLAUDE_CODE env vars
  • Uses git interpret-trailers --in-place for proper trailer formatting (< 100ms)
  • Wraps existing hooks via .user-backup rename, restores on uninstall
  • Adds --hooks and --uninstall-hooks CLI options to git mem init
  • Adds AI-Agent to AI_TRAILER_KEYS
  • 12 new tests (install, idempotency, wrap, uninstall, commit message modification, merge skip)

Closes GIT-70

Test plan

  • installHook creates hook with fingerprint in .git/hooks/
  • Second install is idempotent (no-op)
  • Existing non-git-mem hooks are wrapped with backup
  • uninstallHook removes hook and restores backup
  • Hook adds AI-Agent trailer when GIT_MEM_AGENT is set
  • Hook adds AI-Agent trailer when CLAUDE_CODE is set
  • Hook skips when no AI env vars present
  • Hook skips when AI-Agent trailer already present
  • Hook skips merge commits
  • All 310 tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added --hooks and --uninstall-hooks CLI options to the init command to manage Git hook installation.
    • Automatic Git hook injection of an "AI-Agent" trailer into commit messages during AI-assisted sessions; skips merge/squash/amend commits and avoids duplicate trailers.
    • Install/uninstall preserves and restores existing user hooks when present.
  • Tests

    • Added comprehensive tests covering hook install/uninstall and commit-message behavior.

…(GIT-70)

Adds a git hook that automatically injects AI-Agent trailers into commit
messages when an AI-assisted session is detected (via $GIT_MEM_AGENT or
$CLAUDE_CODE env vars). Install with `git mem init --hooks`, remove with
`git mem init --uninstall-hooks`. Wraps existing hooks safely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 13, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds CLI flags --hooks and --uninstall-hooks to the init command and a new prepare-commit-msg hook implementation with install/uninstall functions; the hook injects an AI-Agent trailer into commit messages when AI-assisted sessions are detected and supports idempotent install/uninstall and wrapping existing hooks.

Changes

Cohort / File(s) Summary
CLI Hook Options
src/cli.ts, src/commands/init.ts
Added --hooks and --uninstall-hooks flags to init; extended IInitCommandOptions and wired installHook/uninstallHook flows and logging into the init command, including early exit for uninstall.
Git Hook Implementation
src/hooks/prepare-commit-msg.ts
New module implementing installHook/uninstallHook and hook runtime: detects AI sessions via GIT_MEM_AGENT or CLAUDE_CODE, skips merge/squash/amend commits, avoids duplicate AI-Agent trailers, uses git interpret-trailers, finds repo git dir (worktree-aware), and supports wrapping/restoring existing hooks. Exports IHookInstallResult.
Domain Model
src/domain/entities/ITrailer.ts
Added AGENT trailer key value 'AI-Agent' to AI_TRAILER_KEYS.
Hook Tests
tests/unit/hooks/prepare-commit-msg.test.ts
Adds comprehensive tests using temporary git repos covering install/uninstall idempotence, wrapping/backups, restoration, and commit-message trailer injection/skip/deduplication behavior.

Sequence Diagram

sequenceDiagram
    participant User as Developer
    participant CLI
    participant Init as init Command
    participant HookMod as prepare-commit-msg Module
    participant GitFS as Git Repository
    participant Commit as Commit Process

    User->>CLI: git-mem init --hooks
    CLI->>Init: run init (hooks=true)
    Init->>HookMod: installHook(cwd)
    HookMod->>GitFS: locate .git (handle worktree)
    HookMod->>GitFS: read existing prepare-commit-msg hook
    Note over HookMod: if existing non-git-mem hook:\nrename backup and write wrapper
    HookMod->>GitFS: write git-mem prepare-commit-msg hook
    HookMod-->>Init: return IHookInstallResult
    Init->>CLI: log result
    CLI-->>User: installation result

    User->>Commit: git commit -m "msg"
    Commit->>GitFS: trigger prepare-commit-msg
    GitFS->>HookMod: execute hook script
    HookMod->>HookMod: check GIT_MEM_AGENT / CLAUDE_CODE
    Note over HookMod: if AI session and not merge/squash/amend\nand trailer missing -> use interpret-trailers
    HookMod->>GitFS: update commit message with AI-Agent trailer
    HookMod-->>GitFS: exit
    GitFS-->>User: commit created (possibly modified)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #41: Implements the same prepare-commit-msg hook installation/uninstallation and CLI init hook flags — directly related code overlap.
  • PR #27: Adds integration tests around hook install/uninstall behavior and exercises hook entry points in real repositories.
  • PR #28: Modifies the unified init command structure that this change extends with hooks/uninstall options.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature added: a prepare-commit-msg hook for passive AI-Agent trailer injection, matching the primary changes across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into git-69

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch git-70

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/hooks/prepare-commit-msg.ts`:
- Around line 32-35: Update the misleading comment above the case
"$COMMIT_SOURCE" in prepare-commit-msg.ts: remove or reword "amend" so the
comment accurately reflects that the hook skips only merge and squash commit
sources (since Git does not pass "amend" as a COMMIT_SOURCE). Reference the
existing case "$COMMIT_SOURCE" in the file and adjust the comment to say
something like "Skip merge/squash commits (amend is not passed as a
COMMIT_SOURCE)" or simply "Skip merge and squash commits."
- Around line 114-117: The wrapper currently embeds the absolute backupPath into
HOOK_SCRIPT when building wrapperScript which breaks if the repo is moved;
change the injected command to call the backup hook relative to the hook's
location (e.g., use "$(dirname "$0")/prepare-commit-msg.user-backup") instead of
the absolute backupPath so the wrapperScript will always find
prepare-commit-msg.user-backup regardless of repo location; update the
replacement logic that constructs wrapperScript (references: wrapperScript,
HOOK_SCRIPT, backupPath, prepare-commit-msg.user-backup) to substitute the
relative dirname invocation.
- Around line 95-124: The installHook function can overwrite an existing backup
at backupPath when wrapping an existing hook; before calling
renameSync(hookPath, backupPath) check existsSync(backupPath) and handle that
case (e.g., avoid overwriting by choosing a new unique backup name, abort/warn
and skip wrapping, or fail the install) so the original user hook isn't lost;
ensure the chosen behavior updates the wrapped boolean and the wrapper creation
logic that references backupPath, and keep isGitMemHook and hookPath checks
intact.

Comment thread src/hooks/prepare-commit-msg.ts
Comment thread src/hooks/prepare-commit-msg.ts
Comment thread src/hooks/prepare-commit-msg.ts
TonyCasey added a commit that referenced this pull request Feb 13, 2026
- Fix comment: "merge/squash" not "merge/squash/amend" (git has no amend source)
- Guard against overwriting existing backup in installHook
- Use relative path for backup hook reference in wrapper script
- Validate --limit flag as positive integer in trailers command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Base automatically changed from git-69 to main February 13, 2026 11:41
Copilot AI review requested due to automatic review settings February 13, 2026 11:41
@TonyCasey TonyCasey merged commit b1455c0 into main Feb 13, 2026
4 of 5 checks passed
@TonyCasey TonyCasey deleted the git-70 branch February 13, 2026 11:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a passive AI-Agent trailer injection system via a Git prepare-commit-msg hook. The feature enables automatic tracking of AI-assisted commits by detecting AI session context through environment variables ($GIT_MEM_AGENT or $CLAUDE_CODE) and injecting standardized AI-Agent: trailers into commit messages using Git's native git interpret-trailers command.

Changes:

  • Adds core hook implementation with install/uninstall functions and shell script logic
  • Integrates hook management into the CLI via --hooks and --uninstall-hooks options
  • Extends the trailer type system with AI-Agent key
  • Provides comprehensive test coverage (12 tests) covering install, wrap, uninstall, and commit message modification scenarios

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/hooks/prepare-commit-msg.ts Core implementation of hook install/uninstall logic and shell script generation
tests/unit/hooks/prepare-commit-msg.test.ts Comprehensive test suite covering installation, wrapping, uninstallation, and commit message modification
src/commands/init.ts Integration of hook install/uninstall into the init command workflow
src/cli.ts CLI option definitions for --hooks and --uninstall-hooks
src/domain/entities/ITrailer.ts Addition of AGENT constant to AI_TRAILER_KEYS

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import { describe, it, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'child_process';
import { mkdtempSync, writeFileSync, readFileSync, existsSync, chmodSync, rmSync, mkdirSync } from 'fs';
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The import mkdirSync is unused. Remove it from the imports to keep the code clean.

Suggested change
import { mkdtempSync, writeFileSync, readFileSync, existsSync, chmodSync, rmSync, mkdirSync } from 'fs';
import { mkdtempSync, writeFileSync, readFileSync, existsSync, chmodSync, rmSync } from 'fs';

Copilot uses AI. Check for mistakes.
COMMIT_MSG_FILE="$1"
COMMIT_SOURCE="$2"

# Skip merge/squash/amend commits
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The comment says "Skip merge/squash/amend commits" but the case statement only skips merge and squash. If amend commits should also be skipped, add it to the case statement. Otherwise, update the comment to match the actual behavior.

Suggested change
# Skip merge/squash/amend commits
# Skip merge/squash commits

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +125
export function installHook(cwd?: string): IHookInstallResult {
const gitDir = findGitDir(cwd || process.cwd());
const hooksDir = join(gitDir, 'hooks');
const hookPath = join(hooksDir, 'prepare-commit-msg');
const backupPath = join(hooksDir, 'prepare-commit-msg.user-backup');

// Already installed — idempotent
if (isGitMemHook(hookPath)) {
return { installed: false, wrapped: false, hookPath };
}

let wrapped = false;

// Existing non-git-mem hook — wrap it
if (existsSync(hookPath)) {
renameSync(hookPath, backupPath);
wrapped = true;

// Create a wrapper that calls the user's hook first, then ours
const wrapperScript = HOOK_SCRIPT.replace(
'#!/bin/sh',
`#!/bin/sh\n# Wrapped existing hook — original saved as prepare-commit-msg.user-backup\nif [ -x "${backupPath}" ]; then\n "${backupPath}" "$@" || exit $?\nfi`,
);
writeFileSync(hookPath, wrapperScript);
} else {
writeFileSync(hookPath, HOOK_SCRIPT);
}

chmodSync(hookPath, 0o755);
return { installed: true, wrapped, hookPath };
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The installHook function assumes the hooks directory exists, but this may not always be true (e.g., in bare repositories or corrupted git directories). Consider adding mkdirSync(hooksDir, { recursive: true }) before line 109 to ensure the directory exists before attempting to write files to it.

Copilot uses AI. Check for mistakes.
// Create a wrapper that calls the user's hook first, then ours
const wrapperScript = HOOK_SCRIPT.replace(
'#!/bin/sh',
`#!/bin/sh\n# Wrapped existing hook — original saved as prepare-commit-msg.user-backup\nif [ -x "${backupPath}" ]; then\n "${backupPath}" "$@" || exit $?\nfi`,
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The wrapper script uses an absolute path ${backupPath} in the shell script. This creates a hardcoded absolute path in the hook file, which will break if the repository is moved or cloned. Consider using a relative path like ./prepare-commit-msg.user-backup or using $(dirname "$0")/prepare-commit-msg.user-backup instead.

Suggested change
`#!/bin/sh\n# Wrapped existing hook — original saved as prepare-commit-msg.user-backup\nif [ -x "${backupPath}" ]; then\n "${backupPath}" "$@" || exit $?\nfi`,
`#!/bin/sh\n# Wrapped existing hook — original saved as prepare-commit-msg.user-backup\nBACKUP_HOOK="$(dirname "$0")/prepare-commit-msg.user-backup"\nif [ -x "$BACKUP_HOOK" ]; then\n "$BACKUP_HOOK" "$@" || exit $?\nfi`,

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants