Add codebase scanner, CLAUDE.md init, and usage-focused workflow

- ctc scan: runs linters/tests/type-checkers to find errors without
  burning AI tokens. Auto-detects Python, Node, Rust, Go projects.
- ctc init: drops a CLAUDE.md into any repo teaching Claude Code to
  work efficiently (chunk work, commit often, read scan results first).
- Updated README with the actual workflow: scan -> paste -> fix.

https://claude.ai/code/session_01Fzv8baXnEVVhnrffAb3Ucc
This commit is contained in:
Claude
2026-03-29 22:48:58 +00:00
parent 2d4ead1e3f
commit e211583214
4 changed files with 529 additions and 67 deletions
+134 -3
View File
@@ -5,7 +5,8 @@ import sys
from pathlib import Path
from .analyzer import analyze_codebase
from .decomposer import TaskChunk, decompose_by_phase, decompose_by_component, STRATEGIES
from .decomposer import TaskChunk, decompose_by_phase, decompose_by_component
from .scanner import scan_codebase
from .tracker import save_plan, load_plan, mark_done, get_next_chunk, get_status_summary, TRACKER_DIR
@@ -54,8 +55,8 @@ def cmd_plan(args: argparse.Namespace) -> None:
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>")
print("\nTo start, copy the prompt from chunk_01.md into Claude Code.")
print("After each chunk, run: ctc done <chunk_id>")
def cmd_status(args: argparse.Namespace) -> None:
@@ -128,6 +129,74 @@ def cmd_show(args: argparse.Namespace) -> None:
sys.exit(1)
def cmd_scan(args: argparse.Namespace) -> None:
"""Scan the codebase for errors, then output a ready-to-paste prompt."""
root = Path(args.project).resolve()
if not root.is_dir():
print(f"Error: {root} is not a directory.", file=sys.stderr)
sys.exit(1)
print(f"Scanning {root}...", file=sys.stderr)
result = scan_codebase(root)
task = args.task or ""
prompt = result.to_prompt(task)
output_file = root / ".claude-chunks" / "scan_results.md"
output_file.parent.mkdir(exist_ok=True)
output_file.write_text(prompt)
if args.copy:
try:
import subprocess
process = subprocess.Popen(
_get_clipboard_cmd(),
stdin=subprocess.PIPE,
)
process.communicate(prompt.encode())
print("Scan complete! Prompt copied to clipboard.", file=sys.stderr)
print(f"Also saved to: {output_file}", file=sys.stderr)
except (FileNotFoundError, OSError):
print(f"Scan complete! Saved to: {output_file}", file=sys.stderr)
print(prompt)
else:
print(f"Scan complete! Saved to: {output_file}", file=sys.stderr)
print(file=sys.stderr)
print(prompt)
# Summary stats to stderr
n_err = len(result.errors)
n_warn = len(result.warnings)
n_files = len(set(result.files_with_issues))
print(f"\nFound: {n_err} errors, {n_warn} warnings across {n_files} files", file=sys.stderr)
def cmd_init(args: argparse.Namespace) -> None:
"""Initialize a CLAUDE.md in the target project to teach Claude Code chunking."""
root = Path(args.project).resolve()
claude_md = root / "CLAUDE.md"
if claude_md.exists() and not args.force:
print(f"CLAUDE.md already exists at {claude_md}. Use --force to overwrite.", file=sys.stderr)
sys.exit(1)
template = _get_claude_md_template()
# If CLAUDE.md exists and --force, append rather than overwrite
if claude_md.exists() and args.force:
existing = claude_md.read_text()
if "# Usage Efficiency" not in existing:
claude_md.write_text(existing.rstrip() + "\n\n" + template)
print(f"Appended chunking instructions to {claude_md}")
else:
print("CLAUDE.md already has chunking instructions.")
else:
claude_md.write_text(template)
print(f"Created {claude_md}")
print("Claude Code will now follow usage-efficient chunking practices in this repo.")
def _get_clipboard_cmd() -> list[str]:
"""Get the platform-appropriate clipboard command."""
import platform
@@ -160,6 +229,57 @@ def _parse_manual_chunks(filepath: str, task: str) -> list[dict]:
return chunks
def _get_claude_md_template() -> str:
return '''# Usage Efficiency
When working on large tasks in this codebase, follow these practices to minimize
token usage and avoid running out of session capacity.
## Before Starting a Large Task
1. Check if a pre-scan exists: look for `.claude-chunks/scan_results.md`
2. If it exists, read it FIRST — it contains linter/test/build errors already identified
3. Use those results to target your work. Do NOT re-read the entire codebase.
## Chunking Strategy
For any task that will touch more than 5 files or require significant analysis:
1. **Plan first, implement second.** Before writing code, list:
- Which files need to change
- What order to change them in
- How to verify each change works
2. **Work in commits.** Make a commit after each logical unit of work. This creates
save points if the session ends unexpectedly.
3. **Stay focused.** Only read files directly relevant to the current task. Don't
explore the codebase "to understand it better" — that burns tokens fast.
4. **Use tools efficiently:**
- Use `grep`/`rg` to find what you need instead of reading whole files
- Read specific line ranges instead of entire files when possible
- Don't re-read files you've already read in this session
## If the Task Is Too Large
If you realize mid-task that you won't finish in this session:
1. Commit everything you've done so far
2. Create a file called `.claude-chunks/handoff.md` with:
- What was completed
- What remains to be done
- Any gotchas or context the next session needs
3. Tell the user to run `ctc status` to see progress
## Error Fixing Workflow
When asked to find and fix errors in the codebase:
1. First check for `.claude-chunks/scan_results.md` — it may already have the errors
2. If not, ask the user to run `ctc scan` first (it's faster than AI-powered search)
3. Focus on the specific files and line numbers from the scan results
4. Don't read unrelated files
'''
def main() -> None:
parser = argparse.ArgumentParser(
prog="ctc",
@@ -184,6 +304,15 @@ def main() -> None:
help="File with one chunk description per line (for manual strategy)",
)
# scan
p_scan = sub.add_parser("scan", help="Scan codebase for errors and generate a prompt")
p_scan.add_argument("task", nargs="?", default="", help="Optional: what you're trying to fix")
p_scan.add_argument("-c", "--copy", action="store_true", help="Copy prompt to clipboard")
# init
p_init = sub.add_parser("init", help="Add CLAUDE.md with chunking instructions to a project")
p_init.add_argument("--force", action="store_true", help="Overwrite/append to existing CLAUDE.md")
# status
sub.add_parser("status", help="Show progress on the current plan")
@@ -203,6 +332,8 @@ def main() -> None:
commands = {
"plan": cmd_plan,
"scan": cmd_scan,
"init": cmd_init,
"status": cmd_status,
"done": cmd_done,
"next": cmd_next,