Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **Post-commit removal swept the remaining active docs** (codex review of
#167): `codeindex init`'s final output no longer promises "Auto-update
README_AI.md on commit"; `README_zh.md` (feature bullet, workflow block,
plugin-skill table row — the English README was already clean),
`examples/ai-integration-guide.md` (10+ mentions incl. a whole
Post-commit section and doc-only scenario), and
`docs/development/team-workflow-guide.md` ("Three hooks" → two) no
longer advertise the removed workflow.
- **`hooks status` survives an unreadable leftover hook** (codex review):
the leftover check's `read_text` now catches `OSError` instead of
crashing the informational status output.
- **JS-test detection prunes venvs and shares the walk skip-set** (codex
review of #165): `_has_js_test_files` no longer walks `.venv`/`venv`/
`.tox`/`.eggs`, and the skip list is one shared constant with
`detect_languages` so the two walkers cannot drift.
- **Leftover post-commit hooks surfaced** (GH #167 follow-up). A
`.git/hooks/post-commit` installed by codeindex < 0.37 survives the
upgrade silently — the dead wrapper's errors go to
Expand Down
3 changes: 0 additions & 3 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ codeindex 是一个**开源 CLI**,通过**两阶段流水线**把任意代码
- **两阶段文档流水线**(v0.23.0)——阶段 1:通过 SmartWriter 生成结构化 README_AI.md;阶段 2:AI 为每个模块生成一行式功能描述。AI agent 可以浏览 README_AI.md 层级结构并找到正确的模块,**无需 grep**。
- **智能索引**——分层文档(概览 → 导航 → 详细),为 AI agent 优化,每个文件 ≤10KB(导航索引,而非技术文档——见 [ADR-005](docs/architecture/adr/005-navigation-disclaimer-and-readme-size-cap.md))
- **自动 AI 增强**——当配置了 `ai_command` 时,`scan-all` 会自动启用 AI 模块描述。使用 `--no-ai` 退出
- **自动更新 hook**——可选的 post-commit hook(`codeindex hooks install`)为变更的目录重新生成 README_AI.md。薄包装模式:`pipx upgrade ai-codeindex` 自动更新 hook 逻辑

### 解析与分析

Expand Down Expand Up @@ -123,7 +122,6 @@ codeindex 是无状态的解析层;LoomGraph 是自包含的知识图谱(SQL
# 个人开发者工作流
codeindex init # 配置 CLAUDE.md 集成
codeindex scan-all # 结构化 + AI 描述(自动)
codeindex hooks install post-commit # 提交时自动更新
# Claude Code 读取 README_AI.md → 理解模块用途 → 用 Serena 查细节
```

Expand Down Expand Up @@ -270,7 +268,6 @@ codeindex affected --since HEAD~5
|-------|--------------|
| `codeindex:arch` | 从 `README_AI.md` 回答架构 / "X 在哪里" 类问题 |
| `codeindex:index` | 引导你完成 `codeindex init` → `scan-all` |
| `codeindex:hooks` | 配置自动更新的 post-commit hook |
| `codeindex:update-guide` | 刷新你项目 `CLAUDE.md` 中的 codeindex 章节 |

`codeindex init` 还会把 codeindex 章节注入到你项目的 `CLAUDE.md`,
Expand Down
2 changes: 1 addition & 1 deletion docs/development/team-workflow-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ They run in CI but are excluded from the fast loop to keep feedback tight.

## 5. Git Hooks: Automated QA on Every Commit

Three hooks run automatically. You cannot commit or push without passing them.
Two hooks run automatically. You cannot commit or push without passing them.

### pre-commit (runs on `git commit`)
Checks **staged Python files only** — fast, targeted.
Expand Down
63 changes: 2 additions & 61 deletions examples/ai-integration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

**codeindex** is a code indexing tool that generates AI-friendly documentation. It provides **Git Hooks management** for automating:
- Pre-commit: Code quality checks (lint + debug code detection)
- Post-commit: Automatic README_AI.md updates

**Your task**: Update the user's project CLAUDE.md or AGENTS.md so future AI sessions understand how Git Hooks work.

Expand Down Expand Up @@ -75,46 +74,19 @@ ruff check --fix src/
git commit --no-verify -m "urgent fix"
```

#### Post-commit Hook

Runs **after every commit** to keep documentation updated:

1. Analyzes commit changes automatically
2. Updates `README_AI.md` for affected directories
3. Creates follow-up commit: `docs: auto-update README_AI.md for <hash>`
4. Prevents infinite loops (skips if only doc files changed)

**Expected behavior**:
```bash
# You commit
git commit -m "feat: add new feature"
# Pre-commit checks...
# ✓ All checks passed!

# Post-commit runs automatically
# 📝 Post-commit: Analyzing changes...
# → Updating src/README_AI.md
# ✓ Auto-commit created
```

**This is normal!** The auto-commit is expected behavior.

### For AI Code: Important Behaviors

**When committing code**:
1. **Hooks run automatically** - No action needed
2. **If lint errors**: Fix errors before retrying commit
3. **Post-commit auto-commits**: Expected, don't be alarmed
4. **Bypass hooks**: Only use `--no-verify` in emergencies
3. **Bypass hooks**: Only use `--no-verify` in emergencies

**Typical commit flow**:
```bash
git add src/myfile.py
git commit -m "feat: add feature"
# → Pre-commit runs (lint + debug check)
# → If passed: commit succeeds
# → Post-commit runs (doc update)
# → May create auto-commit
```

**Handling failures**:
Expand Down Expand Up @@ -224,19 +196,6 @@ Hooks are installed to `.git/hooks/`:
- `0` - Pass, commit proceeds
- `1` - Fail, commit blocked

### Post-commit: Auto Documentation

**Runs**: After commit succeeds
**Process**:
1. Analyze commit: `codeindex affected --json`
2. Get affected directories
3. Update README_AI.md for each
4. Create follow-up commit

**Loop Prevention**:
- Checks if commit only touched doc files
- Skips if only `README_AI.md` or `PROJECT_INDEX.md` changed

---

## 💡 Common Scenarios
Expand All @@ -254,11 +213,7 @@ git commit -m "feat: add feature"
# ✓ Lint passed
# ✓ No debug code

# Post-commit runs
# → Updates src/README_AI.md
# → Creates auto-commit

# Result: 2 commits (original + auto-doc)
# Result: commit succeeds, docs stay clean
```

### Scenario 2: Lint Errors
Expand Down Expand Up @@ -302,20 +257,6 @@ git commit -m "feat: add feature"
# ✓ Success
```

### Scenario 4: Doc-Only Commit

```bash
# Only update docs
vim docs/guide.md
git commit -m "docs: update guide"

# Pre-commit runs (no Python files, skipped)
# Post-commit runs
# ✓ Changes below threshold, skipping update

# Result: 1 commit (no auto-doc needed)
```

---

## 🔧 Integration Checklist
Expand Down
2 changes: 1 addition & 1 deletion src/codeindex/cli_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def _print_post_init_message():
console.print(" 2. [cyan]codeindex scan-all[/cyan] → Generate documentation indexes")
console.print(" 3. [cyan]codeindex status[/cyan] → Check coverage")
console.print("\n[dim]Optional:[/dim]")
console.print(" • [cyan]codeindex hooks install[/cyan] → Auto-update README_AI.md on commit")
console.print(" • [cyan]codeindex hooks install[/cyan] → Lint hooks (pre-commit / pre-push)")
console.print(
" • [dim]Claude Code:[/dim] [cyan]/plugin install codeindex@codeindex-claude[/cyan]"
)
Expand Down
5 changes: 4 additions & 1 deletion src/codeindex/cli_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,10 @@ def status():
for name in manager.RETIRED_HOOKS:
hook_path = manager.hooks_dir / name
if hook_path.exists():
content = hook_path.read_text()
try:
content = hook_path.read_text()
except OSError:
continue # unreadable — status is informational, don't crash
if manager.CODEINDEX_MARKER in content:
console.print(
f"[yellow]⚠[/yellow] {name}: leftover from a removed "
Expand Down
16 changes: 13 additions & 3 deletions src/codeindex/init_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ def get_parser_install_guidance(languages: list[str]) -> dict:

return result


# Directories never worth walking: dependencies / caches / venvs. Shared by
# detect_languages and _has_js_test_files so one list cannot drift (GH #165).
_WALK_SKIP_DIRS = {"node_modules", ".git", "__pycache__", ".venv", "venv", ".tox", ".eggs"}


def detect_languages(project_dir: Path, max_scan_files: int = 1000) -> List[str]:
"""Auto-detect programming languages in the project.

Expand All @@ -177,7 +183,7 @@ def detect_languages(project_dir: Path, max_scan_files: int = 1000) -> List[str]
scanned_files = 0

# Common directories to skip
skip_dirs = {"node_modules", ".git", "__pycache__", ".venv", "venv", ".tox", ".eggs"}
skip_dirs = _WALK_SKIP_DIRS

for root, dirs, files in os.walk(project_dir):
# Remove skip directories from walk
Expand Down Expand Up @@ -270,10 +276,14 @@ def infer_include_patterns(project_dir: Path) -> List[str]:


def _has_js_test_files(project_dir: Path) -> bool:
"""True if the project contains JS/TS co-located test files or a __tests__ dir."""
"""True if the project contains JS/TS co-located test files or a __tests__ dir.

Uncapped walk (early-exit on first hit) is fine here: ``codeindex init``
is a one-shot interactive command and a later scan walks the tree anyway.
"""
suffixes = (".spec.ts", ".spec.tsx", ".spec.js", ".test.ts", ".test.tsx", ".test.js")
for root, dirs, files in os.walk(project_dir):
dirs[:] = [d for d in dirs if d not in ("node_modules", ".git", "__pycache__")]
dirs[:] = [d for d in dirs if d not in _WALK_SKIP_DIRS]
if "__tests__" in dirs:
return True
if any(f.endswith(suffixes) for f in files):
Expand Down
15 changes: 15 additions & 0 deletions tests/test_cli_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,18 @@ def test_uninstall_all_removes_leftover(self, tmp_path):

assert result.exit_code == 0
assert not (repo / ".git" / "hooks" / "post-commit").exists()

def test_status_survives_unreadable_leftover(self, tmp_path):
"""codex review, #167 follow-up: an unreadable leftover must not
crash status (it is informational output)."""
repo = self._make_repo(tmp_path)
self._write_leftover(repo)

from unittest.mock import patch as mock_patch

with mock_patch(
"pathlib.Path.read_text", side_effect=PermissionError("denied")
):
result = self._run_cli(repo, ["status"])

assert result.exit_code == 0
13 changes: 13 additions & 0 deletions tests/test_init_wizard_exclude.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,16 @@ def test_explain_exclude_lists_test_patterns() -> None:

assert "**/*.spec.ts" in text
assert "**/__tests__/**" in text


def test_venv_spec_files_do_not_trigger_suggestion(tmp_path: Path) -> None:
"""A spec file inside a venv is not the user's test — pruned like
detect_languages prunes venvs (codex review, GH #165 follow-up)."""
(tmp_path / "src").mkdir()
(tmp_path / "src" / "main.py").write_text("x = 1\n")
(tmp_path / ".venv").mkdir()
(tmp_path / ".venv" / "some-lib.spec.ts").write_text("describe('dep');\n")

excludes = infer_exclude_patterns(tmp_path)

assert not any(p in excludes for p in JS_TS_TEST_PATTERNS)
Loading