import os import sys import json import datetime import shutil import re from state_engine import ContextManager, Guardrails def detect_stale_features(ctx): features = ctx.state.get('features', {}) stale = [] now = datetime.datetime.now() for fname, fdata in features.items(): # archive된거 제외하려면 로직 필요하나 일단 history 마지막 날짜 기준 if fdata.get('phase') in ['REPORT', 'ARCHIVE']: continue history = fdata.get('history', []) if not history: continue try: last_time = datetime.datetime.fromisoformat(history[-1].get('timestamp', '')) days_idle = (now - last_time).days if days_idle >= 2: # Reduce to 2 for tighter detection in AI contexts stale.append({"feature": fname, "daysIdle": days_idle, "phase": fdata.get('phase')}) except: pass return stale def print_agent_context_injection(ctx): """ Prints a markdown-formatted context for the agent to consume immediately. """ state = ctx.state current_feature = state.get('current_feature') level = state.get('project_level', 'Starter') # Read blueprint for goal blueprint_path = os.path.join(".hmcmkit", "blueprint.md") goal = "Unknown" if os.path.exists(blueprint_path): try: with open(blueprint_path, "r", encoding="utf-8") as f: content = f.read() # Simple extraction for goal match = re.search(r"- \*\*Goal\*\*: (.*)", content) if match: goal = match.group(1) except Exception: pass print("\n" + "="*60) print("🚀 HMCMkit DYNAMIC CONTEXT INJECTION (Action Items)") print("="*60) print(f"\n### 📍 Current Project Status") print(f"- **Project Name**: {state.get('project_name')}") print(f"- **Project Goal**: {goal}") print(f"- **Project Level**: {level}") print(f"- **Active Feature**: {current_feature or 'None (Ready for new task)'}") if current_feature: feature_data = state.get('features', {}).get(current_feature, {}) print(f"- **Current Phase**: {feature_data.get('phase', 'PLAN')}") print(f"- **Match Rate**: {feature_data.get('match_rate', 0)}%") print(f"\n### 🎯 Recommended Next Action") if not current_feature: print("1. 상단 `blueprint.md`를 읽고 프로젝트의 현황을 파악하세요.") print("2. 사용자에게 다음에 개발할 기능(Feature)이 무엇인지 물어보거나, `blueprint.md`에 명시된 할 일을 제안하세요.") else: phase = state.get('features', {}).get(current_feature, {}).get('phase', 'PLAN') print(f"1. 현재 활성화된 기능 `{current_feature}`의 `{phase}` 단계를 계속 진행하세요.") print(f"2. 만약 `{phase}` 단계가 완료되었다면, `Check` 단계를 거쳐 다음 단계로 전이(Transition)를 제안하세요.") guard = Guardrails(ctx) loop_check = guard.check_loop(current_feature) if not loop_check['safe']: print(f"\n 🚨 [루프 차단 가드레일]: {loop_check['reason']}") stale = detect_stale_features(ctx) if stale: print("\n### ⚠️ Stale Feature 경고") for s in stale: print(f"- `{s['feature']}`: {s['daysIdle']}일 동안 변경 사항이 없습니다. (현재: {s['phase']})") print("\n" + "="*60 + "\n") def sync_skills_to_native_ide(): """ Syncs HMCMkit skills to the native .agent/skills directory. Uses Symlink first, fallbacks to Copy on error. """ src_dir = os.path.join(".hmcmkit", "knowledge", "skills") dest_dir = os.path.join(".agents", "skills") if not os.path.exists(src_dir): return if not os.path.exists(dest_dir): os.makedirs(dest_dir, exist_ok=True) print(f"🔄 Syncing skills to native IDE: {dest_dir}") for item in os.listdir(src_dir): s_path = os.path.abspath(os.path.join(src_dir, item)) d_path = os.path.abspath(os.path.join(dest_dir, item)) # Cleanup existing if os.path.exists(d_path) or os.path.islink(d_path): try: if os.path.islink(d_path) or os.path.isfile(d_path): os.remove(d_path) elif os.path.isdir(d_path): shutil.rmtree(d_path) except Exception as e: print(f" ⚠️ Could not cleanup {item}: {e}") continue # Try Symlink first try: os.symlink(s_path, d_path, target_is_directory=os.path.isdir(s_path)) print(f" [LINK] {item}") except Exception as e: # Fallback to Copy try: if os.path.isdir(s_path): shutil.copytree(s_path, d_path) else: shutil.copy2(s_path, d_path) print(f" [COPY] {item} (Symlink failed: {e})") except Exception as copy_e: print(f" ⚠️ Failed to sync {item}: {copy_e}") def bootup_check(): """ Simulates hooks/session-start.js logic. Checks if .hmcmkit environment is valid and updates last_access timestamp. """ hmcmkit_root = ".hmcmkit" if not os.path.exists(hmcmkit_root): print(f"Error: {hmcmkit_root} directory not found. Please initialize first.") return False # Check core files required_files = [ os.path.join(hmcmkit_root, "config.json"), os.path.join(hmcmkit_root, "state.json") ] missing = [f for f in required_files if not os.path.exists(f)] if missing: print(f"❌ Error: Missing core files: {missing}") return False # v1.0.2: Auto-generate Index if missing index_path = os.path.join(hmcmkit_root, "system", "_GRAPH-INDEX.md") if not os.path.exists(index_path): print("🔍 Knowledge Index missing. Generating...") try: from graph_index import generate_graph_index generate_graph_index(hmcmkit_root) except Exception as e: print(f"⚠️ Warning: Could not generate Knowledge Index: {e}") # Update state last_access ctx = ContextManager(os.path.join(hmcmkit_root, "state.json")) ctx.state['last_bootup'] = datetime.datetime.now().isoformat() ctx._save_state() # Auto-detect Project Level level = ctx.detect_project_level() ctx._save_state() # v1.0.1: Ensure backup dir exists backup_dir = os.path.join(hmcmkit_root, "backups") if not os.path.exists(backup_dir): os.makedirs(backup_dir) # NATIVE SKILL SYNC sync_skills_to_native_ide() # Check for active feature current_feature = ctx.get_active_feature() print(f"\n✅ System Ready (Level: {level})") print(f" Active Feature: {current_feature or 'None'}") # DYNAMIC CONTEXT INJECTION print_agent_context_injection(ctx) print("\n [Usage Hints]") print(" - Plan : 'I need a feature...'") print(" - Do : 'Implement the feature...'") print(" - Check: 'Verify my work...'") return True if __name__ == "__main__": bootup_check()