feat: hooks refactor — staging hygiene, MD version discipline, release workflow, AI attribution, GitHub templates#29
Conversation
Two-layer enforcement to prevent the version drift found in commit 92c6542 (14 missing bumps, 5 overbumps, 2 skipped versions, 1 regression): Layer 1 — Git pre-commit hook: validates staged .md files have bumped version + last_updated when content changes. Catches missing bumps, regressions, skipped versions. Blocks commit on errors. Layer 2 — Claude Code PostToolUse hook: advisory reminder when Claude edits .md files, prompting version + timestamp bump before committing. New files: - scripts/audit_md_version_history.py: walks full git history, classifies each transition, produces per-file scorecards (baseline: 88% overall) - scripts/validate_md_versions.py: staged file validation (reusable by both git hook and Claude Code hook) - tests/test_md_version_discipline.py: 39 unit tests covering parsing, classification, validation, and exclusions - .githooks/md-version-reminder.sh: PostToolUse advisory hook Modified: - .githooks/pre-commit: new MD version discipline section - .claude/settings.json: PostToolUse hook for Edit|Write - Makefile: chmod +x for new hook in install-hooks target Scoping: only validates files with staged content changes — no cross-file cascading, no forced bumps on untouched files. https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cff608d03f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if not staged_sv: | ||
| if staged_version: | ||
| findings.append( |
There was a problem hiding this comment.
Fail validation when front matter version is missing
If version is absent in front matter, staged_version is empty, the if staged_version: guard prevents adding any error, and validation returns successfully. In practice this lets a staged .md edit pass pre-commit even after removing/omitting version, which bypasses the version-discipline enforcement this hook is meant to provide.
Useful? React with 👍 / 👎.
| if major_d > 1: | ||
| skipped = True | ||
| elif major_d == 0: | ||
| if minor_d > 1: | ||
| skipped = True |
There was a problem hiding this comment.
Detect skipped versions when lower components also jump
The skipped-version check only catches major_d > 1 or, when majors are equal, large minor/patch jumps. Transitions like 1.2.3 -> 2.5.7 or 1.2.3 -> 1.3.9 are treated as valid even though they skip many intermediate versions. That allows commits to bypass the stated "increment by exactly 1" rule.
Useful? React with 👍 / 👎.
| def get_staged_md_files() -> list[str]: | ||
| """Return list of staged .md files (Added, Copied, Modified).""" | ||
| result = subprocess.run( | ||
| ["git", "diff", "--cached", "--name-only", "--diff-filter=ACM", "--", "*.md"], |
There was a problem hiding this comment.
Include renamed markdown files in staged validation
Using --diff-filter=ACM excludes rename status R, so a rename+edit (old.md -> new.md) can produce no staged markdown matches and skip validation entirely. This creates an enforcement gap where content changes in renamed docs are not checked for version/timestamp discipline.
Useful? React with 👍 / 👎.
| old_content = git_show(old_hash, filepath) | ||
| new_content = git_show(new_hash, filepath) |
There was a problem hiding this comment.
Resolve historical paths when auditing renamed files
The audit walks hashes from git log --follow but always reads blobs using the current filepath, so pre-rename commits often return no content for that path. Those transitions then get misclassified (typically as no_frontmatter), which distorts per-file and overall discipline scores for renamed markdown files.
Useful? React with 👍 / 👎.
CLAUDE.md 4.16.0 → 4.17.0: - Update architecture table: stale hook descriptions → actual pre-commit sections (IP checks + MD version discipline + ruff + RAG) - Add new files: md-version-reminder.sh, validate_md_versions.py, audit_md_version_history.py - Add development commands for version discipline tools markdown-frontmatter.md 1.2.0 → 1.3.0: - Replace original audit numbers (from commit 92c6542) with actual findings from full 188-commit audit: 4 missing bumps, 1 overbump, 1 skip, 1 regression (87.8% score) - Document automated enforcement: git pre-commit + Claude Code PostToolUse hooks - Document two-track versioning policy (release vs document versions) CONTRIBUTING.md 3.26.1 → 3.27.0: - Update hook description in dev setup step 4 README.md 3.36.0 → 3.36.1: - Update hook description in Quick Start step 3 All 4 files validated by scripts/validate_md_versions.py --staged. https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS
Staging hygiene (pre-commit): - Block .env, .key, .pem, credentials.json from being staged - Block rag/store/, agent_data/, agent_traces/ artifacts - Warn on files >1MB - Detect API keys/tokens (ghp_, sk-, AKIA, AIza) in staged diffs Pre-release validation (.githooks/pre-release): - Validates version sync across all 5 locations before tagging: pyproject.toml, src/__init__.py, LICENSE, README badge, CHANGELOG - Checks working tree is clean - Checks tag doesn't already exist - Usage: bash .githooks/pre-release 3.36.0 Release workflow (.github/workflows/release.yml): - Triggered by v* tag push - Validates tag == pyproject.toml == src/__init__.py - Validates CHANGELOG entry exists - Runs ruff lint + full test suite - Creates GitHub Release with CHANGELOG section as body Also: - Fix ruff F401: remove unused pytest import from test file - Add make release VERSION=X.Y.Z target - Update CLAUDE.md 4.17.0 → 4.18.0 (new hooks/scripts in arch table) - Update CONTRIBUTING.md 3.27.0 → 3.28.0 (release workflow section) https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS
…notes, support New files: - .github/CODEOWNERS: path-based reviewer assignment for all critical paths (auth, rights, telnet, license, commands, agent harness) - .github/ISSUE_TEMPLATE/documentation.yml: structured docs issue template with area, severity, and suggestion fields - .github/release.yml: auto-generated release notes config with categories (breaking, security, new tools, new skills, features, bug fixes, docs) - .github/SUPPORT.md: support documentation with resource links, question/bug/security reporting guidance Existing templates retained: - bug_report.yml (safety scope + MA2 version + platform) - feature_request.yml (area + safety scope + constraints) - config.yml (blank issues disabled, security redirect) - pull_request_template.md (safety classification + test checklist) - dependabot.yml (weekly pip + npm + github-actions) https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS
Model detection (.githooks/detect-claude-model.sh): - Extracts active Claude model from process args (--model flag) - Fallback: parses session transcript JSONL - Returns model ID (e.g. claude-opus-4-6) or "unknown" - Only activates in Claude Code context (CLAUDECODE=1) Commit attribution (.githooks/prepare-commit-msg): - Auto-injects structured git trailers for Claude Code commits: AI-Model: claude-opus-4-6 AI-Session: cse_015q4sbkP8HQLhA1BtA1QqeS Claude-Code: 2.1.42 - Skips merge commits and non-Claude contexts - Preserves existing trade secret language filter Native attribution (.claude/settings.json): - Enables Co-Authored-By trailer via attribution.commit - Enables PR attribution via attribution.pr Queryable audit trail: git log --format="%(trailers:key=AI-Model)" | sort -u CLAUDE.md 4.18.0 → 4.19.0 (new hook in arch table) SECURITY.md 1.1.0 → 1.2.0 (AI-assisted development section) https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS
When src/private/ git submodule is not checked out, test files that import PROFESSIONAL/ENTERPRISE tools fail at collection or call time. Fixes: - test_live_integration.py: wrap bulk import in try/except, skip module - test_server_orchestration_tools.py: same pattern - test_architecture_hygiene.py: skip tier validation + destructive tool tests when src/private/ files are absent; guard Path.exists() in file iteration loops - pre-push hook: use --maxfail=50 (match CI) + ignore known private- dependent test files to prevent push blocks from pre-existing failures https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS AI-Model: claude-opus-4-6 AI-Session: cse_015q4sbkP8HQLhA1BtA1QqeS Claude-Code: 2.1.42
When src/private/ git submodule is not checked out, the pre-push hook now dynamically scans test files via AST to find imports of missing PROFESSIONAL/ENTERPRISE tools, and --ignore's those files from pytest. Also handles runtime-dependent files (test_categorization.py) that import modules which internally read src/private/ at execution time. This fixes 193 pre-existing test failures that blocked git push when the private submodule was unavailable. https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS AI-Model: claude-opus-4-6 AI-Session: cse_015q4sbkP8HQLhA1BtA1QqeS Claude-Code: 2.1.42
test_rights.py reads src/private/ files directly (not via import), so the AST scanner misses it. Added to runtime-dependent skip list. https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS AI-Model: claude-opus-4-6 AI-Session: cse_015q4sbkP8HQLhA1BtA1QqeS Claude-Code: 2.1.42
The count audit reads src/private/ tool files to count @mcp.tool() decorators. When the private submodule is not checked out, this crashes. Guard with file existence check, matching other pre-push guards. https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS AI-Model: claude-opus-4-6 AI-Session: cse_015q4sbkP8HQLhA1BtA1QqeS Claude-Code: 2.1.42
P0 fixes: - test.yml: remove continue-on-error from pytest (tests now block PRs), add submodule checkout, add validate_md_versions step, add dynamic test-skip for missing src/private/ (matches pre-push logic) - prepare-commit-msg: expand commit source exclusion to squash + commit (amend) to prevent AI trailer duplication - release.yml: add LICENSE version + README badge version validation (was only checking pyproject.toml + __init__.py, now checks all 5) - md-version-reminder.sh: add jq existence guard with warning instead of silent failure P1 fixes: - pre-release: add unpushed commits check (prevents tagging code that isn't on remote) - pre-commit: add error handling for RAG re-index (was silent on failure) - detect-claude-model.sh: walk PPID ancestry chain to find model from THIS session's claude process, avoiding cross-session attribution in multi-session environments https://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS AI-Model: claude-opus-4-6 AI-Session: cse_015q4sbkP8HQLhA1BtA1QqeS Claude-Code: 2.1.42
From PR #29 (claude/implement-hooks-refactor-q3YlA) — new files only, skipping conflicting modifications to existing hooks/docs: New scripts: scripts/validate_md_versions.py — staged .md version validation scripts/audit_md_version_history.py — full git history audit tool New tests: tests/test_md_version_discipline.py — 39 tests for version discipline New hooks: .githooks/pre-release — pre-release 5-location version sync .githooks/md-version-reminder.sh — PostToolUse advisory reminder .githooks/detect-claude-model.sh — AI model attribution from process args New GitHub metadata: .github/CODEOWNERS — path-based reviewer assignments .github/workflows/release.yml — v* tag → GitHub Release workflow .github/release.yml — auto-generated release notes config .github/ISSUE_TEMPLATE/documentation.yml .github/SUPPORT.md Also fixes assign_executor_property (src/private) — parameter renamed from property_name → option broke callers; renamed back. Telemetry showed 25% error rate (TypeError) on every call with the old parameter name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tests/test_tools.py: update assign_executor_property calls to use property_name kwarg (renamed back from option in prior session) - tests/test_skill.py: update filesystem skill counts 35→36 after new skill added via PR #29 cherry-pick - tests/test_rag_audit_fixes.py: update MAX_RETRY_WAIT expectation 120→300 to match current config - tests/test_rag_query.py: patch rag.store.sqlite.logger (where the dimension warning actually fires); fix assertion to check "dimension" not "mismatch"; implement text fallback in rag_query when embedding search returns empty (implements documented but missing behavior) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Comprehensive hooks and templates overhaul motivated by commit 92c6542 which revealed systemic markdown version drift. This PR adds automated enforcement at every stage of the development lifecycle — from file edit to release.
9 commits, 25+ files changed, ~2,000 lines
1. Markdown Version Discipline
scripts/validate_md_versions.py— blocks commits when.mdcontent changes without version/timestamp bumpscripts/audit_md_version_history.py— full git history audit (87.8% discipline score across 188 commits).githooks/md-version-reminder.sh— Claude Code PostToolUse advisory on.mdeditstests/test_md_version_discipline.py2. Staging Hygiene (pre-commit)
.env,.key,.pem,credentials.jsonfrom being stagedrag/store/,agent_data/,agent_traces/artifactsghp_,sk-,AKIA,AIza) in staged diffs3. Release Workflow
.githooks/pre-release— validates version sync across all 5 locations before tagging.github/workflows/release.yml— tag-triggered CI: lint, test, create GitHub Release.github/release.yml— auto-generated release notes with categoriesmake release VERSION=X.Y.Z— one-command release prep4. Level 3 AI Model Attribution
.githooks/detect-claude-model.sh— detects active model (e.g.claude-opus-4-6) from process args.githooks/prepare-commit-msg— auto-injects structured git trailers:.claude/settings.json— nativeCo-Authored-Byattribution viaattributionfieldgit log --format="%(trailers:key=AI-Model)" | sort -u5. GitHub Templates
.github/CODEOWNERS— path-based reviewer assignment for critical paths.github/ISSUE_TEMPLATE/documentation.yml— structured docs issue template.github/SUPPORT.md— support documentation6. Pre-push Submodule Resilience
src/private/tools via AST scanaudit_md_counts.pyfor missing private files7. Documentation Corrections
Hook Lifecycle Coverage
md-version-reminder.shTest plan
uv run ruff check src/ tests/ rag/— linting passesuv run python -m pytest tests/test_md_version_discipline.py -v— 39/39 tests passuv run python scripts/validate_md_versions.py --staged— validates own editsuv run python scripts/audit_md_version_history.py --summary-only— 87.8% scorebash .githooks/detect-claude-model.sh— returnsclaude-opus-4-6bash .githooks/pre-release 3.35.3— validates current release versionecho "test" > .env && git add .envshould be blockedhttps://claude.ai/code/session_015q4sbkP8HQLhA1BtA1QqeS