From 2cd04974e0ac4fca06aefe9fa766caf96e7ec42f Mon Sep 17 00:00:00 2001 From: laptran Date: Sat, 13 Jun 2026 17:55:39 -0400 Subject: [PATCH] Add git pre-commit hook for task enforcement --- scripts/install-hooks.sh | 14 ++++++++ scripts/pre-commit-hook.sh | 52 ++++++++++++++++++++++++++++ tasks/pre-commit-enforcement/SPEC.md | 37 ++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100755 scripts/install-hooks.sh create mode 100755 scripts/pre-commit-hook.sh create mode 100644 tasks/pre-commit-enforcement/SPEC.md diff --git a/scripts/install-hooks.sh b/scripts/install-hooks.sh new file mode 100755 index 0000000..853f128 --- /dev/null +++ b/scripts/install-hooks.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Install git hooks for the automaton framework. +set -e + +HOOKS_DIR="$(cd "$(dirname "$0")/.." && pwd)/.git/hooks" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "Installing git hooks..." + +cp "$SCRIPT_DIR/pre-commit-hook.sh" "$HOOKS_DIR/pre-commit" +chmod +x "$HOOKS_DIR/pre-commit" + +echo "Installed: $HOOKS_DIR/pre-commit" +echo "Done." diff --git a/scripts/pre-commit-hook.sh b/scripts/pre-commit-hook.sh new file mode 100755 index 0000000..86f67d0 --- /dev/null +++ b/scripts/pre-commit-hook.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Pre-commit hook: blocks commits that change files without a corresponding task. +# Install via: scripts/install-hooks.sh +set -e + +TASKS_DIR="tasks" + +# Get list of changed files (staged) +CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep -v "^$TASKS_DIR/" || true) + +if [ -z "$CHANGED_FILES" ]; then + exit 0 +fi + +# Check if any task spec mentions the changed files +matches="" +for file in $CHANGED_FILES; do + basename=$(basename "$file") + # Search for the filename or path in all task SPEC.md files + if grep -q -r "$basename\|$file" "$TASKS_DIR" --include="SPEC.md" 2>/dev/null; then + matches="$matches $file" + fi +done + +# Count how many changed files are matched +matched_count=$(echo "$matches" | tr ' ' '\n' | sort -u | grep -c . || true) +total_count=$(echo "$CHANGED_FILES" | grep -c . || true) + +if [ "$matched_count" -ge "$total_count" ]; then + exit 0 +fi + +# Some files lack task coverage — warn and ask +echo "" +echo "=== TASK ENFORCEMENT ===" +echo "Changed files not covered by any task in $TASKS_DIR/:" +for file in $CHANGED_FILES; do + if ! echo "$matches" | grep -q "$file"; then + echo " - $file" + fi +done +echo "" +echo "The .rules.md requires all changes to go through a task." +echo "Create a task in $TASKS_DIR/{name}/SPEC.md first," +echo "or use 'git commit --no-verify' to bypass this check." +echo "" +read -p "Continue without a task? (y/N) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Commit blocked. Create a task for your changes first." + exit 1 +fi diff --git a/tasks/pre-commit-enforcement/SPEC.md b/tasks/pre-commit-enforcement/SPEC.md new file mode 100644 index 0000000..c07d12a --- /dev/null +++ b/tasks/pre-commit-enforcement/SPEC.md @@ -0,0 +1,37 @@ +# SPEC: Git Pre-Commit Hook for Task Enforcement + +## Overview + +The `.rules.md` says "All changes must go through a task in tasks/{name}/" but nothing enforces it. The agent repeatedly makes ad-hoc edits and only creates tasks retroactively (or not at all). A pre-commit git hook mechanically blocks commits that lack a corresponding task. + +## Root Cause + +The rule is passive (a file on disk) and relies on the agent remembering to check it. Every session starts fresh — past failures don't carry over. The agent has no active enforcement. + +## Solution + +A `scripts/pre-commit-hook.sh` that: + +1. Gets the list of changed files (staged + unstaged) +2. Checks if there's a task in `tasks/` whose SPEC.md keywords match the changed files +3. If no matching task found, prints the changed files and asks: "Continue without a task? (y/N)" +4. If the user says no, exits non-zero and blocks the commit + +The hook is installed via `scripts/install-hooks.sh` which copies it to `.git/hooks/pre-commit`. + +## Files + +- `scripts/pre-commit-hook.sh` — the hook script +- `scripts/install-hooks.sh` — installer + +## Acceptance Criteria + +- [ ] `scripts/pre-commit-hook.sh` blocks commits that change files without a matching task +- [ ] `scripts/install-hooks.sh` installs the hook into `.git/hooks/pre-commit` +- [ ] Hook allows commits when user confirms (y) despite no task +- [ ] Hook allows commits when a matching task exists in tasks/ +- [ ] README updated to mention hook installation + +## Why This Fix Works + +A pre-commit hook runs before every commit. The agent cannot bypass it without intentionally deciding to — and even then, the prompt forces conscious acknowledgment ("Continue without a task?") rather than simply forgetting.