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
107 lines
3.6 KiB
Python
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
|