search_before_build.py
1 #!/usr/bin/env python3 2 """ 3 Search Before Build - Torah/Talmud Convergence Check 4 5 Before building new functionality, search for existing implementations. 6 Integration > Invention. 7 8 Usage: 9 python3 scripts/search_before_build.py "resonance threshold" 10 python3 scripts/search_before_build.py "graph sink" --verbose 11 python3 scripts/search_before_build.py "broadcast mesh" --suggest 12 13 This enforces the "Search before build" protocol from CLAUDE.md. 14 """ 15 16 import subprocess 17 import sys 18 from pathlib import Path 19 from typing import List, Tuple 20 21 REPO_ROOT = Path(__file__).parent.parent 22 23 # Key directories to search 24 SEARCH_DIRS = [ 25 "core/", # Core modules 26 "scripts/", # Tools and utilities 27 "patterns/", # Design patterns 28 ] 29 30 # File patterns that often contain reusable code 31 CODE_PATTERNS = ["*.py", "*.js", "*.md"] 32 33 34 def search_codebase(query: str, verbose: bool = False) -> List[Tuple[str, str, int]]: 35 """ 36 Search codebase for query terms. 37 38 Returns list of (file, matching_line, line_number). 39 """ 40 results = [] 41 42 # Split query into terms 43 terms = query.lower().split() 44 45 for search_dir in SEARCH_DIRS: 46 dir_path = REPO_ROOT / search_dir 47 if not dir_path.exists(): 48 continue 49 50 # Use grep for fast search 51 try: 52 for term in terms: 53 cmd = [ 54 "grep", "-r", "-i", "-n", 55 "--include=*.py", "--include=*.js", "--include=*.md", 56 term, str(dir_path) 57 ] 58 result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) 59 60 for line in result.stdout.strip().split('\n'): 61 if line and ':' in line: 62 parts = line.split(':', 2) 63 if len(parts) >= 3: 64 filepath = parts[0].replace(str(REPO_ROOT) + '/', '') 65 lineno = int(parts[1]) if parts[1].isdigit() else 0 66 content = parts[2].strip()[:100] 67 results.append((filepath, content, lineno)) 68 except Exception as e: 69 if verbose: 70 print(f"Search error: {e}") 71 72 # Deduplicate by file+line 73 seen = set() 74 unique = [] 75 for r in results: 76 key = (r[0], r[2]) 77 if key not in seen: 78 seen.add(key) 79 unique.append(r) 80 81 return unique[:30] # Limit results 82 83 84 def analyze_results(results: List[Tuple[str, str, int]], query: str) -> dict: 85 """ 86 Analyze search results to suggest existing systems. 87 """ 88 analysis = { 89 "total_matches": len(results), 90 "files_touched": len(set(r[0] for r in results)), 91 "core_modules": [], 92 "scripts": [], 93 "patterns": [], 94 "recommendation": "" 95 } 96 97 for filepath, content, lineno in results: 98 if filepath.startswith("core/"): 99 analysis["core_modules"].append(f"{filepath}:{lineno}") 100 elif filepath.startswith("scripts/"): 101 analysis["scripts"].append(f"{filepath}:{lineno}") 102 elif filepath.startswith("patterns/"): 103 analysis["patterns"].append(f"{filepath}:{lineno}") 104 105 # Generate recommendation 106 if analysis["core_modules"]: 107 analysis["recommendation"] = ( 108 f"FOUND in core/: Check {analysis['core_modules'][0]} first. " 109 "Extend existing module rather than creating new one." 110 ) 111 elif analysis["scripts"]: 112 analysis["recommendation"] = ( 113 f"FOUND in scripts/: Check {analysis['scripts'][0]}. " 114 "Can this be extended or composed with?" 115 ) 116 elif analysis["patterns"]: 117 analysis["recommendation"] = ( 118 f"Pattern exists: See {analysis['patterns'][0]}. " 119 "Follow existing pattern rather than inventing new approach." 120 ) 121 else: 122 analysis["recommendation"] = ( 123 "No existing implementation found. " 124 "This may be genuinely new - document why." 125 ) 126 127 return analysis 128 129 130 def main(): 131 if len(sys.argv) < 2: 132 print("Usage: python3 scripts/search_before_build.py <query> [--verbose] [--suggest]") 133 print("\nExamples:") 134 print(' python3 scripts/search_before_build.py "resonance threshold"') 135 print(' python3 scripts/search_before_build.py "mesh broadcast" --suggest') 136 sys.exit(1) 137 138 query = sys.argv[1] 139 verbose = "--verbose" in sys.argv or "-v" in sys.argv 140 suggest = "--suggest" in sys.argv or "-s" in sys.argv 141 142 print(f"=" * 60) 143 print(f"SEARCH BEFORE BUILD: '{query}'") 144 print(f"=" * 60) 145 print() 146 147 results = search_codebase(query, verbose) 148 149 if not results: 150 print("No matches found.") 151 print("\nThis may be genuinely new functionality.") 152 print("If building: Document why this is necessary.") 153 else: 154 print(f"Found {len(results)} matches:\n") 155 156 # Group by file 157 by_file = {} 158 for filepath, content, lineno in results: 159 if filepath not in by_file: 160 by_file[filepath] = [] 161 by_file[filepath].append((content, lineno)) 162 163 for filepath, matches in sorted(by_file.items()): 164 print(f" {filepath}") 165 for content, lineno in matches[:3]: 166 print(f" L{lineno}: {content[:60]}...") 167 if len(matches) > 3: 168 print(f" ... and {len(matches) - 3} more") 169 print() 170 171 if suggest or results: 172 analysis = analyze_results(results, query) 173 print("-" * 60) 174 print("RECOMMENDATION:") 175 print(f" {analysis['recommendation']}") 176 print("-" * 60) 177 178 # Exit code: 0 if found (suggests integration), 1 if not found (may build new) 179 sys.exit(0 if results else 1) 180 181 182 if __name__ == "__main__": 183 main()