Files
claude-smart-usage/claude_task_chunker/analyzer.py
T
Claude 2d4ead1e3f Add claude-task-chunker: break large tasks into efficient Claude Code sessions
CLI tool that decomposes big coding tasks into self-contained chunks,
each with a ready-to-paste prompt including codebase context, targeted
instructions, and dependency tracking. Supports phase, component, and
manual decomposition strategies.

https://claude.ai/code/session_01Fzv8baXnEVVhnrffAb3Ucc
2026-03-29 22:43:40 +00:00

107 lines
3.6 KiB
Python

"""Codebase analyzer - gathers structure and context for task decomposition."""
import os
import subprocess
from dataclasses import dataclass, field
from pathlib import Path
@dataclass
class CodebaseInfo:
"""Summary of a codebase's structure and key files."""
root: Path
languages: dict[str, int] = field(default_factory=dict) # extension -> file count
total_files: int = 0
total_lines: int = 0
tree: list[str] = field(default_factory=list) # directory tree (paths)
key_files: list[str] = field(default_factory=list) # config, entry points, etc.
git_branch: str = ""
recent_commits: list[str] = field(default_factory=list)
def summary(self) -> str:
lines = [f"Codebase: {self.root.name}"]
if self.git_branch:
lines.append(f"Branch: {self.git_branch}")
lines.append(f"Files: {self.total_files} | Lines: ~{self.total_lines}")
if self.languages:
top = sorted(self.languages.items(), key=lambda x: -x[1])[:5]
lines.append("Languages: " + ", ".join(f"{ext} ({n})" for ext, n in top))
if self.key_files:
lines.append("Key files: " + ", ".join(self.key_files[:10]))
return "\n".join(lines)
IGNORE_DIRS = {
".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build",
".next", ".nuxt", "target", "vendor", ".tox", ".mypy_cache", ".pytest_cache",
"coverage", ".cache", "env", ".env", ".eggs", "*.egg-info",
}
KEY_FILE_NAMES = {
"package.json", "pyproject.toml", "setup.py", "setup.cfg", "Cargo.toml",
"go.mod", "Makefile", "CMakeLists.txt", "Dockerfile", "docker-compose.yml",
"docker-compose.yaml", ".env.example", "requirements.txt", "tsconfig.json",
"webpack.config.js", "vite.config.ts", "vite.config.js", "next.config.js",
"tailwind.config.js", "CLAUDE.md",
}
def analyze_codebase(root: Path) -> CodebaseInfo:
"""Walk the codebase and gather structural info."""
info = CodebaseInfo(root=root.resolve())
# Git info
try:
info.git_branch = subprocess.check_output(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=root, stderr=subprocess.DEVNULL, text=True,
).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
pass
try:
log = subprocess.check_output(
["git", "log", "--oneline", "-10"],
cwd=root, stderr=subprocess.DEVNULL, text=True,
).strip()
if log:
info.recent_commits = log.splitlines()
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# Walk files
languages: dict[str, int] = {}
total_lines = 0
for dirpath, dirnames, filenames in os.walk(root):
# Prune ignored directories
dirnames[:] = [d for d in dirnames if d not in IGNORE_DIRS]
rel_dir = os.path.relpath(dirpath, root)
for fname in filenames:
full = os.path.join(dirpath, fname)
rel = os.path.join(rel_dir, fname) if rel_dir != "." else fname
info.tree.append(rel)
info.total_files += 1
# Check key files
if fname in KEY_FILE_NAMES:
info.key_files.append(rel)
# Language stats
ext = Path(fname).suffix.lower()
if ext:
languages[ext] = languages.get(ext, 0) + 1
# Line count (skip binary)
try:
with open(full, "r", errors="ignore") as f:
total_lines += sum(1 for _ in f)
except (OSError, UnicodeDecodeError):
pass
info.languages = languages
info.total_lines = total_lines
return info