TradingAgents/.claude/lib/feature_completion_detector.py

344 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Feature Completion Detector
Analyzes features against CLAUDE.md, PROJECT.md, and git history to detect
which features may already be complete. This helps avoid duplicate work
in batch processing.
Key Features:
1. Search CLAUDE.md for feature references
2. Search PROJECT.md for completed goals
3. Check git log for related commits
4. Pattern matching for issue numbers and feature descriptions
5. JSON output for command consumption
Usage:
from feature_completion_detector import FeatureCompletionDetector
# Create detector
detector = FeatureCompletionDetector(project_root=Path("/path/to/project"))
# Check if feature is complete
result = detector.check_feature("Extract agent-output-formats skill (Issue #62)")
# Result contains:
# {
# "feature": "Extract agent-output-formats skill (Issue #62)",
# "likely_complete": True,
# "evidence": [
# "Found in CLAUDE.md: 'Issue #62 Phase 1.1 - agent-output-formats skill'",
# "Found in git log: commit 'feat: Extract agent-output-formats skill (Issue #62)'"
# ],
# "confidence": "high" # high, medium, low
# }
Author: implementer agent
Date: 2025-11-15
Issue: batch-implement feature fix
Phase: Implementation
Design Patterns:
See library-design-patterns skill for standardized design patterns.
"""
import json
import re
import subprocess
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Dict, Any, Optional
# ==============================================================================
# Data Classes
# ==============================================================================
@dataclass
class CompletionCheck:
"""Result of checking if a feature is complete.
Attributes:
feature: Feature description
likely_complete: True if feature appears to be complete
evidence: List of evidence strings supporting the conclusion
confidence: Confidence level (high, medium, low)
"""
feature: str
likely_complete: bool
evidence: List[str] = field(default_factory=list)
confidence: str = "low" # high, medium, low
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
"feature": self.feature,
"likely_complete": self.likely_complete,
"evidence": self.evidence,
"confidence": self.confidence,
}
# ==============================================================================
# Main Class
# ==============================================================================
class FeatureCompletionDetector:
"""Detector for identifying already-completed features.
This class searches CLAUDE.md, PROJECT.md, and git history to determine
if a feature has already been implemented.
Attributes:
project_root: Path to project root directory
"""
def __init__(self, project_root: Path):
"""Initialize detector.
Args:
project_root: Path to project root directory
Raises:
ValueError: If project_root is invalid
"""
self.project_root = Path(project_root)
# Validate project root exists
if not self.project_root.exists():
raise ValueError(f"Project root not found: {self.project_root}")
if not self.project_root.is_dir():
raise ValueError(f"Project root is not a directory: {self.project_root}")
def check_feature(self, feature: str) -> CompletionCheck:
"""Check if a feature is likely complete.
Args:
feature: Feature description to check
Returns:
CompletionCheck with evidence and confidence level
"""
evidence = []
confidence_score = 0
# Extract issue number if present (e.g., "Issue #62")
issue_match = re.search(r'Issue\s+#(\d+)', feature, re.IGNORECASE)
issue_number = issue_match.group(1) if issue_match else None
# Extract phase if present (e.g., "Phase 1.1", "Phase 2")
phase_match = re.search(r'Phase\s+([\d.]+)', feature, re.IGNORECASE)
phase = phase_match.group(1) if phase_match else None
# 1. Check CLAUDE.md
claude_md = self.project_root / "CLAUDE.md"
if claude_md.exists():
claude_content = claude_md.read_text(encoding="utf-8")
# Search for issue number
if issue_number and f"Issue #{issue_number}" in claude_content:
evidence.append(f"Found 'Issue #{issue_number}' in CLAUDE.md")
confidence_score += 2
# Search for phase
if phase and f"Phase {phase}" in claude_content:
evidence.append(f"Found 'Phase {phase}' in CLAUDE.md")
confidence_score += 1
# Search for key phrases from feature
keywords = self._extract_keywords(feature)
for keyword in keywords:
if keyword.lower() in claude_content.lower():
evidence.append(f"Found keyword '{keyword}' in CLAUDE.md")
confidence_score += 1
# 2. Check PROJECT.md
project_md = self.project_root / ".claude" / "PROJECT.md"
if project_md.exists():
project_content = project_md.read_text(encoding="utf-8")
# Search for issue number
if issue_number and f"Issue #{issue_number}" in project_content:
evidence.append(f"Found 'Issue #{issue_number}' in PROJECT.md")
confidence_score += 2
# Search for completion markers
if issue_number:
completion_patterns = [
f"✅.*Issue #{issue_number}",
f"✓.*Issue #{issue_number}",
f"COMPLETED.*Issue #{issue_number}",
]
for pattern in completion_patterns:
if re.search(pattern, project_content, re.IGNORECASE):
evidence.append(f"Found completion marker for Issue #{issue_number} in PROJECT.md")
confidence_score += 3
break
# 3. Check git log
git_evidence = self._check_git_log(feature, issue_number)
if git_evidence:
evidence.extend(git_evidence)
confidence_score += len(git_evidence)
# Determine if likely complete based on evidence
likely_complete = confidence_score >= 3
# Determine confidence level
if confidence_score >= 5:
confidence = "high"
elif confidence_score >= 3:
confidence = "medium"
else:
confidence = "low"
return CompletionCheck(
feature=feature,
likely_complete=likely_complete,
evidence=evidence,
confidence=confidence,
)
def check_all_features(self, features: List[str]) -> List[CompletionCheck]:
"""Check multiple features for completion.
Args:
features: List of feature descriptions
Returns:
List of CompletionCheck results
"""
return [self.check_feature(feature) for feature in features]
def _extract_keywords(self, feature: str) -> List[str]:
"""Extract key phrases from feature description.
Args:
feature: Feature description
Returns:
List of keywords to search for
"""
keywords = []
# Extract quoted strings
quoted = re.findall(r'"([^"]+)"', feature)
keywords.extend(quoted)
# Extract skill names (e.g., "agent-output-formats skill")
skill_match = re.search(r'([\w-]+)\s+skill', feature, re.IGNORECASE)
if skill_match:
keywords.append(skill_match.group(1))
# Extract agent names (e.g., "test-master agent")
agent_match = re.search(r'([\w-]+)\s+agent', feature, re.IGNORECASE)
if agent_match:
keywords.append(agent_match.group(1))
# Extract library names (e.g., "security_utils.py")
library_match = re.search(r'([\w_]+\.py)', feature)
if library_match:
keywords.append(library_match.group(1))
return keywords
def _check_git_log(self, feature: str, issue_number: Optional[str] = None) -> List[str]:
"""Check git log for related commits.
Args:
feature: Feature description
issue_number: Issue number if present
Returns:
List of evidence strings from git log
"""
evidence = []
try:
# Check if we're in a git repo
git_dir = self.project_root / ".git"
if not git_dir.exists():
return evidence
# Search git log for issue number
if issue_number:
result = subprocess.run(
["git", "log", "--all", "--oneline", f"--grep=#{issue_number}"],
cwd=self.project_root,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0 and result.stdout.strip():
commits = result.stdout.strip().split('\n')
evidence.append(f"Found {len(commits)} commit(s) mentioning Issue #{issue_number}")
# Search for keywords in recent commits (last 50)
keywords = self._extract_keywords(feature)
for keyword in keywords[:3]: # Limit to top 3 keywords
result = subprocess.run(
["git", "log", "--all", "-50", "--oneline", f"--grep={keyword}"],
cwd=self.project_root,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0 and result.stdout.strip():
evidence.append(f"Found commits mentioning '{keyword}'")
except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
# Git not available or timeout - skip git checks
pass
return evidence
# ==============================================================================
# CLI Entry Point
# ==============================================================================
if __name__ == "__main__":
import sys
from pathlib import Path
if len(sys.argv) < 2:
print("Usage: python feature_completion_detector.py <feature> [<feature2> ...]")
print("\nExample:")
print(" python feature_completion_detector.py 'Extract agent-output-formats skill (Issue #62)'")
print("\nOutput: JSON with completion check results")
sys.exit(1)
features = sys.argv[1:]
project_root = Path.cwd()
# Initialize detector
detector = FeatureCompletionDetector(project_root=project_root)
try:
# Check features
results = detector.check_all_features(features)
# Output JSON
output = {
"results": [r.to_dict() for r in results],
"total_features": len(features),
"likely_complete_count": sum(1 for r in results if r.likely_complete),
}
print(json.dumps(output, indent=2))
sys.exit(0)
except Exception as e:
error_output = {
"error": str(e),
"type": type(e).__name__,
}
print(json.dumps(error_output, indent=2))
sys.exit(1)