chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합

- 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>
This commit is contained in:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
import re
import sys
import json
def calculate_ambiguity_score(user_request, context={}):
score = 0
factors = []
# Factor 1: No file path (higher ambiguity)
file_path_pattern = r'(\/[\w.-]+\/[\w.-]+)|([A-Z]:\\[\w.-]+\\[\w.-]+)|(\.\/[\w.-]+)|(\.(js|ts|py|go|rs|java|tsx|jsx|vue|svelte|md|json|yaml|yml)$)'
if not re.search(file_path_pattern, user_request, re.IGNORECASE):
factors.append('no_file_path')
score += 0.15
# Factor 2: No technical terms
tech_terms = [
'api', 'database', 'server', 'client', 'component', 'module',
'function', 'class', 'interface', 'type', 'schema', 'endpoint',
'authentication', 'authorization', 'middleware', 'controller',
'service', 'repository', 'model', 'view', 'hook', 'context'
]
if not any(term in user_request.lower() for term in tech_terms):
factors.append('no_technical_terms')
score += 0.1
# Factor 3: No specific nouns (PascalCase or camelCase)
if not re.search(r'("[^"]+")|(\'[^\']+\')|([A-Z][a-z]+[A-Z][a-z]+)|([a-z]+[A-Z][a-z]+)', user_request):
factors.append('no_specific_nouns')
score += 0.15
# Factor 4: No scope definition
scope_patterns = [
r'only|just|specifically|exactly',
r'all|every|entire|whole',
r'from\s+\w+\s+to\s+\w+',
r'in\s+the\s+\w+\s+(?:file|folder|directory|module)'
]
if not any(re.search(p, user_request, re.IGNORECASE) for p in scope_patterns):
factors.append('no_scope')
score += 0.1
# Factor 5: Multiple interpretations
ambiguous_patterns = [
r'maybe|perhaps|or|either',
r'it|this|that|those|these',
r'stuff|things|something',
r'fix|update|change|modify'
]
pronoun_count = len(re.findall(r'\b(it|this|that)\b', user_request, re.IGNORECASE))
if pronoun_count >= 2 or any(re.search(p, user_request, re.IGNORECASE) for p in ambiguous_patterns):
factors.append('multiple_interpretations')
score += 0.2
# Factor 6: Short request
if len(user_request) < 30:
factors.append('short_request')
score += 0.15
# Factor 7: Context Conflict (New in v2)
# Check if request contradicts current phase
current_phase = context.get('current_feature_phase', 'PLAN')
phase_keywords = {
'PLAN': ['implement', 'code', 'build', 'deploy'],
'DESIGN': ['deploy', 'test', 'release'],
'DO': ['plan', 'design', 'architecture']
}
conflict_keywords = phase_keywords.get(current_phase, [])
for keyword in conflict_keywords:
if keyword in user_request.lower():
factors.append(f'context_conflict_with_{current_phase}')
score += 0.15
break
score = min(1.0, max(0.0, score))
return {"score": score, "factors": factors}
if __name__ == "__main__":
if len(sys.argv) < 2:
print(json.dumps({"error": "No request provided"}))
sys.exit(1)
request = sys.argv[1]
# Simulate context from arg 2 if present
context = {}
if len(sys.argv) > 2:
try:
context = json.loads(sys.argv[2])
except:
pass
result = calculate_ambiguity_score(request, context)
print(json.dumps(result, indent=2))
+53
View File
@@ -0,0 +1,53 @@
import os
import sys
import shutil
import glob
def archive_feature(feature_name):
archive_root = "archive"
target_dir = os.path.join(archive_root, feature_name)
print(f"Archiving feature '{feature_name}' to {target_dir}...")
if not os.path.exists(target_dir):
os.makedirs(target_dir)
# Find all files related to this feature
# Convention: {feature}.*.md or inside feature folders
patterns = [
f"docs/**/*{feature_name}*.md",
f"src/**/*{feature_name}*"
]
moved_count = 0
for pattern in patterns:
# Recursive glob
files = glob.glob(pattern, recursive=True)
for src in files:
if os.path.isdir(src): continue
# mirror logic logic is complex, for v1.0 let's just flatten or simple move
# Simple move: archive/{feature}/{original_basename}
basename = os.path.basename(src)
dst = os.path.join(target_dir, basename)
try:
shutil.move(src, dst)
print(f" Moved: {src} -> {dst}")
moved_count += 1
except Exception as e:
print(f" Error moving {src}: {e}")
if moved_count > 0:
print(f"✅ Successfully archived {moved_count} files.")
else:
print("⚠️ No files found to archive.")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python archive.py <feature_name>")
sys.exit(1)
archive_feature(sys.argv[1])
+196
View File
@@ -0,0 +1,196 @@
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()
+88
View File
@@ -0,0 +1,88 @@
import sys
import re
import json
def extract_checklist(markdown_content):
checklist = []
lines = markdown_content.split('\n')
current_section = "General"
in_table = False
headers = []
for line in lines:
stripped = line.strip()
# Detect headers as sections
header_match = re.match(r'^(#{1,6})\s+(.+)', line)
if header_match:
current_section = header_match.group(2).strip()
in_table = False
continue
# Detect existing checkboxes
msg_match = re.match(r'^\s*-\s*\[([ x])\]\s+(.+)', line)
if msg_match:
status = "completed" if msg_match.group(1) == 'x' else "pending"
checklist.append({
"section": current_section,
"item": msg_match.group(2).strip(),
"type": "checkbox",
"status": status
})
continue
# Detect table start
if '|' in line and not in_table:
# check if it looks like a header row
parts = [p.strip() for p in line.split('|') if p.strip()]
if len(parts) > 1:
headers = parts
in_table = True
continue
# Detect table separator
if in_table and set(stripped.replace('|', '').replace('-', '').replace(' ', '')) == set():
continue
# Detect table rows
if in_table and '|' in line:
parts = [p.strip() for p in line.split('|') if p.strip()]
if len(parts) == len(headers):
# Heuristic: If first column looks like a File/ID/Method, treat as requirement
item_text = f"{parts[0]}: {parts[1]}" if len(parts) >= 2 else parts[0]
checklist.append({
"section": current_section,
"item": item_text,
"type": "table_row",
"status": "pending"
})
elif not stripped:
in_table = False
return checklist
def generate_verification_plan(checklist):
output = []
output.append("# Generated Gap Analysis Checklist")
output.append("\n## Gap Analysis Matrix\n")
output.append("| Category | Requirement | Implementation Status | Notes |")
output.append("|---|---|---|---|")
for item in checklist:
output.append(f"| {item['section']} | {item['item']} | Pending | |")
return "\n".join(output)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python checklist_generator.py <input_md_file>")
sys.exit(1)
try:
with open(sys.argv[1], 'r', encoding='utf-8') as f:
content = f.read()
checklist = extract_checklist(content)
print(generate_verification_plan(checklist))
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
+43
View File
@@ -0,0 +1,43 @@
import os
import sys
def generate_graph_index(root_path=".hmcmkit"):
index_content = ["# HMCMkit Knowledge Graph Index", "", "> Automatically generated index of all knowledge assets.", ""]
# Simple tree walk
for root, dirs, files in os.walk(root_path):
# Filter hidden
dirs[:] = [d for d in dirs if not d.startswith('.')]
files = [f for f in files if not f.startswith('.')]
rel_path = os.path.relpath(root, root_path)
if rel_path == ".":
level = 0
rel_path = ""
else:
level = rel_path.count(os.sep) + 1
indent = " " * level
folder_name = os.path.basename(root)
if folder_name != ".hmcmkit" and rel_path != "":
index_content.append(f"{indent}- 📂 **{folder_name}/**")
sub_indent = " " * (level + 1)
for f in files:
if f.endswith(".md") or f.endswith(".json") or f.endswith(".py"):
link_path = os.path.join(root, f).replace("\\", "/") # POSIX style for links
# Make link relative to where _GRAPH-INDEX.md will be (in system/)
# Actually, let's just use the path from project root for clarity in Antigravity context
index_content.append(f"{sub_indent}- [{f}]({link_path})")
output_path = os.path.join(root_path, "system", "_GRAPH-INDEX.md")
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
f.write("\n".join(index_content))
print(f"✅ Generated Graph Index at: {output_path}")
if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else ".hmcmkit"
generate_graph_index(target)
+86
View File
@@ -0,0 +1,86 @@
import os
import json
def init_environment():
"""
Resets the HMCMkit environment to its initial state.
- state.json is reset to defaults.
- blueprint.md is reset to a baseline template.
"""
hmcmkit_root = ".hmcmkit"
if not os.path.exists(hmcmkit_root):
print(f"Error: {hmcmkit_root} directory not found.")
return
state_path = os.path.join(hmcmkit_root, "state.json")
blueprint_path = os.path.join(hmcmkit_root, "blueprint.md")
# 1. Reset state.json
default_state = {
"project_name": "New HMCMkit Project",
"current_feature": None,
"features": {},
"team_config": {
"enabled": False,
"max_teammates": 4
},
"last_bootup": None,
"project_level": "Starter"
}
try:
with open(state_path, 'w', encoding='utf-8') as f:
json.dump(default_state, f, indent=2, ensure_ascii=False)
print(f"✅ Reset state: {state_path}")
except Exception as e:
print(f"❌ Failed to reset state: {e}")
# 2. Reset blueprint.md
blueprint_template = """# Project Blueprint (Domain Knowledge Layer)
> **Role**: The single source of truth for "What" we are building.
> **Usage**: Read this file at the start of every session to understand the project context.
## 1. Project Overview
- **Name**: New HMCMkit Project
- **Goal**: [Define your project goal here]
- **Tech Stack**: [List your technology stack]
## 2. Architecture Diagram (Mermaid)
```mermaid
graph TD
User[User] -->|Request| Antigravity[Antigravity Agent]
subgraph Context_Layer [.hmcmkit/]
Blueprint[blueprint.md]
Governance[governance.md]
State[state.json]
end
Antigravity -->|Read Context| Context_Layer
```
## 3. Data Models
[Define your core data models and entities]
## 4. API Specifications
[List your API endpoints if applicable]
## 5. Conventions & Standards
- **Naming**: kebab-case for files, PascalCase for components.
- **Language**: English for code, Korean for user communication.
"""
try:
with open(blueprint_path, 'w', encoding='utf-8') as f:
f.write(blueprint_template)
print(f"✅ Reset blueprint: {blueprint_path}")
except Exception as e:
print(f"❌ Failed to reset blueprint: {e}")
print("\n🚀 HMCMkit environment has been initialized.")
if __name__ == "__main__":
init_environment()
+65
View File
@@ -0,0 +1,65 @@
import re
import sys
import json
SUPPORTED_LANGUAGES = ['en', 'ko', 'ja', 'zh', 'es', 'fr', 'de', 'it']
AGENT_TRIGGER_PATTERNS = {
'gap-detector': {
'en': ['verify', 'check', 'gap', 'compare', 'validate'],
'ko': ['검증', '확인', '갭', '비교', '검사', '맞아?', '이거 괜찮아?'],
},
'pdca-iterator': {
'en': ['improve', 'iterate', 'fix', 'auto-fix', 'optimize'],
'ko': ['개선', '반복', '수정', '자동 수정', '고쳐줘', '개선해줘'],
},
'cto-lead': {
'en': ['team', 'project lead', 'CTO', 'team mode', 'coordinate team'],
'ko': ['팀', '팀장', '프로젝트 리드', 'CTO', '팀 구성', '팀 모드'],
},
'bkend-expert': {
'en': ['bkend', 'BaaS', 'backend service', 'database setup', 'user auth', 'file upload', 'REST API', 'signup feature', 'login feature'],
'ko': ['bkend', '백엔드 서비스', '데이터베이스 설정', '사용자 인증', '파일 업로드', '회원가입 기능', '로그인 기능'],
},
'code-analyzer': {
'en': ['analyze', 'quality', 'security', 'code review', 'any issues?'],
'ko': ['분석', '품질', '보안', '코드 리뷰', '이상해', '뭔가 이상해'],
},
'starter-guide': {
'en': ['help', 'beginner', 'first time', 'how to', 'explain'],
'ko': ['도움', '초보자', '처음', '어떻게', '설명해', '모르겠'],
}
}
def detect_language(text):
if not text: return 'en'
if re.search(r'[\uAC00-\uD7AF]', text): return 'ko'
if re.search(r'[\u3040-\u30FF]', text): return 'ja'
return 'en'
def detect_intent(text):
lang = detect_language(text)
detected_agents = []
lower_text = text.lower()
for agent, patterns in AGENT_TRIGGER_PATTERNS.items():
# Check specific lang patterns
lang_patterns = patterns.get(lang, []) + patterns.get('en', [])
for pattern in lang_patterns:
if pattern.lower() in lower_text:
detected_agents.append(agent)
break
return {
"language": lang,
"suggested_agents": detected_agents
}
if __name__ == "__main__":
if len(sys.argv) < 2:
print(json.dumps({"error": "No text provided"}))
sys.exit(1)
result = detect_intent(sys.argv[1])
print(json.dumps(result, indent=2, ensure_ascii=False))
+96
View File
@@ -0,0 +1,96 @@
import os
import sys
import shutil
import json
import datetime
# Configuration
HMCMKIT_ROOT = ".hmcmkit"
CONFIG_PATH = os.path.join(HMCMKIT_ROOT, "config.json")
TEMPLATE_DIR = os.path.join(HMCMKIT_ROOT, "knowledge", "templates")
def load_config():
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
return json.load(f)
def get_target_path(config, phase, feature_name):
"""
Resolves the target path based on config.json 'pdca' section.
"""
pdca_config = config.get('pdca', {})
path_key = f"{phase.lower()}DocPaths" # e.g. planDocPaths
paths = pdca_config.get(path_key, [])
if not paths:
# Fallback defaults
defaults = {
"plan": f"docs/01-plan/{feature_name}.plan.md",
"design": f"docs/02-design/{feature_name}.design.md",
"gap-analysis": f"docs/03-check/{feature_name}.gap.md",
"report": f"docs/04-act/{feature_name}.report.md"
}
return defaults.get(phase.lower(), f"docs/{phase}/{feature_name}.md")
# Use the first configured path
first_path = paths[0]
return first_path.replace("{feature}", feature_name).replace("{type}", phase)
def scaffold(phase, feature_name):
config = load_config()
# 1. Identify Template
template_types = config.get('templates', {}).get('types', {})
template_file = template_types.get(phase.lower())
if not template_file:
print(f"❌ Error: No template defined for phase '{phase}'")
return False
template_path = os.path.join(TEMPLATE_DIR, template_file)
if not os.path.exists(template_path):
# Try looking for Level Variants (e.g. plan-starter.template.md)
# For now, just error
print(f"❌ Error: Template file not found at {template_path}")
return False
# 2. Identify Target Path
target_path = get_target_path(config, phase, feature_name)
# 3. Create Directory
os.makedirs(os.path.dirname(target_path), exist_ok=True)
if os.path.exists(target_path):
print(f"⚠️ Target file already exists: {target_path}")
# v1.0.1: Auto Snapshot
try:
from snapshot import create_snapshot
create_snapshot(target_path)
print(f" (Backup created before potential overwrite)")
except ImportError:
pass
return False
# 4. Read & process Template
with open(template_path, 'r', encoding='utf-8') as f:
content = f.read()
# Simple Variable Replacement
content = content.replace("{{feature}}", feature_name)
content = content.replace("{{date}}", datetime.datetime.now().strftime("%Y-%m-%d"))
# 5. Write File
with open(target_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"✅ Created {phase.upper()} document: {target_path}")
return True
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python scaffold.py <phase> <feature_name>")
print("Phases: plan, design, gap-analysis, report")
sys.exit(1)
phase = sys.argv[1]
feature = sys.argv[2]
scaffold(phase, feature)
+38
View File
@@ -0,0 +1,38 @@
import os
import sys
import shutil
import datetime
def create_snapshot(file_path):
"""
Creates a timestamped backup of the given file in .hmcmkit/backups/
"""
if not os.path.exists(file_path):
print(f"❌ Error: File not found: {file_path}")
return None
# Determine paths
hmcmkit_root = ".hmcmkit"
backup_dir = os.path.join(hmcmkit_root, "backups")
os.makedirs(backup_dir, exist_ok=True)
# Generate timestamped name
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = os.path.basename(file_path)
backup_name = f"{timestamp}_{filename}"
backup_path = os.path.join(backup_dir, backup_name)
try:
shutil.copy2(file_path, backup_path)
print(f"📦 Snapshot created: {backup_path}")
return backup_path
except Exception as e:
print(f"❌ Snapshot failed: {e}")
return None
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python snapshot.py <file_path>")
sys.exit(1)
create_snapshot(sys.argv[1])
+283
View File
@@ -0,0 +1,283 @@
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