Add Phase 10-12 implementation: multi-tenant, marketplace, observability
Major additions: - marketplace/: Agent template registry with FTS5 search, ratings, versioning - observability/: Prometheus metrics, distributed tracing, structured logging - ledger/migrations/: Database migration scripts for multi-tenant support - tests/governance/: 15 new test files for phases 6-12 (295 total tests) - bin/validate-phases: Full 12-phase validation script New features: - Multi-tenant support with tenant isolation and quota enforcement - Agent marketplace with semantic versioning and search - Observability with metrics, tracing, and log correlation - Tier-1 agent bootstrap scripts Updated components: - ledger/api.py: Extended API for tenants, marketplace, observability - ledger/schema.sql: Added tenant, project, marketplace tables - testing/framework.ts: Enhanced test framework - checkpoint/checkpoint.py: Improved checkpoint management Archived: - External integrations (Slack/GitHub/PagerDuty) moved to .archive/ - Old checkpoint files cleaned up Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
4b5b3b0a2d
commit
8c6e7831e9
@@ -594,15 +594,52 @@ class CheckpointManager:
|
||||
return name.startswith(".") or name in SKIP_DIRS or "evd-" in name
|
||||
|
||||
def parse_phase(content: str) -> str:
|
||||
content_lower = content.lower()
|
||||
if "complete" in content_lower and "in_progress" not in content_lower:
|
||||
return "complete"
|
||||
if "in_progress" in content_lower or "in progress" in content_lower:
|
||||
return "in_progress"
|
||||
if "blocked" in content_lower:
|
||||
"""Parse phase status from STATUS.md content.
|
||||
|
||||
Looks for explicit status in 'Current Phase' section first,
|
||||
then falls back to checking the header area only (first 500 chars).
|
||||
This prevents activity log entries from triggering false status.
|
||||
"""
|
||||
# First, try to find explicit status in Current Phase section
|
||||
# Pattern: **STATUS** or **STATUS:** or **PHASE X: NAME** (Status)
|
||||
phase_pattern = r'\*\*\s*(COMPLETE|BLOCKED|IN[_\s]?PROGRESS|NEEDS[_\s]?REVIEW|NOT[_\s]?STARTED)\s*\*\*'
|
||||
phase_match = re.search(phase_pattern, content[:1000], re.IGNORECASE)
|
||||
if phase_match:
|
||||
status = phase_match.group(1).lower().replace(" ", "_").replace("-", "_")
|
||||
if "complete" in status:
|
||||
return "complete"
|
||||
if "progress" in status:
|
||||
return "in_progress"
|
||||
if "block" in status:
|
||||
return "blocked"
|
||||
if "review" in status:
|
||||
return "needs_review"
|
||||
return "not_started"
|
||||
|
||||
# Check for status in parentheses after phase name: (Complete) or (Blocked)
|
||||
paren_pattern = r'\((?:status:\s*)?(Complete|Blocked|In[_\s]?Progress|Needs[_\s]?Review)\)'
|
||||
paren_match = re.search(paren_pattern, content[:1000], re.IGNORECASE)
|
||||
if paren_match:
|
||||
status = paren_match.group(1).lower()
|
||||
if "complete" in status:
|
||||
return "complete"
|
||||
if "progress" in status:
|
||||
return "in_progress"
|
||||
if "block" in status:
|
||||
return "blocked"
|
||||
if "review" in status:
|
||||
return "needs_review"
|
||||
|
||||
# Fallback: check header area only (first 500 chars) for keywords
|
||||
header = content[:500].lower()
|
||||
if "blocked" in header:
|
||||
return "blocked"
|
||||
if "needs_review" in content_lower or "needs review" in content_lower:
|
||||
if "in_progress" in header or "in progress" in header:
|
||||
return "in_progress"
|
||||
if "needs_review" in header or "needs review" in header:
|
||||
return "needs_review"
|
||||
if "complete" in header:
|
||||
return "complete"
|
||||
return "not_started"
|
||||
|
||||
def parse_timestamp(content: str) -> Optional[str]:
|
||||
@@ -1472,6 +1509,32 @@ Examples:
|
||||
print(f" ⚠️ Needs Review: {summary.get('needs_review', 0):3d}")
|
||||
print(f" ⬜ Not Started: {summary.get('not_started', 0):3d}")
|
||||
|
||||
# Check project state for blockers
|
||||
project_state_path = Path("/opt/agent-governance/project_state.yaml")
|
||||
if project_state_path.exists():
|
||||
try:
|
||||
import yaml
|
||||
with open(project_state_path) as f:
|
||||
project_state = yaml.safe_load(f)
|
||||
|
||||
overall = project_state.get("project", {}).get("overall_status", "unknown")
|
||||
blockers = project_state.get("blockers", [])
|
||||
pending_blockers = [b for b in blockers if b.get("status") == "pending"]
|
||||
|
||||
if overall != "complete" or pending_blockers:
|
||||
print(f"\n[PROJECT STATE WARNING]")
|
||||
print(f" Overall Status: {overall.upper()}")
|
||||
if pending_blockers:
|
||||
print(f" Pending Blockers: {len(pending_blockers)}")
|
||||
for b in pending_blockers:
|
||||
print(f" ❗ {b.get('id', 'unknown')}: {b.get('description', 'No description')}")
|
||||
if b.get("resolution_doc"):
|
||||
print(f" See: {b.get('resolution_doc')}")
|
||||
except ImportError:
|
||||
pass # yaml not available, skip
|
||||
except Exception as e:
|
||||
print(f"\n[PROJECT STATE ERROR] Could not load: {e}")
|
||||
|
||||
# Active directories (non-complete)
|
||||
active_dirs = [d for d in checkpoint.directory_statuses
|
||||
if d.phase in ("in_progress", "blocked", "needs_review")]
|
||||
|
||||
Reference in New Issue
Block a user