- 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>
284 lines
11 KiB
Python
284 lines
11 KiB
Python
import os
|
|
import json
|
|
import datetime
|
|
import re
|
|
|
|
# ==========================================
|
|
# 1. Constants & Configuration (Ported from lib/core & lib/pdca)
|
|
# ==========================================
|
|
|
|
PDCA_PHASES = {
|
|
'PLAN': {'order': 1, 'name': 'Plan', 'icon': '📋'},
|
|
'DESIGN': {'order': 2, 'name': 'Design', 'icon': '📐'},
|
|
'DO': {'order': 3, 'name': 'Do', 'icon': '🔨'},
|
|
'CHECK': {'order': 4, 'name': 'Check', 'icon': '🔍'},
|
|
'ACT': {'order': 5, 'name': 'Act', 'icon': '🔄'},
|
|
'REPORT': {'order': 6, 'name': 'Report', 'icon': '📊'}
|
|
}
|
|
|
|
LEVEL_PHASE_MAP = {
|
|
"Starter": {
|
|
"required": ["PLAN", "DO", "CHECK"],
|
|
"optional": ["DESIGN"],
|
|
"skip": ["PHASE-1", "PHASE-2", "PHASE-4", "PHASE-7", "PHASE-9"]
|
|
},
|
|
"Dynamic": {
|
|
"required": ["PLAN", "DESIGN", "DO", "CHECK", "REPORT"],
|
|
"optional": ["PHASE-3", "PHASE-5", "PHASE-6"],
|
|
"skip": ["PHASE-9"]
|
|
},
|
|
"Enterprise": {
|
|
"required": ["PLAN", "DESIGN", "DO", "CHECK", "REPORT",
|
|
"PHASE-1", "PHASE-2", "PHASE-3", "PHASE-4",
|
|
"PHASE-5", "PHASE-6", "PHASE-7", "PHASE-8", "PHASE-9"],
|
|
"optional": [],
|
|
"skip": []
|
|
}
|
|
}
|
|
|
|
TEAM_STRATEGIES = {
|
|
"Starter": None,
|
|
"Dynamic": {
|
|
"teammates": 3,
|
|
"cto_agent": "cto-lead",
|
|
"roles": [
|
|
{"name": "developer", "agents": ["bkend-expert"], "phases": ["DO", "ACT"]},
|
|
{"name": "frontend", "agents": ["frontend-architect"], "phases": ["DESIGN", "DO"]},
|
|
{"name": "qa", "agents": ["qa-monitor", "gap-detector"], "phases": ["CHECK"]}
|
|
]
|
|
},
|
|
"Enterprise": {
|
|
"teammates": 5,
|
|
"cto_agent": "cto-lead",
|
|
"roles": [
|
|
{"name": "architect", "agents": ["enterprise-expert", "infra-architect"], "phases": ["DESIGN"]},
|
|
{"name": "developer", "agents": ["bkend-expert", "frontend-architect"], "phases": ["DO", "ACT"]},
|
|
{"name": "qa", "agents": ["qa-strategist", "qa-monitor", "gap-detector"], "phases": ["CHECK"]},
|
|
{"name": "reviewer", "agents": ["code-analyzer", "design-validator"], "phases": ["CHECK", "ACT"]},
|
|
{"name": "security", "agents": ["security-architect"], "phases": ["DESIGN", "CHECK"]}
|
|
]
|
|
}
|
|
}
|
|
|
|
# ==========================================
|
|
# 2. State Engine Logic
|
|
# ==========================================
|
|
|
|
class ContextManager:
|
|
"""
|
|
Manages global state, feature context, and project level detection.
|
|
Ported from lib/task/context.js & lib/pdca/level.js
|
|
"""
|
|
def __init__(self, state_file_path):
|
|
self.state_file_path = state_file_path
|
|
self.state = self._load_state()
|
|
|
|
def _load_state(self):
|
|
if not os.path.exists(self.state_file_path):
|
|
return {
|
|
"project_name": "Antigravity Project",
|
|
"project_level": "Dynamic", # Default
|
|
"current_feature": None,
|
|
"features": {},
|
|
"team_config": {
|
|
"enabled": False,
|
|
"max_teammates": 4
|
|
},
|
|
"last_bootup": None
|
|
}
|
|
try:
|
|
with open(self.state_file_path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except json.JSONDecodeError:
|
|
return self._load_state() # Recursive retry with default if corrupt (handled by external reset usually)
|
|
|
|
def _save_state(self):
|
|
with open(self.state_file_path, 'w', encoding='utf-8') as f:
|
|
json.dump(self.state, f, indent=2, ensure_ascii=False)
|
|
|
|
def detect_project_level(self, project_root="."):
|
|
"""
|
|
Ported from lib/pdca/level.js: detectLevel()
|
|
"""
|
|
try:
|
|
files = os.listdir(project_root)
|
|
files_str = " ".join(files).lower()
|
|
|
|
# Enterprise Markers
|
|
if any(m in files_str for m in ['kubernetes', 'k8s', 'terraform', 'microservices', 'docker-compose']):
|
|
self.state['project_level'] = "Enterprise"
|
|
return "Enterprise"
|
|
|
|
# Dynamic Markers
|
|
if any(m in files for m in ['package.json', 'requirements.txt', 'go.mod', 'Cargo.toml', 'pom.xml']):
|
|
self.state['project_level'] = "Dynamic"
|
|
return "Dynamic"
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
self.state['project_level'] = "Starter"
|
|
return "Starter"
|
|
|
|
def set_active_feature(self, feature_name):
|
|
self.state['current_feature'] = feature_name
|
|
if feature_name and feature_name not in self.state['features']:
|
|
self.state['features'][feature_name] = {
|
|
"phase": "PLAN",
|
|
"match_rate": 0,
|
|
"history": []
|
|
}
|
|
self._save_state()
|
|
|
|
def get_active_feature(self):
|
|
return self.state.get('current_feature')
|
|
|
|
def get_feature_status(self, feature_name):
|
|
return self.state.get('features', {}).get(feature_name)
|
|
|
|
|
|
class PDCAStatus:
|
|
"""
|
|
Manages Phase Transitions and Deliverable Checks.
|
|
Ported from lib/pdca/phase.js
|
|
"""
|
|
def __init__(self, context_manager):
|
|
self.ctx = context_manager
|
|
|
|
def validate_transition(self, feature_name, to_phase):
|
|
"""
|
|
Ported from lib/pdca/phase.js: validatePdcaTransition()
|
|
NOW INCLUDES LEVEL-BASED VALIDATION
|
|
"""
|
|
feature = self.ctx.get_feature_status(feature_name)
|
|
if not feature:
|
|
return {"valid": True, "reason": "New feature"}
|
|
|
|
from_phase = feature.get('phase', 'PLAN').upper()
|
|
to_phase = to_phase.upper()
|
|
|
|
from_order = PDCA_PHASES.get(from_phase, {}).get('order', 0)
|
|
to_order = PDCA_PHASES.get(to_phase, {}).get('order', 0)
|
|
|
|
# Allow going back
|
|
if to_order < from_order:
|
|
return {"valid": True, "reason": "Returning to earlier phase"}
|
|
|
|
# Check Level Requirements
|
|
level = self.ctx.state.get("project_level", "Dynamic")
|
|
level_config = LEVEL_PHASE_MAP.get(level, LEVEL_PHASE_MAP["Dynamic"])
|
|
|
|
# Iterate phases between from_phase and to_phase
|
|
# e.g. PLAN(1) -> DO(3). Check if DESIGN(2) is required.
|
|
for i in range(from_order + 1, to_order):
|
|
intermediate_phase_name = ""
|
|
for name, info in PDCA_PHASES.items():
|
|
if info['order'] == i:
|
|
intermediate_phase_name = name
|
|
break
|
|
|
|
if intermediate_phase_name and intermediate_phase_name in level_config['required']:
|
|
return {"valid": False, "reason": f"Cannot skip required phase: {intermediate_phase_name} ({level} Level)"}
|
|
|
|
# Cannot skip more than 1 phase generally (sanity check)
|
|
if to_order > from_order + 1:
|
|
# But allow if intermediate is NOT required
|
|
# The loop above handles the "Required" check.
|
|
# If we are here, it means intermediate phases were optional or skipped.
|
|
pass
|
|
|
|
return {"valid": True, "reason": "Transition allowed"}
|
|
|
|
def update_phase(self, feature_name, phase, match_rate=None):
|
|
phase = phase.upper() # Normalize
|
|
|
|
# Validate first
|
|
validation = self.validate_transition(feature_name, phase)
|
|
if not validation['valid']:
|
|
print(f"Warning: Invalid transition - {validation['reason']}")
|
|
# We enforce it anyway for now as agent override, but log it
|
|
|
|
feature = self.ctx.get_feature_status(feature_name)
|
|
if feature:
|
|
feature['phase'] = phase
|
|
if match_rate is not None:
|
|
feature['match_rate'] = match_rate
|
|
|
|
entry = {
|
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
"action": "phase_update",
|
|
"phase": phase,
|
|
"match_rate": match_rate
|
|
}
|
|
feature['history'].append(entry)
|
|
self.ctx._save_state()
|
|
|
|
class Guardrails:
|
|
"""
|
|
Implements safety checks like Loop Detection.
|
|
Ported from hmcmkit (Hybrid JS -> Python approach)
|
|
"""
|
|
def __init__(self, context_manager):
|
|
self.ctx = context_manager
|
|
|
|
def check_loop(self, feature_name):
|
|
feature = self.ctx.get_feature_status(feature_name)
|
|
if not feature or 'history' not in feature:
|
|
return {"safe": True}
|
|
|
|
recent = feature['history'][-10:] if len(feature['history']) >= 10 else feature['history']
|
|
check_act_count = sum(1 for h in recent if h.get('phase', '') in ['CHECK', 'ACT'])
|
|
|
|
if check_act_count >= 8: # 4 cycles of check-act
|
|
return {"safe": False, "reason": "무한 루프 감지: CHECK/ACT 반복이 4회를 초과했습니다. 자동 수정을 멈추고 사용자에게 보고하세요."}
|
|
|
|
return {"safe": True}
|
|
|
|
|
|
class TeamCoordinator:
|
|
"""
|
|
Manages Team Strategy and Roles.
|
|
Ported from lib/team/strategy.js & lib/team/coordinator.js
|
|
"""
|
|
def __init__(self, context_manager):
|
|
self.ctx = context_manager
|
|
|
|
def get_team_strategy(self):
|
|
level = self.ctx.state.get("project_level", "Dynamic")
|
|
return TEAM_STRATEGIES.get(level, TEAM_STRATEGIES["Dynamic"])
|
|
|
|
def suggest_team_mode(self, user_message):
|
|
"""
|
|
Ported from lib/team/coordinator.js: suggestTeamMode()
|
|
"""
|
|
level = self.ctx.state.get("project_level", "Dynamic")
|
|
|
|
if level == "Starter":
|
|
return {"suggest": False, "reason": "Starter level does not support teams"}
|
|
|
|
# Logic: Message > 1000 chars or Keyword trigger
|
|
if len(user_message) >= 1000:
|
|
return {"suggest": True, "reason": "Long message detected (Major Feature)"}
|
|
|
|
keywords = ["team", "project lead", "cto", "role", "assign"]
|
|
if any(k in user_message.lower() for k in keywords):
|
|
return {"suggest": True, "reason": "Team keywords detected"}
|
|
|
|
return {"suggest": False}
|
|
|
|
def get_roles_for_phase(self, phase):
|
|
strategy = self.get_team_strategy()
|
|
if not strategy: return []
|
|
|
|
phase = phase.upper()
|
|
active_roles = []
|
|
for role in strategy['roles']:
|
|
if phase in role['phases']:
|
|
active_roles.append(role)
|
|
return active_roles
|
|
|
|
if __name__ == "__main__":
|
|
# Test Suite
|
|
# mgr = ContextManager(".hmcmkit/state.json")
|
|
pass
|