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."""