From 5ccb325560b1f1c4d8b87115bc0203b6d2902b21 Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 13:53:04 +0800 Subject: [PATCH 1/8] fix(hooks): redirect post-commit stderr to log file instead of /dev/null Errors from post-commit hook were silently discarded, making debugging impossible. Now writes to ~/.codeindex/hooks/post-commit.log. Also cleaned up 5 stale files from .git/hooks/. Closes #36 (P0) Co-Authored-By: Claude Opus 4.6 --- docs/guides/git-hooks-integration.md | 1 + src/codeindex/cli_hooks.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/guides/git-hooks-integration.md b/docs/guides/git-hooks-integration.md index eff4f5d..b4655ea 100644 --- a/docs/guides/git-hooks-integration.md +++ b/docs/guides/git-hooks-integration.md @@ -192,6 +192,7 @@ 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 ↓ diff --git a/src/codeindex/cli_hooks.py b/src/codeindex/cli_hooks.py index 41371f1..2fe407d 100644 --- a/src/codeindex/cli_hooks.py +++ b/src/codeindex/cli_hooks.py @@ -375,8 +375,14 @@ def _generate_post_commit_script(config: dict) -> str: # noqa: E501 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) -codeindex hooks run post-commit 2>/dev/null || true +# Errors go to log file instead of being silently discarded +codeindex hooks run post-commit 2>>"$LOG_FILE" || true """ From 165852ae8f9bb5d61beb164492f4ec8da425922e Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 13:53:04 +0800 Subject: [PATCH 2/8] docs: auto-update README_AI.md for 5ccb325 Updated by post-commit hook. Update level: current --- src/codeindex/README_AI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codeindex/README_AI.md b/src/codeindex/README_AI.md index 64f970e..17888e8 100644 --- a/src/codeindex/README_AI.md +++ b/src/codeindex/README_AI.md @@ -1,4 +1,4 @@ - + # codeindex From 31b45309c84982205f8b8e2b2f069fe0313ea046 Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 14:18:26 +0800 Subject: [PATCH 3/8] refactor(hooks): unify hook installation and version-control hook-common.sh - Add scripts/hooks/hook-common.sh to version control - HookManager._ensure_hook_common() copies it on install - Makefile install-hooks now delegates to `codeindex hooks install --all` - Add test for hook-common.sh copy behavior Closes #36 (P1) Co-Authored-By: Claude Opus 4.6 --- Makefile | 6 +- scripts/hooks/hook-common.sh | 121 +++++++++++++++++++++++++++++++++++ src/codeindex/cli_hooks.py | 12 ++++ tests/test_cli_hooks.py | 20 ++++++ 4 files changed, 155 insertions(+), 4 deletions(-) create mode 100755 scripts/hooks/hook-common.sh diff --git a/Makefile b/Makefile index 977ee79..0a34f17 100644 --- a/Makefile +++ b/Makefile @@ -39,11 +39,9 @@ install: ## Install package in editable mode install-dev: ## Install with dev dependencies pip install -e ".[dev,all]" -install-hooks: ## Install Git hooks (pre-commit, pre-push) +install-hooks: ## Install Git hooks via codeindex CLI @echo "$(CYAN)Installing Git hooks...$(RESET)" - @mkdir -p .git/hooks - @cp scripts/hooks/pre-push .git/hooks/pre-push - @chmod +x .git/hooks/pre-push + @codeindex hooks install --all --force @echo "$(GREEN)✓ Git hooks installed$(RESET)" # ============================================================================ diff --git a/scripts/hooks/hook-common.sh b/scripts/hooks/hook-common.sh new file mode 100755 index 0000000..4d1a974 --- /dev/null +++ b/scripts/hooks/hook-common.sh @@ -0,0 +1,121 @@ +#!/bin/zsh +# Common utilities for Git hooks +# Source this file in all hooks to reduce code duplication + +# ============================================ +# Color Definitions +# ============================================ +export RED='\033[0;31m' +export GREEN='\033[0;32m' +export YELLOW='\033[0;33m' +export CYAN='\033[0;36m' +export RESET='\033[0m' + +# ============================================ +# Repository Utilities +# ============================================ + +# Get repository root directory +get_repo_root() { + git rev-parse --show-toplevel +} + +# ============================================ +# Virtual Environment Management +# ============================================ + +# Activate virtual environment (required for all hooks) +# Returns 0 on success, 1 on failure +activate_venv() { + local repo_root=$(get_repo_root) + + if [ -f "$repo_root/.venv/bin/activate" ]; then + source "$repo_root/.venv/bin/activate" + return 0 + elif [ -f "$repo_root/venv/bin/activate" ]; then + source "$repo_root/venv/bin/activate" + return 0 + else + echo -e "${RED}✗ Virtual environment not found${RESET}" + echo -e "${YELLOW}→ Create with: python3 -m venv .venv${RESET}" + echo -e "${YELLOW}→ Install: pip install -e '.[dev,all]'${RESET}" + return 1 + fi +} + +# ============================================ +# Tool Discovery +# ============================================ + +# Find a tool (prefer venv, fallback to system) +# Usage: find_tool ruff +# Returns: path to tool or empty string (sets exit code) +find_tool() { + local tool=$1 + local repo_root=$(get_repo_root) + + if [ -f "$repo_root/.venv/bin/$tool" ]; then + echo "$repo_root/.venv/bin/$tool" + return 0 + elif command -v $tool &> /dev/null; then + echo "$tool" + return 0 + else + echo -e "${RED}✗ $tool not found${RESET}" >&2 + echo -e "${YELLOW}→ Install: pip install $tool${RESET}" >&2 + return 1 + fi +} + +# ============================================ +# Time Measurement +# ============================================ + +# Start timing (returns timestamp) +time_start() { + date +%s +} + +# End timing and return elapsed seconds +# Usage: elapsed=$(time_end $start_time) +time_end() { + local start=$1 + local end=$(date +%s) + echo $((end - start)) +} + +# ============================================ +# Pretty Output +# ============================================ + +# Print section header +section_header() { + local message=$1 + echo -e "${CYAN}${message}${RESET}" +} + +# Print success message +success() { + local message=$1 + echo -e "${GREEN}✓ ${message}${RESET}" +} + +# Print warning message +warning() { + local message=$1 + echo -e "${YELLOW}⚠ ${message}${RESET}" +} + +# Print error message +error() { + local message=$1 + echo -e "${RED}✗ ${message}${RESET}" +} + +# Print completion message with time +completed() { + local hook_name=$1 + local elapsed=$2 + echo "" + echo -e "${GREEN}✓ [$hook_name] Completed in ${elapsed}s${RESET}" +} diff --git a/src/codeindex/cli_hooks.py b/src/codeindex/cli_hooks.py index 2fe407d..16bc9c5 100644 --- a/src/codeindex/cli_hooks.py +++ b/src/codeindex/cli_hooks.py @@ -124,8 +124,20 @@ def install_hook( hook_path.write_text(script) hook_path.chmod(0o755) # Make executable + # Ensure hook-common.sh is installed (used by pre-commit/pre-push) + self._ensure_hook_common() + return True + def _ensure_hook_common(self) -> None: + """Copy hook-common.sh to .git/hooks/ if bundled version exists.""" + common_dest = self.hooks_dir / "hook-common.sh" + # Source from scripts/hooks/ in the repo + common_src = self.repo_path / "scripts" / "hooks" / "hook-common.sh" + if common_src.exists(): + shutil.copy(common_src, common_dest) + common_dest.chmod(0o755) + def uninstall_hook( self, hook_name: str, restore_backup: bool = True ) -> bool: diff --git a/tests/test_cli_hooks.py b/tests/test_cli_hooks.py index 7e5903e..ed480ae 100644 --- a/tests/test_cli_hooks.py +++ b/tests/test_cli_hooks.py @@ -99,6 +99,26 @@ def test_install_hook(self, tmp_path): assert (hooks_dir / "pre-commit").exists() assert (hooks_dir / "pre-commit").stat().st_mode & 0o111 # Executable + def test_install_hook_copies_hook_common(self, tmp_path): + """Should copy hook-common.sh when scripts/hooks/hook-common.sh exists.""" + repo_path = tmp_path / "test_repo" + hooks_dir = repo_path / ".git" / "hooks" + hooks_dir.mkdir(parents=True) + + # Create scripts/hooks/hook-common.sh in the repo + scripts_hooks = repo_path / "scripts" / "hooks" + scripts_hooks.mkdir(parents=True) + common_src = scripts_hooks / "hook-common.sh" + common_src.write_text("#!/bin/bash\n# common utilities\n") + + manager = HookManager(repo_path) + manager.install_hook("pre-commit") + + common_dest = hooks_dir / "hook-common.sh" + assert common_dest.exists() + assert common_dest.read_text() == "#!/bin/bash\n# common utilities\n" + assert common_dest.stat().st_mode & 0o111 # Executable + def test_install_hook_with_backup(self, tmp_path): """Should backup existing custom hook before installing.""" repo_path = tmp_path / "test_repo" From 5e7cc5a705dc06c5fd346d3b7b27ce5f907b4c39 Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 14:18:26 +0800 Subject: [PATCH 4/8] docs: auto-update README_AI.md for 31b4530 Updated by post-commit hook. Update level: current --- src/codeindex/README_AI.md | 10 +++++----- tests/README_AI.md | 12 +++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/codeindex/README_AI.md b/src/codeindex/README_AI.md index 17888e8..cd7daed 100644 --- a/src/codeindex/README_AI.md +++ b/src/codeindex/README_AI.md @@ -1,11 +1,11 @@ - + # codeindex ## Overview - **Files**: 88 -- **Symbols**: 553 +- **Symbols**: 554 ## Files @@ -141,6 +141,7 @@ This module provides: - `def install_hook( self, hook_name: str, backup: bool = True, force: bool = False ) -> bool` +- `def _ensure_hook_common(self) -> None` - `def uninstall_hook( self, hook_name: str, restore_backup: bool = True ) -> bool` @@ -157,9 +158,8 @@ This module provides: - `def detect_existing_hooks(hooks_dir: Path) -> list[str]` - `def install_hook(hook_name: str, repo_path: Optional[Path] = None) -> bool` - `def uninstall_hook(hook_name: str, repo_path: Optional[Path] = None) -> bool` -- `def run_post_commit_hook() -> int` -_... and 5 more symbols_ +_... and 6 more symbols_ ### cli_parse.py _CLI parse command - Parse a single source file and output JSON. @@ -1679,7 +1679,7 @@ including file size issues,_ Lower values indicate higher severity (CRITICAL is m -**class** `class DebtIssu +**class** `class Debt --- _Content truncated due to size limit. See individual module README files for details._ diff --git a/tests/README_AI.md b/tests/README_AI.md index d3374de..fa34dd4 100644 --- a/tests/README_AI.md +++ b/tests/README_AI.md @@ -1,11 +1,11 @@ - + # tests ## Overview - **Files**: 120 -- **Symbols**: 2071 +- **Symbols**: 2072 ## Files @@ -393,15 +393,15 @@ _Tests for Git Hooks CLI module (Epic 6, P3.1, Task 4.1-4.5)._ - `def test_get_hook_status_exists_codeindex(self, tmp_path)` - `def test_get_hook_status_exists_custom(self, tmp_path)` - `def test_install_hook(self, tmp_path)` +- `def test_install_hook_copies_hook_common(self, tmp_path)` - `def test_install_hook_with_backup(self, tmp_path)` - `def test_uninstall_hook(self, tmp_path)` - `def test_uninstall_hook_restores_backup(self, tmp_path)` - `def test_list_all_hooks_status(self, tmp_path)` - `def test_generate_pre_commit_hook(self)` - `def test_generate_post_commit_hook(self)` -- `def test_generate_hook_with_config(self)` -_... and 7 more symbols_ +_... and 8 more symbols_ ### test_cli_json.py _Tests for CLI JSON output. @@ -1543,9 +1543,7 @@ This test file validates bridging header handling for Swift/Objective-C interop: > Test import extraction from bridging headers. **class** `class TestBridgingHeaderClasses` -> Test class exposure in bridging headers. - -* +> Test class exposure in brid --- _Content truncated due to size limit. See individual module README files for details._ From 633ddfe82aba638893289c51c64c6b245a9c9b8b Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 15:52:37 +0800 Subject: [PATCH 5/8] chore(ci/hooks): add coverage gate and fix hook portability - CI: add --cov-fail-under=78 to prevent coverage regression - Hooks: change all shebangs from #!/bin/zsh to #!/usr/bin/env bash for cross-platform portability (templates + scripts/hooks/) - Fix zsh-only $= word splitting syntax in local pre-commit - Update test assertions for shebang flexibility Closes #36 (P2) Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 2 +- scripts/hooks/hook-common.sh | 2 +- src/codeindex/cli_hooks.py | 8 ++++---- tests/test_cli_hooks.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 109b76b..ad05792 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: - name: Run tests with coverage run: | - pytest -v --cov=src/codeindex --cov-report=term-missing --cov-report=xml + pytest -v --cov=src/codeindex --cov-report=term-missing --cov-report=xml --cov-fail-under=78 - name: Upload coverage to Codecov (Ubuntu Python 3.11 only) if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' diff --git a/scripts/hooks/hook-common.sh b/scripts/hooks/hook-common.sh index 4d1a974..0c85e9f 100755 --- a/scripts/hooks/hook-common.sh +++ b/scripts/hooks/hook-common.sh @@ -1,4 +1,4 @@ -#!/bin/zsh +#!/usr/bin/env bash # Common utilities for Git hooks # Source this file in all hooks to reduce code duplication diff --git a/src/codeindex/cli_hooks.py b/src/codeindex/cli_hooks.py index 16bc9c5..b5d1f82 100644 --- a/src/codeindex/cli_hooks.py +++ b/src/codeindex/cli_hooks.py @@ -212,7 +212,7 @@ def _generate_pre_commit_script(config: dict) -> str: """Generate pre-commit hook script.""" lint_enabled = config.get("lint_enabled", True) - script = """#!/bin/zsh + script = """#!/usr/bin/env bash # codeindex-managed hook # Pre-commit hook for codeindex # L1: Lint check (ruff) @@ -357,13 +357,13 @@ def _generate_post_commit_script(config: dict) -> str: # noqa: E501 auto_update = config.get("auto_update", True) if not auto_update: - return """#!/bin/zsh + return """#!/usr/bin/env bash # codeindex-managed hook # Post-commit hook (disabled) exit 0 """ - return """#!/bin/zsh + return """#!/usr/bin/env bash # codeindex-managed hook # Post-commit hook for codeindex # Thin wrapper — all logic in Python (auto-updated via pip) @@ -400,7 +400,7 @@ def _generate_post_commit_script(config: dict) -> str: # noqa: E501 def _generate_pre_push_script(config: dict) -> str: """Generate pre-push hook script.""" - return """#!/bin/zsh + return """#!/usr/bin/env bash # codeindex-managed hook # Pre-push hook for codeindex diff --git a/tests/test_cli_hooks.py b/tests/test_cli_hooks.py index ed480ae..f7c475a 100644 --- a/tests/test_cli_hooks.py +++ b/tests/test_cli_hooks.py @@ -200,7 +200,7 @@ def test_generate_pre_commit_hook(self): """Should generate valid pre-commit hook script.""" script = generate_hook_script("pre-commit") - assert "#!/bin/zsh" in script or "#!/bin/bash" in script + assert script.startswith("#!/") assert "codeindex-managed hook" in script assert "ruff" in script.lower() or "lint" in script.lower() @@ -208,7 +208,7 @@ def test_generate_post_commit_hook(self): """Should generate valid post-commit hook script.""" script = generate_hook_script("post-commit") - assert "#!/bin/zsh" in script or "#!/bin/bash" in script + assert script.startswith("#!/") assert "codeindex-managed hook" in script assert "README_AI.md" in script or "codeindex" in script From 7fab78171f70d1e609147b781c0317429401590f Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 15:52:37 +0800 Subject: [PATCH 6/8] docs: auto-update README_AI.md for 633ddfe Updated by post-commit hook. Update level: current --- src/codeindex/README_AI.md | 2 +- tests/README_AI.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codeindex/README_AI.md b/src/codeindex/README_AI.md index cd7daed..f3726a5 100644 --- a/src/codeindex/README_AI.md +++ b/src/codeindex/README_AI.md @@ -1,4 +1,4 @@ - + # codeindex diff --git a/tests/README_AI.md b/tests/README_AI.md index fa34dd4..fd70ce6 100644 --- a/tests/README_AI.md +++ b/tests/README_AI.md @@ -1,4 +1,4 @@ - + # tests From e8165a111ccf6dbaad7c3b22321d7a357cad3187 Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 17:22:29 +0800 Subject: [PATCH 7/8] feat(lint): replace regex debug detection with ruff T rules and add mypy - Enable ruff T201 (print) and T100 (debugger) rules in pyproject.toml - Add per-file-ignores for CLI/test files that legitimately use print() - Remove ~50 lines of regex-based L2 debug detection from pre-commit template - Add mypy to dev dependencies with baseline config - CI: add mypy type check as informational (non-blocking) step - Makefile: add `make typecheck` target Closes #36 (P3) Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 8 ++++-- Makefile | 3 ++ pyproject.toml | 16 ++++++++++- src/codeindex/cli_hooks.py | 58 ++------------------------------------ 4 files changed, 27 insertions(+), 58 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad05792..7b88a4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,12 +60,16 @@ jobs: with: python-version: '3.11' - - name: Install ruff - run: pip install ruff + - name: Install lint tools + run: pip install ruff mypy - name: Run ruff check run: ruff check src/ tests/ test_generator/ + - name: Run mypy type check (informational) + run: mypy src/codeindex/parser.py src/codeindex/scanner.py src/codeindex/config.py + continue-on-error: true + build-check: runs-on: ubuntu-latest steps: diff --git a/Makefile b/Makefile index 0a34f17..5f9df25 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,9 @@ lint: ## Run linter (ruff) lint-fix: ## Auto-fix linting issues ruff check --fix src/ tests/ +typecheck: ## Run mypy type check on core modules + mypy src/codeindex/parser.py src/codeindex/scanner.py src/codeindex/config.py + format: ## Format code with ruff ruff format src/ tests/ diff --git a/pyproject.toml b/pyproject.toml index 0434133..75c01b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ dev = [ "pytest-bdd>=7.0", "pytest-cov>=4.0", "ruff>=0.1", + "mypy>=1.0", ] [project.scripts] @@ -82,7 +83,20 @@ extend-exclude = [ ] [tool.ruff.lint] -select = ["E", "F", "I", "N", "W"] +select = ["E", "F", "I", "N", "W", "T"] + +[tool.ruff.lint.per-file-ignores] +"src/codeindex/cli_*.py" = ["T201"] # CLI files legitimately use print() +"scripts/*.py" = ["T201"] +"tests/**/*.py" = ["T201"] + +[tool.mypy] +python_version = "3.10" +warn_return_any = true +warn_unused_configs = true +ignore_missing_imports = true +check_untyped_defs = false +disallow_untyped_defs = false [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/src/codeindex/cli_hooks.py b/src/codeindex/cli_hooks.py index b5d1f82..3a5ddd5 100644 --- a/src/codeindex/cli_hooks.py +++ b/src/codeindex/cli_hooks.py @@ -286,61 +286,9 @@ def _generate_pre_commit_script(config: dict) -> str: """ script += """ -# ============================================ -# L2: Forbid debug code -# ============================================ -echo "\\n${YELLOW}[L2] Checking for debug code...${NC}" - -DEBUG_PATTERNS=( - 'print\\s*\\(' # print() statements - 'breakpoint\\s*\\(' # breakpoint() calls - 'pdb\\.set_trace\\s*\\(' # pdb debugger - 'import\\s+pdb' # pdb import - 'from\\s+pdb\\s+import' # from pdb import -) - -FOUND_DEBUG=0 -for file in $STAGED_PY_FILES; do - # Skip CLI files and modules that use print() for legitimate output - if [[ "$file" == *"/cli"* ]] || [[ "$file" == *"/cli_"* ]] || \\ - [[ "$file" == *"hierarchical.py"* ]] || \\ - [[ "$file" == *"directory_tree.py"* ]] || \\ - [[ "$file" == *"adaptive_selector.py"* ]]; then - continue - fi - - # Get only staged content (not working directory) - STAGED_CONTENT=$(git show ":$file" 2>/dev/null || true) - - if [ -z "$STAGED_CONTENT" ]; then - continue - fi - - for pattern in $DEBUG_PATTERNS; do - # Find matches, excluding console.print() and docstring examples - MATCHES=$(echo "$STAGED_CONTENT" | \\ - grep -n -E "$pattern" | \\ - grep -v "console\\.print" | \\ - grep -v "^[[:space:]]*>>>" || true) - if [ -n "$MATCHES" ]; then - if [ $FOUND_DEBUG -eq 0 ]; then - echo "${RED}✗ Debug code found:${NC}" - FOUND_DEBUG=1 - fi - echo " ${file}:" - echo "$MATCHES" | while read line; do - echo " $line" - done - fi - done -done - -if [ $FOUND_DEBUG -eq 1 ]; then - echo "\\n${RED}✗ Remove debug code before committing.${NC}" - echo " Tip: Use logging module instead of print()" - exit 1 -fi -echo "${GREEN}✓ No debug code found${NC}" +# Note: Debug code detection (print/breakpoint/pdb) is now handled by +# ruff rules T201 (print) and T100 (debugger) in the lint check above. +# Per-file-ignores in pyproject.toml exempt CLI files. # ============================================ # All checks passed From 364382a24015cd05e7732b61bb683a7b612c5de2 Mon Sep 17 00:00:00 2001 From: DreamLinx Date: Mon, 13 Apr 2026 17:22:29 +0800 Subject: [PATCH 8/8] docs: auto-update README_AI.md for e8165a1 Updated by post-commit hook. Update level: affected --- src/codeindex/README_AI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codeindex/README_AI.md b/src/codeindex/README_AI.md index f3726a5..01a4696 100644 --- a/src/codeindex/README_AI.md +++ b/src/codeindex/README_AI.md @@ -1,4 +1,4 @@ - + # codeindex