Spaces:
Running
Running
| import os | |
| import sys | |
| import json | |
| import subprocess | |
| from pathlib import Path | |
| def main(): | |
| print("[graphify update] Starting incremental update...") | |
| # 1. Paths | |
| backend_path = Path('C:/Users/HP/Desktop/Segmento/SegmentoPulse-Backend/SegmentoPulse/backend') | |
| graphify_out = backend_path / 'graphify-out' | |
| if not graphify_out.exists(): | |
| graphify_out.mkdir(parents=True, exist_ok=True) | |
| # 2. Detect incremental changes | |
| from graphify.detect import detect_incremental, save_manifest | |
| result = detect_incremental(backend_path) | |
| new_total = result.get('new_total', 0) | |
| deleted = list(result.get('deleted_files', [])) | |
| print(f"Incremental detection: {new_total} new/changed file(s), {len(deleted)} deleted file(s) to prune.") | |
| # Save incremental state | |
| (graphify_out / '.graphify_incremental.json').write_text(json.dumps(result, ensure_ascii=False), encoding="utf-8") | |
| if new_total == 0 and not deleted: | |
| print("[graphify update] No files changed since last run. Nothing to update.") | |
| return | |
| # 3. Populate .graphify_detect.json | |
| detect_data = { | |
| 'files': result.get('new_files', {}), | |
| 'all_files': result.get('files', {}), | |
| 'total_files': result.get('new_total', 0), | |
| 'total_words': result.get('total_words', 0), | |
| 'skipped_sensitive': result.get('skipped_sensitive', []), | |
| 'needs_graph': True, | |
| } | |
| (graphify_out / '.graphify_detect.json').write_text(json.dumps(detect_data, ensure_ascii=False), encoding="utf-8") | |
| # 4. Check if code only | |
| code_exts = {'.py','.ts','.js','.go','.rs','.java','.cpp','.c','.rb','.swift','.kt','.cs','.scala','.php','.cc','.cxx','.hpp','.h','.kts','.lua','.toc','.f','.F','.f90','.F90','.f95','.F95','.f03','.F03','.f08','.F08'} | |
| new_files = result.get('new_files', {}) | |
| all_changed = [f for files in new_files.values() for f in files] | |
| code_only = all(Path(f).suffix.lower() in code_exts for f in all_changed) | |
| # 5. Part A: AST Extraction for code files | |
| from graphify.extract import collect_files, extract | |
| code_files = [] | |
| for f in result.get('new_files', {}).get('code', []): | |
| code_files.extend(collect_files(Path(f)) if Path(f).is_dir() else [Path(f)]) | |
| if code_files: | |
| print(f"[graphify update] Extracting AST for {len(code_files)} code files...") | |
| ast_result = extract(code_files, cache_root=backend_path) | |
| (graphify_out / '.graphify_ast.json').write_text(json.dumps(ast_result, indent=2, ensure_ascii=False), encoding="utf-8") | |
| print(f"AST Extraction complete: {len(ast_result['nodes'])} nodes, {len(ast_result['edges'])} edges") | |
| else: | |
| ast_result = {'nodes':[],'edges':[],'input_tokens':0,'output_tokens':0} | |
| (graphify_out / '.graphify_ast.json').write_text(json.dumps(ast_result, ensure_ascii=False), encoding="utf-8") | |
| print("No code files to extract AST from.") | |
| # 6. Part B: Semantic Extraction | |
| semantic_result = {'nodes':[],'edges':[],'hyperedges':[],'input_tokens':0,'output_tokens':0} | |
| print("[graphify update] Running semantic extraction on new/changed files...") | |
| # Get all changed files (including code files for semantic relationship extraction) | |
| semantic_files = [] | |
| for category, files in result.get('new_files', {}).items(): | |
| semantic_files.extend(files) | |
| if semantic_files: | |
| from graphify.cache import check_semantic_cache, save_semantic_cache | |
| cached_nodes, cached_edges, cached_hyperedges, uncached = check_semantic_cache(semantic_files) | |
| print(f"Cache hit: {len(semantic_files) - len(uncached)} files, {len(uncached)} files need extraction.") | |
| # Save cached to temp | |
| (graphify_out / '.graphify_cached.json').write_text(json.dumps({ | |
| 'nodes': cached_nodes, | |
| 'edges': cached_edges, | |
| 'hyperedges': cached_hyperedges | |
| }, ensure_ascii=False), encoding="utf-8") | |
| new_nodes, new_edges, new_hyperedges = [], [], [] | |
| input_tokens, output_tokens = 0, 0 | |
| if uncached: | |
| openrouter_key = os.environ.get('OPENROUTER_API_KEY') | |
| gemini_key = os.environ.get('GEMINI_API_KEY') or os.environ.get('GOOGLE_API_KEY') | |
| if openrouter_key: | |
| from graphify.llm import extract_corpus_parallel | |
| print(f"Extracting semantics for {len(uncached)} files via OpenRouter API (gpt-oss-120b)...") | |
| extracted = extract_corpus_parallel([Path(f) for f in uncached], backend="openrouter", api_key=openrouter_key) | |
| new_nodes = extracted.get('nodes', []) | |
| new_edges = extracted.get('edges', []) | |
| new_hyperedges = extracted.get('hyperedges', []) | |
| input_tokens = extracted.get('input_tokens', 0) | |
| output_tokens = extracted.get('output_tokens', 0) | |
| # Save new to cache | |
| save_semantic_cache(new_nodes, new_edges, new_hyperedges) | |
| elif gemini_key: | |
| from graphify.llm import extract_corpus_parallel | |
| print(f"Extracting semantics for {len(uncached)} files via Gemini API...") | |
| extracted = extract_corpus_parallel([Path(f) for f in uncached], backend="gemini", api_key=gemini_key) | |
| new_nodes = extracted.get('nodes', []) | |
| new_edges = extracted.get('edges', []) | |
| new_hyperedges = extracted.get('hyperedges', []) | |
| input_tokens = extracted.get('input_tokens', 0) | |
| output_tokens = extracted.get('output_tokens', 0) | |
| # Save new to cache | |
| save_semantic_cache(new_nodes, new_edges, new_hyperedges) | |
| else: | |
| print("WARNING: Neither OPENROUTER_API_KEY nor GEMINI_API_KEY/GOOGLE_API_KEY is set in environment.") | |
| print("Skipping semantic extraction of uncached files.") | |
| # Merge cached and new | |
| all_nodes = cached_nodes + new_nodes | |
| all_edges = cached_edges + new_edges | |
| all_hyperedges = cached_hyperedges + new_hyperedges | |
| # Dedup nodes by id | |
| seen = set() | |
| deduped_nodes = [] | |
| for n in all_nodes: | |
| if n['id'] not in seen: | |
| seen.add(n['id']) | |
| deduped_nodes.append(n) | |
| semantic_result = { | |
| 'nodes': deduped_nodes, | |
| 'edges': all_edges, | |
| 'hyperedges': all_hyperedges, | |
| 'input_tokens': input_tokens, | |
| 'output_tokens': output_tokens, | |
| } | |
| (graphify_out / '.graphify_semantic.json').write_text(json.dumps(semantic_result, indent=2, ensure_ascii=False), encoding="utf-8") | |
| # 7. Part C: Merge AST and Semantic | |
| ast = json.loads((graphify_out / '.graphify_ast.json').read_text(encoding="utf-8")) | |
| sem = json.loads((graphify_out / '.graphify_semantic.json').read_text(encoding="utf-8")) | |
| seen = {n['id'] for n in ast['nodes']} | |
| merged_nodes = list(ast['nodes']) | |
| for n in sem['nodes']: | |
| if n['id'] not in seen: | |
| merged_nodes.append(n) | |
| seen.add(n['id']) | |
| merged_edges = ast['edges'] + sem['edges'] | |
| merged_hyperedges = sem.get('hyperedges', []) | |
| merged_extract = { | |
| 'nodes': merged_nodes, | |
| 'edges': merged_edges, | |
| 'hyperedges': merged_hyperedges, | |
| 'input_tokens': sem.get('input_tokens', 0), | |
| 'output_tokens': sem.get('output_tokens', 0), | |
| } | |
| (graphify_out / '.graphify_extract.json').write_text(json.dumps(merged_extract, indent=2, ensure_ascii=False), encoding="utf-8") | |
| # 8. Merge with existing graph | |
| existing_graph = graphify_out / 'graph.json' | |
| if existing_graph.exists(): | |
| # Backup | |
| import shutil | |
| shutil.copy2(existing_graph, graphify_out / '.graphify_old.json') | |
| from graphify.build import build_merge, build_from_json | |
| # Run build_merge | |
| G = build_merge( | |
| [merged_extract], | |
| graph_path=str(existing_graph) if existing_graph.exists() else None, | |
| prune_sources=deleted or None, | |
| ) | |
| print(f"[graphify update] Merged graph has {G.number_of_nodes()} nodes, {G.number_of_edges()} edges") | |
| # Write merged result back to .graphify_extract.json so Step 4/analysis sees the full graph | |
| merged_out = { | |
| 'nodes': [{'id': n, **d} for n, d in G.nodes(data=True)], | |
| 'edges': [ | |
| {**{k: val for k, val in d.items() if k not in ('_src', '_tgt', 'source', 'target')}, | |
| 'source': d.get('_src', u), 'target': d.get('_tgt', v)} | |
| for u, v, d in G.edges(data=True) | |
| ], | |
| 'hyperedges': list(G.graph.get('hyperedges', [])), | |
| 'input_tokens': merged_extract.get('input_tokens', 0), | |
| 'output_tokens': merged_extract.get('output_tokens', 0), | |
| } | |
| (graphify_out / '.graphify_extract.json').write_text(json.dumps(merged_out, ensure_ascii=False), encoding="utf-8") | |
| # Save manifest for next runs | |
| save_manifest(result['files']) | |
| # 9. Clustering & Analysis | |
| from graphify.cluster import cluster, score_all | |
| from graphify.analyze import god_nodes, surprising_connections, suggest_questions | |
| from graphify.report import generate | |
| from graphify.export import to_json | |
| print("[graphify update] Running clustering and analysis...") | |
| communities = cluster(G) | |
| cohesion = score_all(G, communities) | |
| tokens = {'input': merged_extract.get('input_tokens', 0), 'output': merged_extract.get('output_tokens', 0)} | |
| gods = god_nodes(G) | |
| surprises = surprising_connections(G, communities) | |
| # Load or generate community labels | |
| labels = {} | |
| labels_file = graphify_out / '.graphify_labels.json' | |
| if labels_file.exists(): | |
| try: | |
| old_labels = json.loads(labels_file.read_text(encoding="utf-8")) | |
| labels = {int(k): v for k, v in old_labels.items()} | |
| except Exception: | |
| pass | |
| # For any new communities, add default labels | |
| for cid in communities: | |
| if cid not in labels: | |
| labels[cid] = f"Community {cid}" | |
| questions = suggest_questions(G, communities, labels) | |
| report = generate(G, communities, cohesion, labels, gods, surprises, detect_data, tokens, str(backend_path), suggested_questions=questions) | |
| (graphify_out / 'GRAPH_REPORT.md').write_text(report, encoding="utf-8") | |
| # Export raw graph | |
| to_json(G, communities, str(existing_graph), force=True) | |
| analysis = { | |
| 'communities': {str(k): v for k, v in communities.items()}, | |
| 'cohesion': {str(k): v for k, v in cohesion.items()}, | |
| 'gods': gods, | |
| 'surprises': surprises, | |
| 'questions': questions, | |
| } | |
| (graphify_out / '.graphify_analysis.json').write_text(json.dumps(analysis, indent=2, ensure_ascii=False), encoding="utf-8") | |
| (graphify_out / '.graphify_labels.json').write_text(json.dumps({str(k): v for k, v in labels.items()}, ensure_ascii=False), encoding="utf-8") | |
| # Show diff if backup exists | |
| old_backup = graphify_out / '.graphify_old.json' | |
| if old_backup.exists(): | |
| try: | |
| from graphify.analyze import graph_diff | |
| from networkx.readwrite import json_graph | |
| old_data = json.loads(old_backup.read_text(encoding="utf-8")) | |
| G_old = json_graph.node_link_graph(old_data, edges='links') | |
| diff = graph_diff(G_old, G) | |
| print("\n=== Graph Changes ===") | |
| print(diff['summary']) | |
| if diff['new_nodes']: | |
| print('New nodes:', ', '.join(n['label'] for n in diff['new_nodes'][:5])) | |
| if diff['new_edges']: | |
| print('New edges:', len(diff['new_edges'])) | |
| except Exception as e: | |
| print(f"Could not compute graph diff: {e}") | |
| # 10. Generate HTML viz | |
| print("[graphify update] Generating HTML visualization...") | |
| try: | |
| subprocess.run([sys.executable, "-m", "graphify", "export", "html"], cwd=str(backend_path), check=True) | |
| except Exception: | |
| try: | |
| subprocess.run(["graphify", "export", "html"], cwd=str(backend_path), shell=True, check=True) | |
| except Exception as e: | |
| print(f"Error generating HTML viz: {e}") | |
| # 11. Cost tracking and cleanup | |
| input_tok = merged_extract.get('input_tokens', 0) | |
| output_tok = merged_extract.get('output_tokens', 0) | |
| cost_path = graphify_out / 'cost.json' | |
| if cost_path.exists(): | |
| try: | |
| cost = json.loads(cost_path.read_text(encoding="utf-8")) | |
| except Exception: | |
| cost = {'runs': [], 'total_input_tokens': 0, 'total_output_tokens': 0} | |
| else: | |
| cost = {'runs': [], 'total_input_tokens': 0, 'total_output_tokens': 0} | |
| import time | |
| cost['runs'].append({ | |
| 'date': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), | |
| 'input_tokens': input_tok, | |
| 'output_tokens': output_tok, | |
| 'files': detect_data.get('total_files', 0), | |
| }) | |
| cost['total_input_tokens'] += input_tok | |
| cost['total_output_tokens'] += output_tok | |
| cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding="utf-8") | |
| # Cleanup temp files | |
| temp_files = [ | |
| '.graphify_detect.json', | |
| '.graphify_extract.json', | |
| '.graphify_ast.json', | |
| '.graphify_semantic.json', | |
| '.graphify_analysis.json', | |
| '.graphify_old.json', | |
| '.graphify_cached.json' | |
| ] | |
| for tf in temp_files: | |
| fpath = graphify_out / tf | |
| if fpath.exists(): | |
| try: | |
| fpath.unlink() | |
| except Exception: | |
| pass | |
| print("\n=== Update Complete ===") | |
| print(f"Merged: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities") | |
| print(f"This run: {input_tok:,} input tokens, {output_tok:,} output tokens") | |
| print(f"All time: {cost['total_input_tokens']:,} input, {cost['total_output_tokens']:,} output") | |
| if __name__ == "__main__": | |
| main() | |