123 lines
4.3 KiB
Python
Executable File
123 lines
4.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Work Items Scanner
|
|
|
|
Scans code for TODOs, FIXMEs, and loads session objectives.
|
|
"""
|
|
|
|
import re
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, List
|
|
|
|
|
|
class WorkItemsScanner:
|
|
"""Scan project for work items"""
|
|
|
|
def __init__(self, project_path: str = "."):
|
|
"""Initialize scanner with project path"""
|
|
self.project_path = Path(project_path)
|
|
|
|
def scan_code_markers(self, patterns: List[str] = None) -> Dict[str, List[Dict]]:
|
|
"""Scan code files for TODO/FIXME markers"""
|
|
if patterns is None:
|
|
patterns = ["*.py", "*.js", "*.ts", "*.tsx", "*.java", "*.go", "*.rs"]
|
|
|
|
todos = []
|
|
fixmes = []
|
|
|
|
for pattern in patterns:
|
|
for file_path in self.project_path.rglob(pattern):
|
|
# Skip test files and node_modules
|
|
if "test" in str(file_path) or "node_modules" in str(file_path):
|
|
continue
|
|
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
for line_num, line in enumerate(f, 1):
|
|
# Match TODO: or TODO -
|
|
if re.search(r'TODO[:\-]', line, re.IGNORECASE):
|
|
comment = line.strip()
|
|
todos.append({
|
|
"file": str(file_path.relative_to(self.project_path)),
|
|
"line": line_num,
|
|
"text": comment
|
|
})
|
|
# Match FIXME: or FIXME -
|
|
if re.search(r'FIXME[:\-]', line, re.IGNORECASE):
|
|
comment = line.strip()
|
|
fixmes.append({
|
|
"file": str(file_path.relative_to(self.project_path)),
|
|
"line": line_num,
|
|
"text": comment
|
|
})
|
|
except (IOError, UnicodeDecodeError):
|
|
# Skip files we can't read
|
|
continue
|
|
|
|
return {
|
|
"todos": todos[:20], # Limit to first 20
|
|
"fixmes": fixmes[:20]
|
|
}
|
|
|
|
def load_session_objectives(self) -> List[Dict]:
|
|
"""Load objectives from .sessions/state.json"""
|
|
state_file = self.project_path / ".sessions" / "state.json"
|
|
|
|
if not state_file.exists():
|
|
return []
|
|
|
|
try:
|
|
with open(state_file) as f:
|
|
state = json.load(f)
|
|
|
|
objectives = state.get("objectives", [])
|
|
# Convert to list of dicts if it's a simple list
|
|
if objectives and isinstance(objectives[0], str):
|
|
return [{"text": obj, "completed": False} for obj in objectives]
|
|
|
|
return objectives
|
|
except (json.JSONDecodeError, IOError, KeyError):
|
|
return []
|
|
|
|
def generate_report(self) -> str:
|
|
"""Generate work items section"""
|
|
lines = []
|
|
lines.append("## 📋 Open Work Items")
|
|
lines.append("")
|
|
|
|
# Session objectives
|
|
objectives = self.load_session_objectives()
|
|
if objectives:
|
|
lines.append("**Session Objectives**:")
|
|
for obj in objectives:
|
|
status = "[x]" if obj.get("completed") else "[ ]"
|
|
text = obj.get("text", "Unknown objective")
|
|
lines.append(f"- {status} {text}")
|
|
lines.append("")
|
|
|
|
# Code markers
|
|
markers = self.scan_code_markers()
|
|
todos = markers["todos"]
|
|
fixmes = markers["fixmes"]
|
|
|
|
if todos:
|
|
lines.append("**TODOs in Code**:")
|
|
for todo in todos[:5]: # Show first 5
|
|
lines.append(f"- {todo['file']}:{todo['line']} - {todo['text'][:80]}")
|
|
if len(todos) > 5:
|
|
lines.append(f"- ... and {len(todos) - 5} more")
|
|
lines.append("")
|
|
|
|
if fixmes:
|
|
lines.append("**FIXMEs in Code**:")
|
|
for fixme in fixmes[:5]:
|
|
lines.append(f"- {fixme['file']}:{fixme['line']} - {fixme['text'][:80]}")
|
|
if len(fixmes) > 5:
|
|
lines.append(f"- ... and {len(fixmes) - 5} more")
|
|
lines.append("")
|
|
|
|
lines.append(f"**Summary**: {len(todos)} TODOs, {len(fixmes)} FIXMEs")
|
|
|
|
return "\n".join(lines)
|