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
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)"

# ============================================================================
Expand All @@ -65,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/

Expand Down
1 change: 1 addition & 0 deletions docs/guides/git-hooks-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dev = [
"pytest-bdd>=7.0",
"pytest-cov>=4.0",
"ruff>=0.1",
"mypy>=1.0",
]

[project.scripts]
Expand All @@ -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"]
Expand Down
121 changes: 121 additions & 0 deletions scripts/hooks/hook-common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
# 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}"
}
10 changes: 5 additions & 5 deletions src/codeindex/README_AI.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<!-- Generated by codeindex (detailed) at 2026-03-15T17:05:28.311112 -->
<!-- Generated by codeindex (detailed) at 2026-04-13T17:22:30.091265 -->

# codeindex

## Overview

- **Files**: 88
- **Symbols**: 553
- **Symbols**: 554

## Files

Expand Down Expand Up @@ -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`
Expand All @@ -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.
Expand Down Expand Up @@ -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._
86 changes: 26 additions & 60 deletions src/codeindex/cli_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -200,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)
Expand Down Expand Up @@ -274,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
Expand All @@ -345,13 +305,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)
Expand All @@ -375,14 +335,20 @@ 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
"""


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

Expand Down
Loading
Loading