Files
claude-smart-usage/claude_task_chunker/cli.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

216 lines
6.8 KiB
Python

"""CLI interface for Claude Task Chunker."""
import argparse
import sys
from pathlib import Path
from .analyzer import analyze_codebase
from .decomposer import TaskChunk, decompose_by_phase, decompose_by_component, STRATEGIES
from .tracker import save_plan, load_plan, mark_done, get_next_chunk, get_status_summary, TRACKER_DIR
def cmd_plan(args: argparse.Namespace) -> None:
"""Decompose a task into chunks."""
root = Path(args.project).resolve()
if not root.is_dir():
print(f"Error: {root} is not a directory.", file=sys.stderr)
sys.exit(1)
task = args.task
strategy = args.strategy
print(f"Analyzing codebase at {root}...")
info = analyze_codebase(root)
summary = info.summary()
print(summary)
print()
print(f"Decomposing task using '{strategy}' strategy...")
if strategy == "phase":
raw_chunks = decompose_by_phase(task)
elif strategy == "component":
# Filter to source files only
source_exts = {".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".rs", ".java", ".rb", ".cpp", ".c", ".h"}
source_files = [f for f in info.tree if Path(f).suffix.lower() in source_exts]
raw_chunks = decompose_by_component(task, source_files)
elif strategy == "manual":
# Read chunk descriptions from a file
if not args.chunks_file:
print("Error: --chunks-file is required with manual strategy.", file=sys.stderr)
sys.exit(1)
raw_chunks = _parse_manual_chunks(args.chunks_file, task)
else:
print(f"Error: Unknown strategy '{strategy}'.", file=sys.stderr)
sys.exit(1)
chunks = [TaskChunk(**c) for c in raw_chunks]
plan_file = save_plan(root, task, chunks, summary)
print(f"Created {len(chunks)} chunks:")
for c in chunks:
deps = f" (after: {', '.join(str(d) for d in c.depends_on)})" if c.depends_on else ""
print(f" {c.id}. {c.title}{deps}")
print(f"\nPlan saved to: {plan_file}")
print(f"Prompts saved to: {root / TRACKER_DIR / 'prompts'}/")
print(f"\nTo start, copy the prompt from chunk_01.md into Claude Code.")
print(f"After each chunk, run: ctc done <chunk_id>")
def cmd_status(args: argparse.Namespace) -> None:
"""Show current progress."""
root = Path(args.project).resolve()
print(get_status_summary(root))
def cmd_done(args: argparse.Namespace) -> None:
"""Mark a chunk as completed."""
root = Path(args.project).resolve()
chunk_id = args.chunk_id
if mark_done(root, chunk_id):
print(f"Chunk {chunk_id} marked as done.")
print()
print(get_status_summary(root))
else:
print(f"Error: Could not find chunk {chunk_id}.", file=sys.stderr)
sys.exit(1)
def cmd_next(args: argparse.Namespace) -> None:
"""Show the next chunk's prompt, ready to paste."""
root = Path(args.project).resolve()
plan = load_plan(root)
if not plan:
print("No active plan. Run `ctc plan` first.", file=sys.stderr)
sys.exit(1)
nxt = get_next_chunk(root)
if not nxt:
print("All chunks are complete! Nothing left to do.")
return
chunk = TaskChunk(**nxt)
prompt = chunk.prompt(plan["codebase_summary"], len(plan["chunks"]))
if args.copy:
try:
import subprocess
process = subprocess.Popen(
_get_clipboard_cmd(),
stdin=subprocess.PIPE,
)
process.communicate(prompt.encode())
print(f"Chunk {chunk.id} prompt copied to clipboard!")
except (FileNotFoundError, OSError):
print("Could not copy to clipboard. Printing instead:\n")
print(prompt)
else:
print(prompt)
def cmd_show(args: argparse.Namespace) -> None:
"""Show a specific chunk's prompt."""
root = Path(args.project).resolve()
plan = load_plan(root)
if not plan:
print("No active plan. Run `ctc plan` first.", file=sys.stderr)
sys.exit(1)
for c in plan["chunks"]:
if c["id"] == args.chunk_id:
chunk = TaskChunk(**c)
print(chunk.prompt(plan["codebase_summary"], len(plan["chunks"])))
return
print(f"Error: Chunk {args.chunk_id} not found.", file=sys.stderr)
sys.exit(1)
def _get_clipboard_cmd() -> list[str]:
"""Get the platform-appropriate clipboard command."""
import platform
system = platform.system()
if system == "Darwin":
return ["pbcopy"]
elif system == "Linux":
return ["xclip", "-selection", "clipboard"]
else:
return ["clip"]
def _parse_manual_chunks(filepath: str, task: str) -> list[dict]:
"""Parse a simple text file where each non-empty line is a chunk description."""
chunks = []
path = Path(filepath)
if not path.exists():
print(f"Error: {filepath} not found.", file=sys.stderr)
sys.exit(1)
lines = [ln.strip() for ln in path.read_text().splitlines() if ln.strip() and not ln.startswith("#")]
for i, line in enumerate(lines, 1):
chunks.append({
"id": i,
"title": line,
"description": f"Part of: {task}\n\n{line}",
"depends_on": [i - 1] if i > 1 else [],
})
return chunks
def main() -> None:
parser = argparse.ArgumentParser(
prog="ctc",
description="Claude Task Chunker - Break large tasks into efficient Claude Code sessions",
)
parser.add_argument(
"-p", "--project", default=".",
help="Path to the project root (default: current directory)",
)
sub = parser.add_subparsers(dest="command", required=True)
# plan
p_plan = sub.add_parser("plan", help="Decompose a task into chunks")
p_plan.add_argument("task", help="Description of the full task")
p_plan.add_argument(
"-s", "--strategy", default="phase",
choices=["phase", "component", "manual"],
help="Decomposition strategy (default: phase)",
)
p_plan.add_argument(
"--chunks-file",
help="File with one chunk description per line (for manual strategy)",
)
# status
sub.add_parser("status", help="Show progress on the current plan")
# done
p_done = sub.add_parser("done", help="Mark a chunk as completed")
p_done.add_argument("chunk_id", type=int, help="ID of the completed chunk")
# next
p_next = sub.add_parser("next", help="Show the next chunk prompt")
p_next.add_argument("-c", "--copy", action="store_true", help="Copy prompt to clipboard")
# show
p_show = sub.add_parser("show", help="Show a specific chunk's prompt")
p_show.add_argument("chunk_id", type=int, help="ID of the chunk to show")
args = parser.parse_args()
commands = {
"plan": cmd_plan,
"status": cmd_status,
"done": cmd_done,
"next": cmd_next,
"show": cmd_show,
}
commands[args.command](args)
if __name__ == "__main__":
main()