- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
197 lines
7.3 KiB
Python
197 lines
7.3 KiB
Python
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()
|