From 2f915b3fc459f4338a5a4776f51fd7830591c960 Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Sat, 15 Aug 2026 22:07:14 +0800 Subject: [PATCH] feat(hooks)!: remove post-commit hook from product (#167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refresh policy belongs to the orchestrator/consumer, not the generator: - graph path: loomgraph index already re-exports every run - README_AI path: release-time / manual scan-all (see #166) Removed: hooks install post-commit, hidden 'hooks run', 'hooks rerun', hooks.post_commit config section (5 modes), GH#87 install warning, _enable warning, loop-guard/tree-aware seam machinery and tests. 'uninstall post-commit' still accepted for leftover cleanup. BREAKING CHANGE: users with the hook installed → 'codeindex hooks uninstall post-commit'; refresh indexes via 'codeindex scan-all'. --- CHANGELOG.md | 14 + CLAUDE.md | 3 +- README.md | 8 +- docs/README.md | 2 +- docs/development/team-workflow-guide.md | 6 - docs/guides/advanced-usage.md | 3 +- docs/guides/configuration.md | 7 +- docs/guides/git-hooks-integration.md | 261 +++---------------- examples/.codeindex.yaml | 18 -- examples/CLAUDE.md.template | 32 --- examples/ai-integration-guide.md | 9 +- src/codeindex/cli_config.py | 1 - src/codeindex/cli_config_commands.py | 2 +- src/codeindex/cli_hooks.py | 252 +----------------- src/codeindex/config.py | 65 ----- src/codeindex/config_help.py | 35 +-- src/codeindex/init_wizard.py | 34 +-- src/codeindex/templates/claude_md_core.md | 3 +- src/codeindex/templates/claude_md_core_zh.md | 3 +- src/codeindex/writers/core.py | 2 +- tests/features/help_system.feature | 7 - tests/features/init_wizard.feature | 20 -- tests/test_cli_hooks.py | 133 +--------- tests/test_hook_post_commit.py | 142 ---------- tests/test_hooks_config.py | 141 ---------- tests/test_hooks_rerun.py | 92 ------- tests/test_hooks_run_hidden.py | 52 ---- tests/test_init_wizard_bdd.py | 91 ------- 28 files changed, 78 insertions(+), 1360 deletions(-) delete mode 100644 tests/test_hook_post_commit.py delete mode 100644 tests/test_hooks_config.py delete mode 100644 tests/test_hooks_rerun.py delete mode 100644 tests/test_hooks_run_hidden.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 95322cc..adcda26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Removed (BREAKING) + +- **post-commit hook removed from the product** (GH #167). Refresh policy + belongs to the orchestrator/consumer, not the generator: `loomgraph index` + already re-exports on every run for the graph path, and README_AI refresh + is release-time or manual (`codeindex scan-all`). Removed with it: + `hooks install post-commit`, the hidden `hooks run`, `hooks rerun`, the + `hooks.post_commit` config section (modes auto/disabled/async/sync/prompt), + and the GH #87 install-time enabled-warning. **Migration**: if you had the + hook installed, run `codeindex hooks uninstall post-commit` (still + accepted for cleanup); refresh indexes with `codeindex scan-all` whenever + you want them fresh. The codeindex repo itself refreshes at release time + (`scripts/release.sh` step 6.5, GH #166). + ### Changed - **README_AI refresh moved from per-commit to release-time** (GH #166). diff --git a/CLAUDE.md b/CLAUDE.md index 95b40c5..07460c1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -241,11 +241,10 @@ This project uses [codeindex](https://github.com/dreamlx/codeindex) (v0.36.0) fo ### Commands & escape hatches -Full reference: `codeindex --help`. The non-obvious ones: +Full reference: `codeindex --help`. The non-obvious one: ```bash codeindex scan-all --ai --retry-all # force re-enrich every dir, ignore cache -codeindex hooks rerun post-commit # force-rerun hook vs HEAD (bypasses loop guard) ``` Transient AI failures (`⚠ : AI error`) → re-run `codeindex scan-all --ai`; successes restore from cache, only failures retry. Persistent → swap model in `ai_command` (`.codeindex.yaml`). diff --git a/README.md b/README.md index 629da8d..e2c2ac5 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Full data incl. the failure cases: **[2026-05 benchmark](docs/benchmark/2026-05- - **Two-phase documentation pipeline** (v0.23.0) — Phase 1: structural README_AI.md via SmartWriter; Phase 2: AI generates one-line functional descriptions per module. AI agents can browse README_AI.md hierarchy and find the right module **without grep**. - **Smart indexing** — Tiered documentation (overview → navigation → detailed) optimized for AI agents, ≤10KB per file (navigation index, not a tech doc — see [ADR-005](docs/architecture/adr/005-navigation-disclaimer-and-readme-size-cap.md)) - **Auto-AI enrichment** — When an AI backend is configured (`ai:` section per [ADR-008](docs/architecture/adr/008-direct-http-api-ai-default.md), or `ai_command` CLI escape hatch), `scan-all` automatically enables AI module descriptions. Use `--no-ai` to opt out -- **Auto-update hooks** — Optional post-commit hook (`codeindex hooks install`) regenerates README_AI.md for changed directories. Thin wrapper pattern: `pipx upgrade ai-codeindex` auto-updates hook logic ### Parsing & Analysis @@ -58,7 +57,7 @@ Full data incl. the failure cases: **[2026-05 benchmark](docs/benchmark/2026-05- - **Adaptive symbol extraction** — Dynamic 5–150 symbols per file based on size - **CLAUDE.md injection** — `codeindex init` injects a codeindex section into your **project's** `CLAUDE.md` (never `~/.claude`) -- **Claude Code plugin** — `codeindex:arch` / `:index` / `:hooks` / `:update-guide` skills via [dreamlx/codeindex-claude](https://github.com/dreamlx/codeindex-claude) +- **Claude Code plugin** — `codeindex:arch` / `:index` / `:update-guide` skills via [dreamlx/codeindex-claude](https://github.com/dreamlx/codeindex-claude) - **Template-based test generation** — YAML + Jinja2 for rapid language support (88–91% time savings) - **Parallel scanning** — Concurrent directory processing with configurable workers @@ -124,7 +123,6 @@ codeindex, LoomGraph has nothing to index. See [LoomGraph Integration Guide](doc # Personal developer workflow codeindex init # Setup CLAUDE.md integration codeindex scan-all # Structural + AI descriptions (auto) -codeindex hooks install post-commit # Auto-update on commit # Claude Code reads README_AI.md → understands module purpose → uses Serena for details ``` @@ -141,7 +139,7 @@ pipx install ai-codeindex ``` > **Claude Code users** — also install the companion plugin for skills -> (`codeindex:arch` / `:index` / `:hooks` / `:update-guide`): +> (`codeindex:arch` / `:index` / `:update-guide`): > ``` > /plugin marketplace add dreamlx/codeindex-claude > /plugin install codeindex@codeindex-claude @@ -255,7 +253,6 @@ codeindex affected --since HEAD~5 | `codeindex parse ` | Parse single file to JSON | [LoomGraph Integration](docs/guides/loomgraph-integration.md) | | `codeindex tech-debt ./src` | Code quality analysis (debt + test smells) | Enhanced in v0.22.0 | | `codeindex debt-scan ./src` | Alias for tech-debt | Backward compatibility | -| `codeindex hooks install` | Git hooks for auto-update | [Git Hooks Guide](docs/guides/git-hooks-integration.md) | | `codeindex doctor` | Health/sync check (CLI, parsers, CLAUDE.md, plugin) | Read-only diagnostic | | `codeindex config explain ` | Parameter help | [Configuration Guide](docs/guides/configuration.md) | @@ -274,7 +271,6 @@ codeindex affected --since HEAD~5 |-------|--------------| | `codeindex:arch` | Answer architecture / "where is X" questions from `README_AI.md` | | `codeindex:index` | Walk you through `codeindex init` → `scan-all` | -| `codeindex:hooks` | Set up the auto-update post-commit hook | | `codeindex:update-guide` | Refresh the codeindex section in your project's `CLAUDE.md` | `codeindex init` also injects a codeindex section into your project's `CLAUDE.md` diff --git a/docs/README.md b/docs/README.md index 98ed871..2ba5808 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,7 +19,7 @@ codeindex 文档导航。codeindex 是 [LoomGraph](https://github.com/dreamlx/Lo - **[graph-export](guides/graph-export.md)** — NDJSON graph export 用法与 schema - **[LoomGraph Integration](guides/loomgraph-integration.md)** — 两仓数据流 - **[Claude Code Integration](guides/claude-code-integration.md)** — plugin/skills 集成 -- **[Git Hooks](guides/git-hooks-integration.md)** — pre/post-commit hooks +- **[Git Hooks](guides/git-hooks-integration.md)** — pre-commit / pre-push hooks - **[JSON Output](guides/json-output-integration.md)** — JSON 输出消费 - **[Contributing](guides/contributing.md)** — TDD workflow、代码风格 diff --git a/docs/development/team-workflow-guide.md b/docs/development/team-workflow-guide.md index b15a000..9356800 100644 --- a/docs/development/team-workflow-guide.md +++ b/docs/development/team-workflow-guide.md @@ -160,12 +160,6 @@ Checks **staged Python files only** — fast, targeted. 1. **ruff lint** — style, imports, unused vars, debug code (T100/T201 rules catch `print()` and `breakpoint()`) 2. Reports exact line numbers. Fix then re-stage. -### post-commit (runs after `git commit`) -Runs in background, non-blocking. - -1. **codeindex scan** — regenerates README_AI.md for changed directories -2. Logs to `~/.codeindex/hooks/post-commit.log` (check here if indexes seem stale) - ### pre-push (runs on `git push`) Last gate before code leaves your machine. diff --git a/docs/guides/advanced-usage.md b/docs/guides/advanced-usage.md index 6c66f04..b5b482a 100644 --- a/docs/guides/advanced-usage.md +++ b/docs/guides/advanced-usage.md @@ -404,12 +404,11 @@ codeindex parse myfile.py | jq . Automatically regenerate indexes on commit using codeindex's built-in hooks: ```bash -# Install all hooks (pre-commit + post-commit) +# Install all hooks (pre-commit + pre-push) codeindex hooks install --all # Or install individually codeindex hooks install pre-commit -codeindex hooks install post-commit # Check hook status codeindex hooks status diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 7bd70f1..ef64360 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -445,9 +445,8 @@ Optional improvements available: - AI-powered multi-language documentation normalization - Add `docstrings` section -4. **Git Hooks Configuration** (v0.7.0) - - Configurable post-commit behavior (5 modes) - - Add `hooks:` section or use CLI: `codeindex hooks install --all` +4. **Git Hooks Configuration** (v0.7.0; post-commit modes removed in GH #167) + - Install via CLI: `codeindex hooks install --all` 5. **Config Explain** (v0.14.0) - Interactive help for any config parameter @@ -501,7 +500,7 @@ tech_debt: ```bash # Explain any config parameter (v0.14.0+) codeindex config explain parallel_workers -codeindex config explain hooks.post_commit.mode +codeindex config explain output_file # Future: auto-upgrade codeindex config upgrade # Planned diff --git a/docs/guides/git-hooks-integration.md b/docs/guides/git-hooks-integration.md index 7b8eec5..d9b42f0 100644 --- a/docs/guides/git-hooks-integration.md +++ b/docs/guides/git-hooks-integration.md @@ -1,16 +1,23 @@ # Git Hooks Integration Guide -**Version**: v0.17.2 **Feature**: Automated Git Hooks management --- ## 📋 Overview -codeindex now provides built-in Git Hooks management to automate: -- **Pre-commit**: Lint checks and debug code detection -- **Post-commit**: Automatic README_AI.md updates -- **Pre-push**: Lint and test validation before push +codeindex provides built-in Git Hooks management to automate: +- **Pre-commit**: Lint checks (ruff, includes debug-code rules) +- **Pre-push**: Validation before push + +> **Post-commit hook removed (GH #167)**: README_AI.md auto-refresh on every +> commit was retired — the per-commit frequency was wrong for a navigation +> index, and the refresh machinery (loop guards, config gating) cost more than +> it returned. README_AI refresh is now **release-time or manual**: run +> `codeindex scan-all` whenever you want fresh indexes, or (in the codeindex +> repo itself) let `scripts/release.sh` step 6.5 refresh before each tag. +> **Migration**: `codeindex hooks uninstall post-commit` — the command still +> accepts `post-commit` to clean up an existing install. No manual hook creation needed - install with one command! @@ -29,7 +36,6 @@ Output: Git Hooks Status ○ pre-commit: not installed - ○ post-commit: not installed ○ pre-push: not installed ``` @@ -44,10 +50,9 @@ Output: Installing Git Hooks ✓ pre-commit: installed - ✓ post-commit: installed ✓ pre-push: installed -✓ Successfully installed 3 hook(s) +✓ Successfully installed 2 hook(s) ``` ### Verify Installation @@ -61,10 +66,9 @@ Output: Git Hooks Status ✓ pre-commit: installed - ✓ post-commit: installed ✓ pre-push: installed -→ 3 codeindex hook(s) installed +→ 2 codeindex hook(s) installed ``` --- @@ -116,8 +120,9 @@ Uninstall codeindex Git hooks. **Examples**: ```bash -# Uninstall specific hook -codeindex hooks uninstall pre-commit +# Uninstall specific hook (also works for a leftover post-commit +# installed by codeindex < 0.37 — see the migration note above) +codeindex hooks uninstall post-commit # Uninstall all hooks codeindex hooks uninstall --all @@ -144,15 +149,12 @@ codeindex hooks uninstall --all --keep-backup - Checks only staged Python files - Auto-detects ruff (venv or system) - Provides fix suggestions - -2. **L2: Debug Code Detection** - Forbid debug statements - - Detects: `print()`, `breakpoint()`, `pdb.set_trace()` - - Skips CLI files (legitimate print usage) - - Shows line numbers for violations + - Debug-code detection (print/breakpoint/pdb) is covered by ruff + rules T201/T100 in the lint check **Exit Codes**: - `0` - All checks passed -- `1` - Lint errors or debug code found +- `1` - Lint errors found **Example Output**: ``` @@ -163,49 +165,9 @@ codeindex hooks uninstall --all --keep-backup All checks passed! ✓ Lint check passed -[L2] Checking for debug code... -✓ No debug code found - ✓ All pre-commit checks passed! ``` -### Post-commit Hook - -**Purpose**: Automatic structural documentation updates - -**Architecture** (v0.23.0+): Thin wrapper pattern -- Shell script (~30 lines): loop guard + venv activation -- Python logic via `codeindex hooks run post-commit`: all business logic -- **Upgrade path**: `pipx upgrade ai-codeindex` automatically updates hook behavior (no need to reinstall hooks) - -**Features**: -- Analyzes commit changes (`codeindex affected`) -- Runs `codeindex scan` for affected directories (structural regeneration) -- Creates follow-up commit with updates -- Avoids infinite loops (skips doc-only commits) -- No custom AI prompts — uses standard codeindex scan pipeline - -**Workflow**: -``` -Code Change Commit - ↓ -Shell wrapper (loop guard + venv) - ↓ -codeindex hooks run post-commit (Python) - ↓ stderr → ~/.codeindex/hooks/post-commit.log - ↓ -codeindex affected --json → affected directories - ↓ -codeindex scan for each → structural README_AI.md update - ↓ -Auto-commit: "docs: auto-update README_AI.md for " -``` - -> **Note**: Post-commit hook only updates structural content. AI-generated -> module descriptions (blockquotes) are not regenerated on every commit — -> they describe module purpose which rarely changes. Run `codeindex scan-all` -> (with `ai_command` configured) to refresh AI descriptions. - ### Pre-push Hook **Purpose**: Validation before push @@ -231,11 +193,11 @@ $ codeindex hooks install --all Installing Git Hooks ✓ pre-commit: installed - ✓ post-commit: installed + ✓ pre-push: installed Backups created: pre-commit → pre-commit.backup - post-commit → post-commit.backup + pre-push → pre-push.backup ``` Backup location: `.git/hooks/.backup` @@ -250,11 +212,11 @@ $ codeindex hooks uninstall --all Uninstalling Git Hooks ✓ pre-commit: uninstalled - ✓ post-commit: uninstalled + ✓ pre-push: uninstalled Backups restored: pre-commit ← pre-commit.backup - post-commit ← post-commit.backup + pre-push ← pre-push.backup ``` ### Manual Backup Management @@ -276,99 +238,9 @@ rm .git/hooks/*.backup ## ⚙️ Configuration -### Post-Commit Hook Configuration - -**NEW in v0.7.0** (Story 6): Post-commit hooks are now fully configurable via `.codeindex.yaml`. - -Add to your `.codeindex.yaml`: - -```yaml -hooks: - post_commit: - mode: auto # auto | disabled | async | sync | prompt - max_dirs_sync: 2 # Auto mode threshold (≤2 = sync, >2 = async) - enabled: true # Master switch - log_file: ~/.codeindex/hooks/post-commit.log -``` - -#### Mode Options - -| Mode | Behavior | Use Case | -|------|----------|----------| -| `auto` **(default)** | Smart detection: ≤2 dirs = sync, >2 = async | Balanced UX (non-blocking for large projects) | -| `disabled` | Completely disabled | Temporary disable or CI environments | -| `async` | Always run in background (non-blocking) | Large projects, fast commits | -| `sync` | Always run synchronously (blocking) | Small projects, immediate feedback | -| `prompt` | Only show reminder, don't auto-execute | Manual control, batch updates | - -#### Examples - -**Disable post-commit hook**: -```yaml -hooks: - post_commit: - mode: disabled - enabled: false -``` - -**Always async (non-blocking)**: -```yaml -hooks: - post_commit: - mode: async - log_file: ~/.my-logs/post-commit.log -``` - -**Always sync (blocking)**: -```yaml -hooks: - post_commit: - mode: sync -``` - -**Prompt only (manual updates)**: -```yaml -hooks: - post_commit: - mode: prompt -``` - -**Custom threshold for auto mode**: -```yaml -hooks: - post_commit: - mode: auto - max_dirs_sync: 5 # ≤5 dirs = sync, >5 = async -``` - -#### Async Mode Output - -When async mode is active, you'll see: - -```bash -⚡ Running in async mode (non-blocking) - 3 directories will be updated in background - Log: ~/.codeindex/hooks/post-commit.log - - To check progress: tail -f ~/.codeindex/hooks/post-commit.log - Or wait for completion: while [ -f ~/.codeindex/hooks/post-commit.lock ]; do sleep 1; done - -✓ You can continue working. Updates will commit automatically. -``` - -#### Prompt Mode Output - -When prompt mode is active, you'll see: - -```bash -⚠️ README_AI.md updates available - 3 directories need updating - Run: codeindex scan -``` - ### Pre-Commit Configuration -Pre-commit hooks are not yet configurable via `.codeindex.yaml`. +Hooks are not configurable via `.codeindex.yaml`. To disable lint check, manually edit `.git/hooks/pre-commit` and comment out the L1 section. @@ -396,14 +268,20 @@ git commit -m "feat: add new feature" 🔍 Running pre-commit checks... ✓ All checks passed! -# 5. Post-commit runs (updates README_AI.md) - 📝 Post-commit: Analyzing changes... - ✓ README_AI.md updated - -# 6. Push +# 5. Push git push ``` +### Refreshing README_AI + +README_AI.md no longer refreshes per-commit. Refresh when you want fresh +navigation (before a release, after a big refactor, or whenever): + +```bash +codeindex scan-all # structural, seconds +codeindex scan-all --ai # + AI enrichment (cached per directory) +``` + ### CI/CD Integration Hooks run locally, not in CI. For CI validation: @@ -483,24 +361,6 @@ pip install ruff brew install ruff # macOS ``` -### Post-commit Creates Infinite Loop - -**Problem**: Commits keep triggering more commits - -**Protection Built-in**: Post-commit hook automatically skips if commit only contains documentation files. - -**Manual Fix** (if needed): -```bash -# Temporarily disable post-commit -mv .git/hooks/post-commit .git/hooks/post-commit.disabled - -# Make commits -git commit -m "fix" - -# Re-enable -mv .git/hooks/post-commit.disabled .git/hooks/post-commit -``` - --- ## 🎓 Advanced Usage @@ -523,37 +383,6 @@ fi **Note**: Manual edits will be lost if you reinstall with `--force`. -### Hook Architecture and Upgrades - -**Thin wrapper pattern** (v0.23.0+): - -Post-commit hooks use a thin shell wrapper that delegates to Python: - -``` -.git/hooks/post-commit (shell, ~30 lines) - → loop guard (skip doc-only commits) - → activate venv - → codeindex hooks run post-commit ← Python logic - -codeindex hooks run post-commit (Python, in cli_hooks.py) - → codeindex affected --json - → codeindex scan for each affected dir - → git add + git commit -``` - -**Upgrade behavior**: -- `pipx upgrade ai-codeindex` → Python logic auto-updates, no hook reinstall needed -- `codeindex hooks install --force` → only needed if shell wrapper itself changes (rare) -- Hooks are marked with `# codeindex-managed hook` comment - -**To update hooks to latest version**: - -```bash -# Usually not needed (Python logic auto-updates via pip) -# Only if instructed by release notes: -codeindex hooks install --all --force -``` - ### Multiple Projects Each Git repository has independent hooks: @@ -584,10 +413,10 @@ A: codeindex hooks are independent. You can use both: - codeindex hooks: Runs after **Q: How do I share hooks with my team?** -A: Each developer runs `codeindex hooks install --all` after cloning the repo. Alternatively, `codeindex init` offers to install hooks during interactive setup. +A: Each developer runs `codeindex hooks install --all` after cloning the repo. **Q: Can I disable specific checks?** -A: Pre-commit checks require manual hook editing. Post-commit hooks are fully configurable via `.codeindex.yaml` (see Configuration section above) with 5 modes: `auto`, `disabled`, `async`, `sync`, `prompt`. +A: Hook checks require manual hook editing (see Configuration above). **Q: What happens if I switch branches?** A: Hooks persist across branches (stored in `.git/hooks/`, not tracked by Git). @@ -596,17 +425,10 @@ A: Hooks persist across branches (stored in `.git/hooks/`, not tracked by Git). ## 🎉 Benefits -**Before Git Hooks Integration**: -- ❌ Manual lint checks before commit -- ❌ Debug code slips into commits -- ❌ README_AI.md becomes outdated -- ❌ Inconsistent code quality - -**After Git Hooks Integration**: - ✅ Automatic lint checks (catch errors early) - ✅ Debug code forbidden (cleaner commits) -- ✅ README_AI.md always up-to-date - ✅ Consistent code quality across team +- ✅ README_AI refresh at meaningful moments, not per-commit noise --- @@ -615,8 +437,3 @@ A: Hooks persist across branches (stored in `.git/hooks/`, not tracked by Git). - [Configuration Guide](configuration.md) - [Getting Started Guide](getting-started.md) - [Advanced Usage](advanced-usage.md) - ---- - -**Last Updated**: 2026-03-12 -**Status**: Production Ready (v0.23.0) diff --git a/examples/.codeindex.yaml b/examples/.codeindex.yaml index 44afe90..690c402 100644 --- a/examples/.codeindex.yaml +++ b/examples/.codeindex.yaml @@ -149,24 +149,6 @@ hooks: - lint # Run ruff lint - debug # Check for debug statements - # Post-commit hook - Auto-update README_AI.md - post_commit: - # Mode: auto | disabled | async | sync | prompt - # auto: Smart detection (≤2 dirs = sync, >2 = async) - # disabled: Completely disabled - # async: Always non-blocking (background updates) - # sync: Always blocking (immediate updates) - # prompt: Reminder only, no auto-execution - mode: auto - - # Auto mode threshold: ≤N directories = sync, >N = async - max_dirs_sync: 2 - - enabled: true # Master switch - - # Log file location - log_file: ~/.codeindex/hooks/post-commit.log - # Pre-push hook - Final quality gate pre_push: enabled: false # Disabled by default (placeholder for future) diff --git a/examples/CLAUDE.md.template b/examples/CLAUDE.md.template index 0f3b7ae..be027aa 100644 --- a/examples/CLAUDE.md.template +++ b/examples/CLAUDE.md.template @@ -270,38 +270,6 @@ codeindex scan-all loomgraph index . ``` -### Git Hooks (Optional) - -Automatically update documentation on commit: - -Create `.git/hooks/post-commit`: -```bash -#!/bin/bash -# Auto-update codeindex documentation after commit - -# Only update if non-documentation files changed -CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD) -NON_DOC_FILES=$(echo "$CHANGED_FILES" | grep -v "README_AI.md" || true) - -if [ -n "$NON_DOC_FILES" ]; then - echo "🔄 Updating code documentation..." - codeindex scan-all --fallback - - # Auto-commit if documentation changed - if ! git diff --quiet README_AI.md; then - git add README_AI.md - git commit -m "docs: auto-update README_AI.md - -Co-Authored-By: Claude Code " - fi -fi -``` - -Make it executable: -```bash -chmod +x .git/hooks/post-commit -``` - --- ## 📊 Project Statistics diff --git a/examples/ai-integration-guide.md b/examples/ai-integration-guide.md index a74e66c..62cca0e 100644 --- a/examples/ai-integration-guide.md +++ b/examples/ai-integration-guide.md @@ -177,7 +177,7 @@ After adding the section: Future AI sessions in this project will now understand: - Git Hooks are installed - - How pre-commit and post-commit work + - How pre-commit works - Expected behaviors and troubleshooting ``` @@ -200,8 +200,6 @@ codeindex Git Hooks ├── pre-commit │ ├── L1: Lint Check (ruff) │ └── L2: Debug Code Detection -├── post-commit -│ └── Auto-update README_AI.md └── Management ├── install/uninstall commands ├── Automatic backups @@ -212,7 +210,6 @@ codeindex Git Hooks Hooks are installed to `.git/hooks/`: - `.git/hooks/pre-commit` - Runs before commit -- `.git/hooks/post-commit` - Runs after commit - Marker: `# codeindex-managed hook` (identifies codeindex hooks) ### Pre-commit: Quality Gate @@ -326,8 +323,8 @@ git commit -m "docs: update guide" After reading this guide, you should: - [ ] Understand what codeindex Git Hooks do -- [ ] Know how pre-commit and post-commit work -- [ ] Understand expected behaviors (auto-commits are normal) +- [ ] Know how pre-commit works +- [ ] Understand expected behaviors - [ ] Know how to handle hook failures - [ ] Have updated user's CLAUDE.md/AGENTS.md with the section above diff --git a/src/codeindex/cli_config.py b/src/codeindex/cli_config.py index d11e284..e960105 100644 --- a/src/codeindex/cli_config.py +++ b/src/codeindex/cli_config.py @@ -194,7 +194,6 @@ def init(force: bool, yes: bool, quiet: bool, help_config: bool, lang: str, dry_ detected_frameworks=detected_frameworks, parallel_workers=parallel_workers, batch_size=batch_size, - enable_hooks=False, # Hooks are opt-in via `codeindex hooks install` create_codeindex_md=False, # Dropped from init (B1/ADR-006); CLAUDE.md is the guide # ADR-008: leave ai_command unset so generate_config_yaml emits the # direct-API `ai:` section (DeepSeek default). The prior claude-CLI diff --git a/src/codeindex/cli_config_commands.py b/src/codeindex/cli_config_commands.py index 4408f09..cc982f4 100644 --- a/src/codeindex/cli_config_commands.py +++ b/src/codeindex/cli_config_commands.py @@ -27,7 +27,7 @@ def explain(parameter: str): Examples: codeindex config explain parallel_workers codeindex config explain batch_size - codeindex config explain hooks.post_commit.mode + codeindex config explain output_file """ # Try to get current value from config current_value = None diff --git a/src/codeindex/cli_hooks.py b/src/codeindex/cli_hooks.py index 553b7f1..efa268f 100644 --- a/src/codeindex/cli_hooks.py +++ b/src/codeindex/cli_hooks.py @@ -9,10 +9,7 @@ - Detect and merge with existing hooks """ -import json -import logging import shutil -import subprocess from datetime import datetime from enum import Enum from pathlib import Path @@ -22,8 +19,6 @@ from .cli_common import console -logger = logging.getLogger(__name__) - class HookStatus(Enum): """Status of a Git hook.""" @@ -37,7 +32,7 @@ class HookManager: """Manage Git hooks for codeindex.""" CODEINDEX_MARKER = "# codeindex-managed hook" - SUPPORTED_HOOKS = ["pre-commit", "post-commit", "pre-push"] + SUPPORTED_HOOKS = ["pre-commit", "pre-push"] def __init__(self, repo_path: Optional[Path] = None): """ @@ -200,8 +195,6 @@ def generate_hook_script( if hook_name == "pre-commit": return _generate_pre_commit_script(config) - elif hook_name == "post-commit": - return _generate_post_commit_script(config) elif hook_name == "pre-push": return _generate_pre_push_script(config) else: @@ -300,55 +293,6 @@ def _generate_pre_commit_script(config: dict) -> str: return script -def _generate_post_commit_script(config: dict) -> str: # noqa: E501 - """Generate post-commit hook script.""" - auto_update = config.get("auto_update", True) - - if not auto_update: - return """#!/usr/bin/env bash -# codeindex-managed hook -# Post-commit hook (disabled) -exit 0 -""" - - return """#!/usr/bin/env bash -# codeindex-managed hook -# Post-commit hook for codeindex -# Thin wrapper — all logic in Python (auto-updated via pip) - -# Avoid infinite loop: skip if last commit only contains README_AI.md. -# -m is required so merge commits enumerate per-parent changes — without it -# `git diff-tree -r HEAD` returns empty on every merge commit and the hook -# silently skips every PR merge in a GitFlow project (GH #84). -LAST_COMMIT_FILES=$(git diff-tree --no-commit-id --name-only -r -m HEAD) -NON_DOC_FILES=$(echo "$LAST_COMMIT_FILES" | \\ - grep -v "README_AI.md" | grep -v "PROJECT_INDEX.md" || true) -if [ -z "$NON_DOC_FILES" ]; then - exit 0 -fi - -# Set up working directory -REPO_ROOT=$(git rev-parse --show-toplevel) -cd "$REPO_ROOT" - -# Try to activate virtual environment -if [ -f "$REPO_ROOT/.venv/bin/activate" ]; then - source "$REPO_ROOT/.venv/bin/activate" -elif [ -f "$REPO_ROOT/venv/bin/activate" ]; then - source "$REPO_ROOT/venv/bin/activate" -fi - -# Ensure log directory exists -LOG_DIR="$HOME/.codeindex/hooks" -mkdir -p "$LOG_DIR" -LOG_FILE="$LOG_DIR/post-commit.log" - -# Delegate to Python (upgradeable via pip) -# Errors go to log file instead of being silently discarded -codeindex hooks run post-commit 2>>"$LOG_FILE" || true -""" - - def _generate_pre_push_script(config: dict) -> str: """Generate pre-push hook script.""" return """#!/usr/bin/env bash @@ -450,103 +394,6 @@ def uninstall_hook(hook_name: str, repo_path: Optional[Path] = None) -> bool: return manager.uninstall_hook(hook_name, restore_backup=True) -def run_post_commit_hook() -> int: - """Execute post-commit hook logic in Python. - - This is called by the thin wrapper shell script via - `codeindex hooks run post-commit`. All logic lives here so that - `pipx upgrade ai-codeindex` automatically updates the behavior. - - Affected directories are re-rendered through the same tree-aware seam - as ``scan-all`` (GH #160): one writer, one world-view. The previous - per-dir ``codeindex scan`` subprocess hardcoded ``level="detailed"`` - and overwrote scan-all's overview/navigation READMEs, oscillating hub - directories between a 350-line symbol dump and a 64-line navigation - aggregate on every commit. - - Returns: - Exit code (0 = success) - """ - # Step 1: Get affected directories - try: - result = subprocess.run( - ["codeindex", "affected", "--json"], - capture_output=True, text=True, timeout=30, - ) - if result.returncode != 0: - return 0 # Silently skip on error - - analysis = json.loads(result.stdout) - except (subprocess.TimeoutExpired, json.JSONDecodeError, FileNotFoundError): - return 0 - - level = analysis.get("level", "skip") - affected_dirs = analysis.get("affected_dirs", []) - - if level == "skip" or not affected_dirs: - return 0 - - # Step 2: Re-render affected directories through the tree-aware seam - # (same code path as scan-all, so hook output is byte-consistent with - # it — correct levels, 0-symbol skip, stale-README cleanup). - repo_root = Path.cwd() - from .cli_scan import _process_directory_with_smartwriter - from .config import Config - from .directory_tree import DirectoryTree - - try: - config = Config.load() - tree = DirectoryTree(repo_root, config) - except Exception: - return 0 - - updated_readmes: list[str] = [] - for dir_path in affected_dirs: - target = repo_root / dir_path - if not target.is_dir(): - continue - - # The seam isolates per-dir failures (try/except → result tuple). - _, success, _, _ = _process_directory_with_smartwriter( - target, tree, config - ) - readme_path = target / config.output_file - if success and readme_path.exists(): - updated_readmes.append(str(readme_path)) - - if not updated_readmes: - return 0 - - # Step 3: Stage and commit updated README_AI.md files - try: - for readme in updated_readmes: - subprocess.run(["git", "add", readme], capture_output=True, timeout=10) - - # Check if there are actual staged changes - diff_result = subprocess.run( - ["git", "diff", "--cached", "--quiet"], - capture_output=True, timeout=10, - ) - if diff_result.returncode == 0: - return 0 # No actual changes - - commit_hash = subprocess.run( - ["git", "rev-parse", "--short", "HEAD"], - capture_output=True, text=True, timeout=10, - ).stdout.strip() - - subprocess.run( - ["git", "commit", "--no-verify", "-m", - f"docs: auto-update README_AI.md for {commit_hash}\n\n" - f"Updated by post-commit hook.\nUpdate level: {level}"], - capture_output=True, timeout=30, - ) - except (subprocess.TimeoutExpired, FileNotFoundError): - pass - - return 0 - - # ============================================================================ # CLI Commands # ============================================================================ @@ -558,48 +405,6 @@ def hooks(): pass -def _maybe_warn_post_commit_disabled(project_dir: Path) -> None: - """Print a reminder if ``hooks.post_commit.enabled`` is false in - ``.codeindex.yaml``. - - ``codeindex init`` ships the yaml with ``enabled: false`` by default, - so the installed ``.git/hooks/post-commit`` wrapper no-ops at runtime - even though the install command printed ✓. User commits, READMEs don't - update, "it doesn't work" — see GH #87. - - We don't flip the flag automatically (contract change, see #75 for the - boundary). The reminder makes the contract visible at install time so - the user can decide. - """ - yaml_path = project_dir / ".codeindex.yaml" - if not yaml_path.exists(): - return - - try: - import yaml - - with open(yaml_path) as f: - data = yaml.safe_load(f) or {} - except Exception: - # Yaml unreadable or parse error — silent skip; the install itself - # succeeded, we don't want this advisory to mask the real result. - return - - enabled = data.get("hooks", {}).get("post_commit", {}).get("enabled", False) - if enabled: - return - - console.print( - "[yellow]⚠[/yellow] [bold]post_commit.enabled is false in " - ".codeindex.yaml[/bold] — the installed hook wrapper checks this " - "flag at runtime and will no-op until you flip it.\n" - " Edit [cyan].codeindex.yaml[/cyan]:\n" - " hooks:\n" - " post_commit:\n" - " [bold]enabled: true[/bold]\n" - ) - - @hooks.command() @click.option( "--all", @@ -686,13 +491,6 @@ def install(hook_name: Optional[str], install_all: bool, force: bool): f"[dim]→ Skipped {skipped_count} already installed hook(s)[/dim]\n" ) - # GH #87: surface the runtime-disabled trap. If post-commit was just - # installed (or was already installed), the wrapper still no-ops when - # .codeindex.yaml has post_commit.enabled=false (the init default). - # Without this reminder, install prints ✓ but commits trigger nothing. - if "post-commit" in hooks_to_install: - _maybe_warn_post_commit_disabled(Path.cwd()) - except ValueError as e: console.print(f"[red]✗[/red] Error: {e}", style="red") raise click.Abort() @@ -784,54 +582,6 @@ def uninstall(hook_name: Optional[str], uninstall_all: bool, keep_backup: bool): raise click.Abort() -@hooks.command("run", hidden=True) # internal: called by shell hook scripts, not users (GH #34) -@click.argument("hook_name") -def run_hook(hook_name: str): - """Run hook logic (called by thin wrapper scripts). - - This is not intended for direct user invocation. - The shell hook script delegates to this command so that - hook logic can be updated via pipx upgrade ai-codeindex. - - Example: codeindex hooks run post-commit - """ - if hook_name == "post-commit": - exit_code = run_post_commit_hook() - raise SystemExit(exit_code) - else: - console.print(f"[yellow]No run handler for hook: {hook_name}[/yellow]") - raise SystemExit(0) - - -@hooks.command("rerun") -@click.argument("hook_name") -def rerun_hook(hook_name: str): - """Force-rerun a hook against HEAD (user escape hatch, GH #89). - - The installed shell hook wrapper has guards (doc-only commit loop guard; - config gates) that can cause a commit's README_AI.md update to be skipped. - ``rerun`` calls the Python hook logic directly — bypassing the shell - wrapper — so the update fires regardless of those guards. - - Use when the post-commit hook didn't fire on a commit: - - \b - - doc-only commit skipped by the loop guard - - retroactive populate after flipping ``hooks.post_commit.enabled`` to true - - a historical stale README (e.g. predating the #84 merge-commit fix) - - For a full re-scan (every directory) use ``codeindex scan-all``. - - Example: codeindex hooks rerun post-commit - """ - if hook_name == "post-commit": - exit_code = run_post_commit_hook() - raise SystemExit(exit_code) - else: - console.print(f"[yellow]No run handler for hook: {hook_name}[/yellow]") - raise SystemExit(0) - - @hooks.command() def status(): """Show status of Git hooks.""" diff --git a/src/codeindex/config.py b/src/codeindex/config.py index 08b1b79..fb7aa02 100644 --- a/src/codeindex/config.py +++ b/src/codeindex/config.py @@ -163,14 +163,6 @@ root: overview # 根目录:只有概述 module: navigation # 模块目录:导航 + 关键类 leaf: detailed # 叶子目录:完整信息 - -# Git Hooks configuration (Story 6) -hooks: - post_commit: - mode: auto # auto | disabled | async | sync | prompt - max_dirs_sync: 2 # Auto mode: ≤2 dirs = sync, >2 = async - enabled: true # Master switch - log_file: ~/.codeindex/hooks/post-commit.log """ @@ -439,61 +431,6 @@ def from_dict(cls, data: dict, global_ai_command: str = "") -> "DocstringConfig" ) -@dataclass -class PostCommitConfig: - """Configuration for post-commit Git hook. - - Modes: - - auto: Smart detection (≤2 dirs = sync, >2 = async) [default] - - disabled: Completely disabled - - async: Always run in background (non-blocking) - - sync: Always run synchronously (blocking) - - prompt: Only show reminder, don't auto-execute - """ - - mode: str = "auto" # auto | disabled | async | sync | prompt - enabled: bool = True # Master switch - max_dirs_sync: int = 2 # Threshold for auto mode - log_file: str = "~/.codeindex/hooks/post-commit.log" - - @classmethod - def from_dict(cls, data: dict) -> "PostCommitConfig": - """Create from config dict.""" - if not data: - return cls() - - mode = data.get("mode", "auto") - valid_modes = ("auto", "disabled", "async", "sync", "prompt") - if mode not in valid_modes: - raise ValueError( - f"Invalid post_commit mode: {mode}. Must be one of {valid_modes}" - ) - - return cls( - mode=mode, - enabled=data.get("enabled", True), - max_dirs_sync=data.get("max_dirs_sync", 2), - log_file=data.get("log_file", "~/.codeindex/hooks/post-commit.log"), - ) - - -@dataclass -class HooksConfig: - """Configuration for Git hooks (Story 6).""" - - post_commit: PostCommitConfig = field(default_factory=PostCommitConfig) - - @classmethod - def from_dict(cls, data: dict) -> "HooksConfig": - """Create from config dict.""" - if not data: - return cls() - - return cls( - post_commit=PostCommitConfig.from_dict(data.get("post_commit", {})) - ) - - @dataclass class Config: """Configuration for codeindex.""" @@ -510,7 +447,6 @@ class Config: incremental: IncrementalConfig = field(default_factory=IncrementalConfig) indexing: IndexingConfig = field(default_factory=IndexingConfig) docstrings: DocstringConfig = field(default_factory=DocstringConfig) # Epic 9 - hooks: HooksConfig = field(default_factory=HooksConfig) # Story 6 parallel_workers: int = DEFAULT_PARALLEL_WORKERS batch_size: int = DEFAULT_BATCH_SIZE @@ -544,7 +480,6 @@ def load(cls, path: Optional[Path | str] = None) -> "Config": docstrings=DocstringConfig.from_dict( data.get("docstrings", {}), global_ai_command=ai_command ), - hooks=HooksConfig.from_dict(data.get("hooks", {})), parallel_workers=data.get("parallel_workers", DEFAULT_PARALLEL_WORKERS), batch_size=data.get("batch_size", DEFAULT_BATCH_SIZE), ) diff --git a/src/codeindex/config_help.py b/src/codeindex/config_help.py index c715cd6..1808c0e 100644 --- a/src/codeindex/config_help.py +++ b/src/codeindex/config_help.py @@ -90,31 +90,6 @@ - "**/__pycache__/**" - "**/node_modules/**" - "**/.git/**\"""", - }, - "hooks.post_commit.enabled": { - "name": "hooks.post_commit.enabled", - "type": "bool", - "default": "false", - "description": "Enable/disable Git post-commit hook", - "example": """hooks: - post_commit: - enabled: true""", - }, - "hooks.post_commit.mode": { - "name": "hooks.post_commit.mode", - "type": "string", - "default": "auto", - "options": "auto, disabled, async, sync, prompt", - "description": "Git Hooks execution mode", - "mode_descriptions": """ - • auto: Smart mode based on change size - • disabled: Skip all updates - • async: Background updates (non-blocking) - • sync: Wait for updates to complete - • prompt: Ask user before running""", - "example": """hooks: - post_commit: - mode: auto""", }, "output_file": { "name": "output_file", @@ -147,10 +122,6 @@ def show_full_config_help() -> None: _show_param_section("include") _show_param_section("exclude") - console.print("\n[bold]Git Hooks[/bold] 🔗\n") - _show_param_section("hooks.post_commit.enabled") - _show_param_section("hooks.post_commit.mode") - console.print("\n[bold]Output[/bold] 📝\n") _show_param_section("output_file") @@ -246,10 +217,6 @@ def explain_parameter( if "trade_offs" in param: console.print(f"\n[bold]Trade-off:[/bold]{param['trade_offs']}") - # Show mode descriptions (for hooks.post_commit.mode) - if "mode_descriptions" in param: - console.print(f"\n[bold]Modes:[/bold]{param['mode_descriptions']}") - # Show examples if "example" in param: console.print(f"\n[bold]Example:[/bold]\n```yaml\n{param['example']}\n```") @@ -281,7 +248,7 @@ def get_current_config_value(param_name: str, config_path: Optional[str] = None) else: config = Config.load() - # Navigate nested attributes (e.g., hooks.post_commit.mode) + # Navigate nested attributes (e.g. adaptive_symbols.enabled) parts = param_name.split(".") value = config diff --git a/src/codeindex/init_wizard.py b/src/codeindex/init_wizard.py index 965ff18..6983184 100644 --- a/src/codeindex/init_wizard.py +++ b/src/codeindex/init_wizard.py @@ -43,8 +43,6 @@ class WizardResult: detected_frameworks: List[str] = field(default_factory=list) parallel_workers: int = 8 batch_size: int = 50 - enable_hooks: bool = False - hooks_mode: str = "auto" create_codeindex_md: bool = False inject_claude_md: bool = True configure_ai: bool = False @@ -53,7 +51,6 @@ class WizardResult: config_created: bool = False codeindex_md_created: bool = False claude_md_injected: bool = False - hooks_installed: bool = False # ============================================================================ @@ -583,17 +580,6 @@ def generate_config_yaml(result: WizardResult, project_dir: Path) -> str: "", ]) - # Git Hooks configuration - yaml_lines.extend([ - "# Git Hooks configuration", - "hooks:", - " post_commit:", - f" enabled: {str(result.enable_hooks).lower()}", - ]) - if result.enable_hooks: - yaml_lines.append(f" mode: {result.hooks_mode}") - yaml_lines.append("") - yaml_lines.append("output_file: README_AI.md") return "\n".join(yaml_lines) @@ -657,28 +643,16 @@ def create_codeindex_md(project_dir: Path) -> Path: --- -## 🔄 Auto-Update Hooks - -Keep README_AI.md in sync with code changes automatically: +## 🔄 Keeping README_AI Fresh ```bash -# Install post-commit hook -codeindex hooks install post-commit +# Re-run the scan whenever you want fresh navigation indexes +codeindex scan-all -# Check hook status +# Check hook status (pre-commit lint / pre-push tests) codeindex hooks status ``` -When installed, README_AI.md files auto-update on every commit. -Configure behavior in `.codeindex.yaml`: - -```yaml -hooks: - post_commit: - enabled: true - mode: auto # auto | sync | async | prompt | disabled -``` - --- ## 🎯 Best Practices for AI Agents diff --git a/src/codeindex/templates/claude_md_core.md b/src/codeindex/templates/claude_md_core.md index 4f993fc..af0fc41 100644 --- a/src/codeindex/templates/claude_md_core.md +++ b/src/codeindex/templates/claude_md_core.md @@ -8,11 +8,10 @@ This project uses [codeindex](https://github.com/dreamlx/codeindex) (v{version}) ### Commands & escape hatches -Full reference: `codeindex --help`. The non-obvious ones: +Full reference: `codeindex --help`. The non-obvious one: ```bash codeindex scan-all --ai --retry-all # force re-enrich every dir, ignore cache -codeindex hooks rerun post-commit # force-rerun hook vs HEAD (bypasses loop guard) ``` Transient AI failures (`⚠ : AI error`) → re-run `codeindex scan-all --ai`; successes restore from cache, only failures retry. Persistent → swap model in `ai_command` (`.codeindex.yaml`). diff --git a/src/codeindex/templates/claude_md_core_zh.md b/src/codeindex/templates/claude_md_core_zh.md index 574b2ae..a065fe1 100644 --- a/src/codeindex/templates/claude_md_core_zh.md +++ b/src/codeindex/templates/claude_md_core_zh.md @@ -8,11 +8,10 @@ ### 命令与 escape hatch -完整参考:`codeindex --help`。非显而易见的两个: +完整参考:`codeindex --help`。非显而易见的一个: ```bash codeindex scan-all --ai --retry-all # 强制重新 enrich 每个目录,忽略缓存 -codeindex hooks rerun post-commit # 对 HEAD 强制重跑 hook(绕过 loop guard) ``` 瞬时 AI 失败(`⚠ : AI error`)→ 重跑 `codeindex scan-all --ai`,成功目录走缓存只重试失败的;持续失败 → 改 `.codeindex.yaml` 的 `ai_command` 换模型。 diff --git a/src/codeindex/writers/core.py b/src/codeindex/writers/core.py index cb80174..faad2ee 100644 --- a/src/codeindex/writers/core.py +++ b/src/codeindex/writers/core.py @@ -148,7 +148,7 @@ def write_readme( # Preserve AI enrichment across structural rewrites (GH #38). # `scan-all --ai` injects an "ok" marker + `> description` blockquote; - # a later structural-only write (post-commit hook, or Phase 1 of the + # a later structural-only write (structural rescan, or Phase 1 of the # next --ai run) must not wipe them, or the idempotent cache goes cold # and the next --ai re-pays the full N AI calls. Capture before # overwrite, re-inject after. diff --git a/tests/features/help_system.feature b/tests/features/help_system.feature index e18a2ef..67162a6 100644 --- a/tests/features/help_system.feature +++ b/tests/features/help_system.feature @@ -54,13 +54,6 @@ Feature: Enhanced Help System And the output should contain "Default: 50" And the output should contain "Trade-off" - Scenario: Explain hooks.post_commit.mode parameter - When I run "codeindex config explain hooks.post_commit.mode" - Then the output should contain "auto" - And the output should contain "disabled" - And the output should contain "async" - And the output should contain "sync" - Scenario: Explain non-existent parameter When I run "codeindex config explain nonexistent" Then the output should contain "Unknown parameter" diff --git a/tests/features/init_wizard.feature b/tests/features/init_wizard.feature index 0e825e5..8a36935 100644 --- a/tests/features/init_wizard.feature +++ b/tests/features/init_wizard.feature @@ -57,20 +57,16 @@ Feature: Interactive Setup Wizard When I run the interactive wizard And I select Python as language And I accept default include patterns - And I disable Git Hooks Then a .codeindex.yaml should be created And it should contain language: python And it should contain suggested include patterns - And hooks.post_commit.enabled should be false Scenario: Create .codeindex.yaml with all features enabled When I run the interactive wizard And I select Python and PHP as languages And I accept default patterns - And I enable Git Hooks with auto mode And I request CODEINDEX.md creation Then .codeindex.yaml should be created with both languages - And hooks.post_commit.mode should be auto And CODEINDEX.md should be created # ============================================================================ @@ -123,22 +119,6 @@ Feature: Interactive Setup Wizard And I skip CODEINDEX.md creation Then CODEINDEX.md should not be created - # ============================================================================ - # Scenario Group 7: Git Hooks Installation - # ============================================================================ - - Scenario: Install Git Hooks with auto mode - When I run the interactive wizard - And I enable Git Hooks with auto mode - Then Git Hooks should be installed - And post-commit hook should be configured with mode auto - - Scenario: Skip Git Hooks installation - When I run the interactive wizard - And I skip Git Hooks installation - Then Git Hooks should not be installed - And hooks.post_commit.enabled should be false - # ============================================================================ # Scenario Group 8: Wizard Success Metrics # ============================================================================ diff --git a/tests/test_cli_hooks.py b/tests/test_cli_hooks.py index b772ec5..44d0e80 100644 --- a/tests/test_cli_hooks.py +++ b/tests/test_cli_hooks.py @@ -183,14 +183,13 @@ def test_list_all_hooks_status(self, tmp_path): (hooks_dir / "pre-commit").write_text("# codeindex-managed hook\n") # Create one custom hook - (hooks_dir / "post-commit").write_text("#!/bin/bash\necho 'custom'\n") + (hooks_dir / "pre-push").write_text("#!/bin/bash\necho 'custom'\n") manager = HookManager(repo_path) statuses = manager.list_all_hooks() assert statuses["pre-commit"] == HookStatus.INSTALLED - assert statuses["post-commit"] == HookStatus.CUSTOM - assert statuses["pre-push"] == HookStatus.NOT_INSTALLED + assert statuses["pre-push"] == HookStatus.CUSTOM class TestHookGeneration: @@ -204,14 +203,6 @@ def test_generate_pre_commit_hook(self): assert "codeindex-managed hook" in script assert "ruff" in script.lower() or "lint" in script.lower() - def test_generate_post_commit_hook(self): - """Should generate valid post-commit hook script.""" - script = generate_hook_script("post-commit") - - assert script.startswith("#!/") - assert "codeindex-managed hook" in script - assert "README_AI.md" in script or "codeindex" in script - def test_generate_hook_with_config(self): """Should customize hook based on config.""" config = { @@ -223,40 +214,6 @@ def test_generate_hook_with_config(self): assert "codeindex-managed hook" in script - def test_post_commit_loop_guard_handles_merge_commits(self): - """Regression for GH #84. - - ``git diff-tree --no-commit-id --name-only -r HEAD`` returns empty - on merge commits by default — it needs ``-m`` to enumerate the - per-parent changes. Without the flag, the loop guard sees zero - files on every merge commit, hits the "all docs" early-exit, and - the wrapper never delegates to ``codeindex hooks run post-commit``. - - Net effect on GitFlow projects: ``README_AI.md`` files never - auto-update on PR merges (the commits that bring new code to - ``main`` / ``develop``). Reported live on fabricOS HEAD ``171702b``. - """ - script = generate_hook_script("post-commit") - - # The broken pattern: -r HEAD without -m. Asserted as a literal - # substring so future whitespace/format tweaks don't accidentally - # let it back in. - assert "diff-tree --no-commit-id --name-only -r HEAD" not in script, ( - "Post-commit loop guard uses `git diff-tree -r HEAD` without -m. " - "On merge commits this returns empty and the hook silently skips " - "(GH #84). Use `-m` or `git show --name-only`." - ) - - # The fix: either -m on diff-tree OR git show --name-only. Both - # produce non-empty output on merge commits. - has_dash_m = "diff-tree" in script and " -m " in script - has_git_show = "git show --name-only" in script - assert has_dash_m or has_git_show, ( - "Post-commit hook must enumerate files on merge commits. " - "Expected either `git diff-tree ... -m HEAD` or " - "`git show --name-only ... HEAD` in the loop guard (GH #84)." - ) - class TestBackupAndRestore: """Test backup and restore functionality.""" @@ -296,12 +253,12 @@ def test_detect_existing_hooks(self, tmp_path): """Should detect all existing hooks.""" hooks_dir = tmp_path (hooks_dir / "pre-commit").write_text("#!/bin/bash\necho 'test'") - (hooks_dir / "post-commit").write_text("#!/bin/bash\necho 'test'") + (hooks_dir / "pre-push").write_text("#!/bin/bash\necho 'test'") detected = detect_existing_hooks(hooks_dir) assert "pre-commit" in detected - assert "post-commit" in detected + assert "pre-push" in detected assert len(detected) == 2 def test_detect_ignores_samples(self, tmp_path): @@ -331,85 +288,3 @@ def test_cli_hooks_status_command(self, mock_run, tmp_path): """Should provide hooks status CLI command.""" # This will be implemented with Click pass - - -class TestPostCommitEnabledWarning: - """GH #87 — ``codeindex hooks install post-commit`` must surface the - runtime-disabled trap. - - ``codeindex init`` writes ``.codeindex.yaml`` with - ``hooks.post_commit.enabled: false`` by default. Without a reminder at - install time, users see the install ✓, make commits, and nothing - happens because the wrapper checks the flag at runtime. These tests - cover the three meaningful states (disabled / enabled / no yaml).""" - - def _yaml(self, enabled: bool) -> str: - return ( - "version: 1\n" - "hooks:\n" - " post_commit:\n" - f" enabled: {str(enabled).lower()}\n" - ) - - def test_install_warns_when_yaml_disables_post_commit(self, tmp_path): - from click.testing import CliRunner - - from codeindex.cli import main - - (tmp_path / ".git" / "hooks").mkdir(parents=True) - (tmp_path / ".codeindex.yaml").write_text(self._yaml(enabled=False)) - - runner = CliRunner() - original_cwd = os.getcwd() - try: - os.chdir(tmp_path) - result = runner.invoke(main, ["hooks", "install", "post-commit"]) - finally: - os.chdir(original_cwd) - - assert result.exit_code == 0, result.output - assert "post_commit.enabled is false" in result.output, result.output - # The user-visible fix (the yaml snippet) must be present. - assert "enabled: true" in result.output - - def test_install_silent_when_yaml_enables_post_commit(self, tmp_path): - from click.testing import CliRunner - - from codeindex.cli import main - - (tmp_path / ".git" / "hooks").mkdir(parents=True) - (tmp_path / ".codeindex.yaml").write_text(self._yaml(enabled=True)) - - runner = CliRunner() - original_cwd = os.getcwd() - try: - os.chdir(tmp_path) - result = runner.invoke(main, ["hooks", "install", "post-commit"]) - finally: - os.chdir(original_cwd) - - assert result.exit_code == 0, result.output - # No reminder when the user has explicitly enabled it. - assert "post_commit.enabled is false" not in result.output - - def test_install_silent_when_no_yaml(self, tmp_path): - """Without ``.codeindex.yaml`` we don't know the project's intent; - the install itself succeeds, advisory stays silent — matches the - rule "don't mask the real result of a non-yaml-driven command".""" - from click.testing import CliRunner - - from codeindex.cli import main - - (tmp_path / ".git" / "hooks").mkdir(parents=True) - # No .codeindex.yaml. - - runner = CliRunner() - original_cwd = os.getcwd() - try: - os.chdir(tmp_path) - result = runner.invoke(main, ["hooks", "install", "post-commit"]) - finally: - os.chdir(original_cwd) - - assert result.exit_code == 0, result.output - assert "post_commit.enabled is false" not in result.output diff --git a/tests/test_hook_post_commit.py b/tests/test_hook_post_commit.py deleted file mode 100644 index 3418b44..0000000 --- a/tests/test_hook_post_commit.py +++ /dev/null @@ -1,142 +0,0 @@ -"""Tests for post-commit hook: thin wrapper + Python logic (Epic 25). - -The post-commit hook should: -1. Be a thin shell wrapper calling `codeindex hooks run post-commit` -2. Python logic handles: affected dirs → codeindex scan → auto-commit -3. No custom AI prompts, no git diff injection -""" - -from unittest.mock import MagicMock, patch - -from codeindex.cli_hooks import ( - _generate_post_commit_script, - generate_hook_script, - run_post_commit_hook, -) - - -class TestThinWrapperScript: - """The generated hook script should be a thin wrapper.""" - - def test_calls_codeindex_hooks_run(self): - """Hook script delegates to `codeindex hooks run post-commit`.""" - script = _generate_post_commit_script({}) - assert "codeindex hooks run post-commit" in script - - def test_no_custom_ai_prompt(self): - """Hook script must not contain custom AI prompts.""" - script = _generate_post_commit_script({}) - assert "PROMPT" not in script - assert "Code Diff" not in script - assert "git diff HEAD" not in script - - def test_still_has_loop_guard(self): - """Hook script still guards against infinite commit loops.""" - script = _generate_post_commit_script({}) - assert "README_AI.md" in script - - def test_disabled_config(self): - """Disabled config generates exit-only script.""" - script = _generate_post_commit_script({"auto_update": False}) - assert "exit 0" in script - - def test_has_codeindex_marker(self): - """Generated script contains codeindex marker for management.""" - script = generate_hook_script("post-commit") - assert "codeindex-managed hook" in script - - -class TestRunPostCommitHook: - """Python-side post-commit logic (tree-aware since GH #160).""" - - @staticmethod - def _affected_json(dirs): - import json as _json - return _json.dumps({"level": "affected", "affected_dirs": dirs}) - - @staticmethod - def _fake_subprocess(affected_dirs, staged_changes=False): - """Dispatch subprocess.run: affected query answered, git ops succeed.""" - def fake_run(cmd, *args, **kwargs): - if "affected" in cmd: - return MagicMock(returncode=0, - stdout=TestRunPostCommitHook._affected_json(affected_dirs)) - if "diff" in cmd: # git diff --cached --quiet: 1 = has changes - return MagicMock(returncode=1 if staged_changes else 0, stdout="") - return MagicMock(returncode=0, stdout="") - return fake_run - - @patch("codeindex.cli_hooks.subprocess.run") - def test_skips_when_no_affected_dirs(self, mock_run): - """No affected dirs → no render, no commit.""" - mock_run.return_value = MagicMock( - returncode=0, - stdout='{"level": "skip", "affected_dirs": []}', - ) - result = run_post_commit_hook() - assert result == 0 - - @patch("codeindex.cli_hooks.Path.cwd") - @patch("codeindex.cli_hooks.subprocess.run") - def test_hub_dir_renders_navigation_not_detailed(self, mock_run, mock_cwd, tmp_path): - """GH #160 regression: a dir with indexed children must keep its - navigation-level README — the old per-dir `codeindex scan` subprocess - hardcoded detailed and overwrote scan-all's hierarchy.""" - (tmp_path / "src" / "auth" / "sub").mkdir(parents=True) - (tmp_path / "src" / "auth" / "__init__.py").write_text("def a():\n pass\n") - (tmp_path / "src" / "auth" / "sub" / "mod.py").write_text("def b():\n pass\n") - mock_cwd.return_value = tmp_path - mock_run.side_effect = self._fake_subprocess(["src/auth"]) - - run_post_commit_hook() - - content = (tmp_path / "src" / "auth" / "README_AI.md").read_text() - assert "(navigation)" in content - - @patch("codeindex.cli_hooks.Path.cwd") - @patch("codeindex.cli_hooks.subprocess.run") - def test_new_dir_without_readme_gets_one(self, mock_run, mock_cwd, tmp_path): - """New dirs (no prior README) are rendered, not skipped — the old - `readme_path.exists()` guard left freshly added source dirs unindexed.""" - pkg = tmp_path / "src" / "newpkg" - pkg.mkdir(parents=True) - (pkg / "mod.py").write_text("def f():\n pass\n") - mock_cwd.return_value = tmp_path - mock_run.side_effect = self._fake_subprocess(["src/newpkg"]) - - run_post_commit_hook() - - assert (pkg / "README_AI.md").exists() - - @patch("codeindex.cli_hooks.Path.cwd") - @patch("codeindex.cli_hooks.subprocess.run") - def test_zero_symbol_dir_stale_readme_removed(self, mock_run, mock_cwd, tmp_path): - """0-symbol skip (GH #158) is inherited from the shared seam.""" - empty = tmp_path / "src" / "empty" - empty.mkdir(parents=True) - (empty / "__init__.py").write_text("") - (empty / "README_AI.md").write_text("# stale\n") - mock_cwd.return_value = tmp_path - mock_run.side_effect = self._fake_subprocess(["src/empty"]) - - run_post_commit_hook() - - assert not (empty / "README_AI.md").exists() - - @patch("codeindex.cli_hooks.Path.cwd") - @patch("codeindex.cli_hooks.subprocess.run") - def test_no_codeindex_scan_subprocess(self, mock_run, mock_cwd, tmp_path): - """Rendering is in-process now — no per-dir `codeindex scan` spawn.""" - pkg = tmp_path / "src" / "pkg" - pkg.mkdir(parents=True) - (pkg / "mod.py").write_text("def f():\n pass\n") - mock_cwd.return_value = tmp_path - mock_run.side_effect = self._fake_subprocess(["src/pkg"]) - - run_post_commit_hook() - - for call in mock_run.call_args_list: - cmd = call.args[0] - # tmp_path itself contains "scan" (pytest dir naming) — match the - # invocation shape, not a substring. - assert not (cmd[0].endswith("codeindex") and len(cmd) > 1 and cmd[1] == "scan") diff --git a/tests/test_hooks_config.py b/tests/test_hooks_config.py deleted file mode 100644 index 9c62d20..0000000 --- a/tests/test_hooks_config.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Tests for hooks configuration. - -Story 6: Git Hooks performance optimization - Config support. -""" - - -from codeindex.config import Config, HooksConfig - - -class TestHooksConfig: - """Test hooks configuration loading and defaults.""" - - def test_default_hooks_config(self): - """Should have default hooks configuration.""" - config = HooksConfig() - - assert config.post_commit.mode == "auto" - assert config.post_commit.max_dirs_sync == 2 - assert config.post_commit.enabled is True - - def test_hooks_config_disabled_mode(self): - """Should support disabled mode.""" - config = HooksConfig.from_dict( - {"post_commit": {"mode": "disabled", "enabled": False}} - ) - - assert config.post_commit.mode == "disabled" - assert config.post_commit.enabled is False - - def test_hooks_config_async_mode(self): - """Should support async mode.""" - config = HooksConfig.from_dict({"post_commit": {"mode": "async"}}) - - assert config.post_commit.mode == "async" - - def test_hooks_config_sync_mode(self): - """Should support sync mode.""" - config = HooksConfig.from_dict({"post_commit": {"mode": "sync"}}) - - assert config.post_commit.mode == "sync" - - def test_hooks_config_prompt_mode(self): - """Should support prompt mode.""" - config = HooksConfig.from_dict({"post_commit": {"mode": "prompt"}}) - - assert config.post_commit.mode == "prompt" - - def test_hooks_config_custom_threshold(self): - """Should support custom max_dirs_sync threshold.""" - config = HooksConfig.from_dict({"post_commit": {"max_dirs_sync": 5}}) - - assert config.post_commit.max_dirs_sync == 5 - - def test_hooks_config_custom_log_file(self): - """Should support custom log file path.""" - log_path = "~/.my-logs/post-commit.log" - config = HooksConfig.from_dict({"post_commit": {"log_file": log_path}}) - - assert config.post_commit.log_file == log_path - - def test_config_loads_hooks_from_yaml(self, tmp_path): - """Should load hooks configuration from .codeindex.yaml.""" - config_file = tmp_path / ".codeindex.yaml" - config_file.write_text( - """ -version: 1 -hooks: - post_commit: - mode: async - max_dirs_sync: 3 - log_file: ~/.codeindex/hooks/my-log.log -""" - ) - - config = Config.load(str(config_file)) - - assert config.hooks.post_commit.mode == "async" - assert config.hooks.post_commit.max_dirs_sync == 3 - assert config.hooks.post_commit.log_file == "~/.codeindex/hooks/my-log.log" - - def test_config_with_disabled_hooks(self, tmp_path): - """Should support completely disabled hooks.""" - config_file = tmp_path / ".codeindex.yaml" - config_file.write_text( - """ -version: 1 -hooks: - post_commit: - mode: disabled - enabled: false -""" - ) - - config = Config.load(str(config_file)) - - assert config.hooks.post_commit.mode == "disabled" - assert config.hooks.post_commit.enabled is False - - def test_config_with_prompt_mode(self, tmp_path): - """Should support prompt-only mode.""" - config_file = tmp_path / ".codeindex.yaml" - config_file.write_text( - """ -version: 1 -hooks: - post_commit: - mode: prompt -""" - ) - - config = Config.load(str(config_file)) - - assert config.hooks.post_commit.mode == "prompt" - assert config.hooks.post_commit.enabled is True - - -class TestPostCommitConfig: - """Test PostCommitConfig specifics.""" - - def test_valid_modes(self): - """Should only accept valid modes.""" - valid_modes = ["auto", "disabled", "async", "sync", "prompt"] - - for mode in valid_modes: - config = HooksConfig.from_dict({"post_commit": {"mode": mode}}) - assert config.post_commit.mode == mode - - def test_auto_mode_is_default(self): - """Auto mode should be the default (smart detection).""" - config = HooksConfig() - assert config.post_commit.mode == "auto" - - def test_max_dirs_sync_default(self): - """max_dirs_sync should default to 2.""" - config = HooksConfig() - assert config.post_commit.max_dirs_sync == 2 - - def test_enabled_default_true(self): - """Post-commit hook should be enabled by default.""" - config = HooksConfig() - assert config.post_commit.enabled is True diff --git a/tests/test_hooks_rerun.py b/tests/test_hooks_rerun.py deleted file mode 100644 index 0b350e4..0000000 --- a/tests/test_hooks_rerun.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Tests for ``codeindex hooks rerun`` — the user-facing post-commit escape -hatch (GH #89). - -``hooks run`` is the hidden internal command the shell hook script delegates -to (GH #34). ``hooks rerun`` is its visible counterpart: a documented escape -hatch for when the post-commit hook didn't fire on a commit (doc-only commit -skipped by the shell wrapper's loop guard, a historical stale README from -before the #84 merge-commit fix, or retroactive populate after flipping -``enabled: false → true``). It calls the same Python logic (``run_post_commit_hook``) -directly, so it bypasses the shell wrapper's doc-only loop guard. -""" - -import re - -from click.testing import CliRunner - -from codeindex.cli_hooks import hooks - -# Click's --help lists subcommands under a "Commands:" header, each as an -# indented line " ". Grab the name column of that block (same -# extraction as tests/test_hooks_run_hidden.py, GH #34). -_COMMANDS_BLOCK = re.compile( - r"Commands:\n((?:[ \t]+\S.*\n?)+)", re.MULTILINE -) - - -def _advertised(help_text: str) -> list[str]: - m = _COMMANDS_BLOCK.search(help_text) - if not m: - return [] - return [ln.strip().split()[0] for ln in m.group(1).splitlines() if ln.strip()] - - -class TestHooksRerun: - def test_rerun_advertised_in_help(self): - """GH #89: ``rerun`` is the user escape hatch and must be visible in - ``hooks --help`` (unlike the hidden ``run``).""" - result = CliRunner().invoke(hooks, ["--help"]) - assert result.exit_code == 0 - advertised = _advertised(result.output) - assert "rerun" in advertised, ( - f"`hooks rerun` is the user-facing escape hatch and must be " - f"advertised in `hooks --help`. Advertised: {advertised}" - ) - - def test_run_still_hidden(self): - """``run`` stays hidden (#34 — shell contract); ``rerun`` is the - user-facing one. Both coexist: run=internal/hidden, rerun=user/visible.""" - result = CliRunner().invoke(hooks, ["--help"]) - advertised = _advertised(result.output) - assert "run" not in advertised, ( - f"`hooks run` stays hidden (shell-internal, GH #34); the user " - f"surface is `rerun`. Advertised: {advertised}" - ) - - def test_rerun_post_commit_calls_run_post_commit_hook(self, tmp_path, monkeypatch): - """``rerun post-commit`` delegates to ``run_post_commit_hook`` (the - Python logic), not via the shell wrapper — so the shell's doc-only - loop guard is bypassed by construction.""" - import os - - called: list[int] = [] - - def fake_run() -> int: - called.append(1) - return 0 - - from codeindex import cli_hooks - - monkeypatch.setattr(cli_hooks, "run_post_commit_hook", fake_run) - - original = os.getcwd() - os.chdir(tmp_path) - try: - result = CliRunner().invoke(hooks, ["rerun", "post-commit"]) - finally: - os.chdir(original) - assert result.exit_code == 0, result.output - assert called == [1], "rerun post-commit must call run_post_commit_hook" - - def test_rerun_unknown_hook_is_handled(self, tmp_path): - """Unknown hook name prints a message, exits 0 (mirrors `run`).""" - import os - - original = os.getcwd() - os.chdir(tmp_path) - try: - result = CliRunner().invoke(hooks, ["rerun", "pre-push"]) - finally: - os.chdir(original) - assert "No run handler" in result.output - assert result.exit_code == 0 diff --git a/tests/test_hooks_run_hidden.py b/tests/test_hooks_run_hidden.py deleted file mode 100644 index 6674f93..0000000 --- a/tests/test_hooks_run_hidden.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Tests for GH #34 — hide the internal `hooks run` subcommand from --help. - -`codeindex hooks run` is invoked by the generated shell hook scripts, not by -users. Surfacing it in `hooks --help` is noise that reads like a user-facing -command. Pin it as `hidden=True` while keeping the invocation working (the -shell scripts depend on it). -""" - -import re - -from click.testing import CliRunner - -from codeindex.cli_hooks import hooks - -# Click's --help lists subcommands under a "Commands:" header, each as an -# indented line " ". Grab the name column of that block. -_COMMANDS_BLOCK = re.compile( - r"Commands:\n((?:[ \t]+\S.*\n?)+)", re.MULTILINE -) - - -def _advertised_subcommands(help_text: str) -> list[str]: - m = _COMMANDS_BLOCK.search(help_text) - if not m: - return [] - return [ln.strip().split()[0] for ln in m.group(1).splitlines() if ln.strip()] - - -class TestHooksRunHidden: - def test_run_absent_from_help(self): - result = CliRunner().invoke(hooks, ["--help"]) - assert result.exit_code == 0 - advertised = _advertised_subcommands(result.output) - assert "run" not in advertised, ( - f"`hooks run` is internal (called by shell hook scripts) and must " - f"not be advertised in `hooks --help`. Advertised: {advertised}" - ) - - def test_run_still_invokable(self, tmp_path): - """The shell hook scripts call `hooks run `; hiding must not - disable it. post-commit on a bare dir exits 0 via the no-op guard.""" - import os - - original = os.getcwd() - os.chdir(tmp_path) - try: - result = CliRunner().invoke(hooks, ["run", "post-commit"]) - finally: - os.chdir(original) - # Unknown-command would be exit code 2 with "No such command". - assert "No such command" not in result.output - assert result.exit_code in (0, 1) diff --git a/tests/test_init_wizard_bdd.py b/tests/test_init_wizard_bdd.py index 7f69b5c..21bc33c 100644 --- a/tests/test_init_wizard_bdd.py +++ b/tests/test_init_wizard_bdd.py @@ -32,7 +32,6 @@ def wizard_context(): "user_choices": {}, "config_created": False, "codeindex_md_created": False, - "hooks_installed": False, "start_time": None, "end_time": None, } @@ -209,12 +208,6 @@ def accept_default_includes(wizard_context): wizard_context["user_choices"]["accept_defaults"] = True -@when("I disable Git Hooks") -def disable_git_hooks(wizard_context): - """Disable Git Hooks installation.""" - wizard_context["user_choices"]["enable_hooks"] = False - - @when(parsers.parse("I select {lang1} and {lang2} as languages")) def select_multiple_languages(wizard_context, lang1, lang2): """Select multiple languages.""" @@ -230,13 +223,6 @@ def accept_default_patterns(wizard_context): wizard_context["user_choices"]["accept_patterns"] = True -@when(parsers.parse("I enable Git Hooks with {mode} mode")) -def enable_git_hooks(wizard_context, mode): - """Enable Git Hooks with specified mode.""" - wizard_context["user_choices"]["enable_hooks"] = True - wizard_context["user_choices"]["hooks_mode"] = mode - - @when("I request CODEINDEX.md creation") def request_codeindex_md(wizard_context): """Request CODEINDEX.md file creation.""" @@ -267,12 +253,6 @@ def skip_codeindex_md(wizard_context): wizard_context["user_choices"]["create_codeindex_md"] = False -@when("I skip Git Hooks installation") -def skip_git_hooks(wizard_context): - """Skip Git Hooks installation.""" - wizard_context["user_choices"]["enable_hooks"] = False - - @when("I make all default choices") def make_default_choices(wizard_context): """Accept all default choices in wizard.""" @@ -377,10 +357,6 @@ def config_created(wizard_context): choices = wizard_context["user_choices"] if "languages" in choices: result.detected_languages = choices["languages"] - if "enable_hooks" in choices: - result.enable_hooks = choices["enable_hooks"] - if "hooks_mode" in choices: - result.hooks_mode = choices["hooks_mode"] if "configure_ai" in choices: result.configure_ai = choices["configure_ai"] if "ai_command" in choices: @@ -418,38 +394,6 @@ def config_contains_includes(wizard_context): assert len(config.include) > 0 -@then("hooks.post_commit.enabled should be false") -def hooks_disabled(wizard_context): - """Verify hooks are disabled in config.""" - from codeindex.init_wizard import generate_config_yaml - - project_dir = wizard_context["project_dir"] - config_file = project_dir / ".codeindex.yaml" - - # Generate config if not exists - if not config_file.exists(): - result = wizard_context.get("wizard_result") - if result and wizard_context.get("user_choices"): - choices = wizard_context["user_choices"] - if "enable_hooks" in choices: - result.enable_hooks = choices["enable_hooks"] - - if result: - yaml_content = generate_config_yaml(result, project_dir) - config_file.write_text(yaml_content) - - config = Config.load(config_file) - assert config.hooks.post_commit.enabled is False - - -@then("hooks.post_commit.mode should be auto") -def hooks_mode_auto(wizard_context): - """Verify hooks mode is set to auto.""" - project_dir = wizard_context["project_dir"] - config = Config.load(project_dir / ".codeindex.yaml") - assert config.hooks.post_commit.mode == "auto" - - @then(".codeindex.yaml should be created with both languages") def config_with_both_languages(wizard_context): """Verify config has both languages.""" @@ -464,10 +408,6 @@ def config_with_both_languages(wizard_context): choices = wizard_context["user_choices"] if "languages" in choices: result.detected_languages = choices["languages"] - if "enable_hooks" in choices: - result.enable_hooks = choices["enable_hooks"] - if "hooks_mode" in choices: - result.hooks_mode = choices["hooks_mode"] if "create_codeindex_md" in choices: result.create_codeindex_md = choices["create_codeindex_md"] @@ -600,37 +540,6 @@ def codeindex_md_not_created(wizard_context): assert not codeindex_file.exists() -@then("Git Hooks should be installed") -def git_hooks_installed(wizard_context): - """Verify Git Hooks were installed.""" - # This will check for .git/hooks/post-commit existence - project_dir = wizard_context["project_dir"] - hooks_dir = project_dir / ".git" / "hooks" - if hooks_dir.exists(): - post_commit = hooks_dir / "post-commit" - assert post_commit.exists() - wizard_context["hooks_installed"] = True - - -@then(parsers.parse("post-commit hook should be configured with mode {mode}")) -def post_commit_mode(wizard_context, mode): - """Verify post-commit hook mode.""" - project_dir = wizard_context["project_dir"] - config = Config.load(project_dir / ".codeindex.yaml") - assert config.hooks.post_commit.mode == mode - - -@then("Git Hooks should not be installed") -def git_hooks_not_installed(wizard_context): - """Verify Git Hooks were not installed.""" - project_dir = wizard_context["project_dir"] - hooks_dir = project_dir / ".git" / "hooks" - if hooks_dir.exists(): - post_commit = hooks_dir / "post-commit" - # Either doesn't exist or is not codeindex hook - assert not post_commit.exists() or "codeindex" not in post_commit.read_text() - - @then("the wizard should complete") def wizard_completes(wizard_context): """Verify wizard completed successfully."""