1
0

refactor: Phase 2b — consolidate 28 modules into 14 packages

Complete the module consolidation planned in REFACTORING_PLAN.md:

Modules merged:
- work_orders/ + task_queue/ → swarm/ (subpackages)
- self_modify/ + self_tdd/ + upgrades/ → self_coding/ (subpackages)
- tools/ → creative/tools/
- chat_bridge/ + telegram_bot/ + shortcuts/ + voice/ → integrations/ (new)
- ws_manager/ + notifications/ + events/ + router/ → infrastructure/ (new)
- agents/ + agent_core/ + memory/ → timmy/ (subpackages)

Updated across codebase:
- 66 source files: import statements rewritten
- 13 test files: import + patch() target strings rewritten
- pyproject.toml: wheel includes (28→14), entry points updated
- CLAUDE.md: singleton paths, module map, entry points table
- AGENTS.md: file convention updates
- REFACTORING_PLAN.md: execution status, success metrics

Extras:
- Module-level CLAUDE.md added to 6 key packages (Phase 6.2)
- Zero test regressions: 1462 tests passing

https://claude.ai/code/session_01JNjWfHqusjT3aiN4vvYgUk
This commit is contained in:
Claude
2026-02-26 22:07:41 +00:00
parent 24c3d33c3b
commit 9f4c809f70
138 changed files with 913 additions and 407 deletions

View File

@@ -0,0 +1 @@
"""Upgrades — System upgrade queue and execution pipeline."""

View File

@@ -0,0 +1,331 @@
"""Database models for Self-Upgrade Approval Queue."""
import json
import sqlite3
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Optional
DB_PATH = Path("data/swarm.db")
class UpgradeStatus(str, Enum):
"""Status of an upgrade proposal."""
PROPOSED = "proposed"
APPROVED = "approved"
REJECTED = "rejected"
APPLIED = "applied"
FAILED = "failed"
EXPIRED = "expired"
@dataclass
class Upgrade:
"""A self-modification upgrade proposal."""
id: str = field(default_factory=lambda: str(uuid.uuid4()))
status: UpgradeStatus = UpgradeStatus.PROPOSED
# Timestamps
proposed_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
approved_at: Optional[str] = None
applied_at: Optional[str] = None
rejected_at: Optional[str] = None
# Proposal details
branch_name: str = ""
description: str = ""
files_changed: list[str] = field(default_factory=list)
diff_preview: str = ""
# Test results
test_passed: bool = False
test_output: str = ""
# Execution results
error_message: Optional[str] = None
approved_by: Optional[str] = None
def _get_conn() -> sqlite3.Connection:
"""Get database connection with schema initialized."""
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(DB_PATH))
conn.row_factory = sqlite3.Row
conn.execute(
"""
CREATE TABLE IF NOT EXISTS upgrades (
id TEXT PRIMARY KEY,
status TEXT NOT NULL DEFAULT 'proposed',
proposed_at TEXT NOT NULL,
approved_at TEXT,
applied_at TEXT,
rejected_at TEXT,
branch_name TEXT NOT NULL,
description TEXT NOT NULL,
files_changed TEXT, -- JSON array
diff_preview TEXT,
test_passed INTEGER DEFAULT 0,
test_output TEXT,
error_message TEXT,
approved_by TEXT
)
"""
)
# Indexes
conn.execute("CREATE INDEX IF NOT EXISTS idx_upgrades_status ON upgrades(status)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_upgrades_proposed ON upgrades(proposed_at)")
conn.commit()
return conn
def create_upgrade(
branch_name: str,
description: str,
files_changed: list[str],
diff_preview: str,
test_passed: bool = False,
test_output: str = "",
) -> Upgrade:
"""Create a new upgrade proposal.
Args:
branch_name: Git branch name for the upgrade
description: Human-readable description
files_changed: List of files that would be modified
diff_preview: Short diff preview for review
test_passed: Whether tests passed on the branch
test_output: Test output text
Returns:
The created Upgrade
"""
upgrade = Upgrade(
branch_name=branch_name,
description=description,
files_changed=files_changed,
diff_preview=diff_preview,
test_passed=test_passed,
test_output=test_output,
)
conn = _get_conn()
conn.execute(
"""
INSERT INTO upgrades (id, status, proposed_at, branch_name, description,
files_changed, diff_preview, test_passed, test_output)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
upgrade.id,
upgrade.status.value,
upgrade.proposed_at,
upgrade.branch_name,
upgrade.description,
json.dumps(files_changed),
upgrade.diff_preview,
int(test_passed),
test_output,
),
)
conn.commit()
conn.close()
return upgrade
def get_upgrade(upgrade_id: str) -> Optional[Upgrade]:
"""Get upgrade by ID."""
conn = _get_conn()
row = conn.execute(
"SELECT * FROM upgrades WHERE id = ?", (upgrade_id,)
).fetchone()
conn.close()
if not row:
return None
return Upgrade(
id=row["id"],
status=UpgradeStatus(row["status"]),
proposed_at=row["proposed_at"],
approved_at=row["approved_at"],
applied_at=row["applied_at"],
rejected_at=row["rejected_at"],
branch_name=row["branch_name"],
description=row["description"],
files_changed=json.loads(row["files_changed"]) if row["files_changed"] else [],
diff_preview=row["diff_preview"] or "",
test_passed=bool(row["test_passed"]),
test_output=row["test_output"] or "",
error_message=row["error_message"],
approved_by=row["approved_by"],
)
def list_upgrades(
status: Optional[UpgradeStatus] = None,
limit: int = 100,
) -> list[Upgrade]:
"""List upgrades, optionally filtered by status."""
conn = _get_conn()
if status:
rows = conn.execute(
"SELECT * FROM upgrades WHERE status = ? ORDER BY proposed_at DESC LIMIT ?",
(status.value, limit),
).fetchall()
else:
rows = conn.execute(
"SELECT * FROM upgrades ORDER BY proposed_at DESC LIMIT ?",
(limit,),
).fetchall()
conn.close()
return [
Upgrade(
id=r["id"],
status=UpgradeStatus(r["status"]),
proposed_at=r["proposed_at"],
approved_at=r["approved_at"],
applied_at=r["applied_at"],
rejected_at=r["rejected_at"],
branch_name=r["branch_name"],
description=r["description"],
files_changed=json.loads(r["files_changed"]) if r["files_changed"] else [],
diff_preview=r["diff_preview"] or "",
test_passed=bool(r["test_passed"]),
test_output=r["test_output"] or "",
error_message=r["error_message"],
approved_by=r["approved_by"],
)
for r in rows
]
def approve_upgrade(upgrade_id: str, approved_by: str = "dashboard") -> Optional[Upgrade]:
"""Approve an upgrade proposal."""
now = datetime.now(timezone.utc).isoformat()
conn = _get_conn()
cursor = conn.execute(
"""
UPDATE upgrades
SET status = ?, approved_at = ?, approved_by = ?
WHERE id = ? AND status = ?
""",
(UpgradeStatus.APPROVED.value, now, approved_by, upgrade_id, UpgradeStatus.PROPOSED.value),
)
conn.commit()
updated = cursor.rowcount > 0
conn.close()
if not updated:
return None
return get_upgrade(upgrade_id)
def reject_upgrade(upgrade_id: str) -> Optional[Upgrade]:
"""Reject an upgrade proposal."""
now = datetime.now(timezone.utc).isoformat()
conn = _get_conn()
cursor = conn.execute(
"""
UPDATE upgrades
SET status = ?, rejected_at = ?
WHERE id = ? AND status = ?
""",
(UpgradeStatus.REJECTED.value, now, upgrade_id, UpgradeStatus.PROPOSED.value),
)
conn.commit()
updated = cursor.rowcount > 0
conn.close()
if not updated:
return None
return get_upgrade(upgrade_id)
def mark_applied(upgrade_id: str) -> Optional[Upgrade]:
"""Mark upgrade as successfully applied."""
now = datetime.now(timezone.utc).isoformat()
conn = _get_conn()
cursor = conn.execute(
"""
UPDATE upgrades
SET status = ?, applied_at = ?
WHERE id = ? AND status = ?
""",
(UpgradeStatus.APPLIED.value, now, upgrade_id, UpgradeStatus.APPROVED.value),
)
conn.commit()
updated = cursor.rowcount > 0
conn.close()
if not updated:
return None
return get_upgrade(upgrade_id)
def mark_failed(upgrade_id: str, error_message: str) -> Optional[Upgrade]:
"""Mark upgrade as failed."""
conn = _get_conn()
cursor = conn.execute(
"""
UPDATE upgrades
SET status = ?, error_message = ?
WHERE id = ? AND status = ?
""",
(UpgradeStatus.FAILED.value, error_message, upgrade_id, UpgradeStatus.APPROVED.value),
)
conn.commit()
updated = cursor.rowcount > 0
conn.close()
if not updated:
return None
return get_upgrade(upgrade_id)
def get_pending_count() -> int:
"""Get count of pending (proposed) upgrades."""
conn = _get_conn()
row = conn.execute(
"SELECT COUNT(*) as count FROM upgrades WHERE status = ?",
(UpgradeStatus.PROPOSED.value,),
).fetchone()
conn.close()
return row["count"]
def prune_old_upgrades(older_than_days: int = 30) -> int:
"""Delete old completed upgrades."""
from datetime import timedelta
cutoff = (datetime.now(timezone.utc) - timedelta(days=older_than_days)).isoformat()
conn = _get_conn()
cursor = conn.execute(
"""
DELETE FROM upgrades
WHERE proposed_at < ? AND status IN ('applied', 'rejected', 'failed')
""",
(cutoff,),
)
deleted = cursor.rowcount
conn.commit()
conn.close()
return deleted

View File

@@ -0,0 +1,285 @@
"""Upgrade Queue management - bridges self-modify loop with approval workflow."""
import logging
import subprocess
from pathlib import Path
from typing import Optional
from self_coding.upgrades.models import (
Upgrade,
UpgradeStatus,
create_upgrade,
get_upgrade,
approve_upgrade,
reject_upgrade,
mark_applied,
mark_failed,
)
logger = logging.getLogger(__name__)
PROJECT_ROOT = Path(__file__).parent.parent.parent
class UpgradeQueue:
"""Manages the upgrade approval and application workflow."""
@staticmethod
def propose(
branch_name: str,
description: str,
files_changed: list[str],
diff_preview: str,
test_passed: bool = False,
test_output: str = "",
) -> Upgrade:
"""Propose a new upgrade for approval.
This is called by the self-modify loop when it generates changes.
The upgrade is created in 'proposed' state and waits for human approval.
Args:
branch_name: Git branch with the changes
description: What the upgrade does
files_changed: List of modified files
diff_preview: Short diff for review
test_passed: Whether tests passed
test_output: Test output
Returns:
The created Upgrade proposal
"""
upgrade = create_upgrade(
branch_name=branch_name,
description=description,
files_changed=files_changed,
diff_preview=diff_preview,
test_passed=test_passed,
test_output=test_output,
)
logger.info(
"Upgrade proposed: %s (%s) - %d files",
upgrade.id[:8],
branch_name,
len(files_changed),
)
# Log to event log
try:
from swarm.event_log import log_event, EventType
log_event(
EventType.SYSTEM_INFO,
source="upgrade_queue",
data={
"upgrade_id": upgrade.id,
"branch": branch_name,
"description": description,
"test_passed": test_passed,
},
)
except Exception:
pass
return upgrade
@staticmethod
def approve(upgrade_id: str, approved_by: str = "dashboard") -> Optional[Upgrade]:
"""Approve an upgrade proposal.
Called from dashboard when user clicks "Approve".
Does NOT apply the upgrade - that happens separately.
Args:
upgrade_id: The upgrade to approve
approved_by: Who approved it (for audit)
Returns:
Updated Upgrade or None if not found/not in proposed state
"""
upgrade = approve_upgrade(upgrade_id, approved_by)
if upgrade:
logger.info("Upgrade approved: %s by %s", upgrade_id[:8], approved_by)
return upgrade
@staticmethod
def reject(upgrade_id: str) -> Optional[Upgrade]:
"""Reject an upgrade proposal.
Called from dashboard when user clicks "Reject".
Cleans up the branch.
Args:
upgrade_id: The upgrade to reject
Returns:
Updated Upgrade or None
"""
upgrade = reject_upgrade(upgrade_id)
if upgrade:
logger.info("Upgrade rejected: %s", upgrade_id[:8])
# Clean up branch
try:
subprocess.run(
["git", "branch", "-D", upgrade.branch_name],
cwd=PROJECT_ROOT,
capture_output=True,
check=False,
)
except Exception as exc:
logger.warning("Failed to delete branch %s: %s", upgrade.branch_name, exc)
return upgrade
@staticmethod
def apply(upgrade_id: str) -> tuple[bool, str]:
"""Apply an approved upgrade.
This is the critical operation that actually modifies the codebase:
1. Checks out the branch
2. Runs tests
3. If tests pass: merges to main
4. Updates upgrade status
Args:
upgrade_id: The approved upgrade to apply
Returns:
(success, message) tuple
"""
upgrade = get_upgrade(upgrade_id)
if not upgrade:
return False, "Upgrade not found"
if upgrade.status != UpgradeStatus.APPROVED:
return False, f"Upgrade not approved (status: {upgrade.status.value})"
logger.info("Applying upgrade: %s (%s)", upgrade_id[:8], upgrade.branch_name)
try:
# 1. Checkout branch
result = subprocess.run(
["git", "checkout", upgrade.branch_name],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
)
if result.returncode != 0:
mark_failed(upgrade_id, f"Checkout failed: {result.stderr}")
return False, f"Failed to checkout branch: {result.stderr}"
# 2. Run tests
result = subprocess.run(
["python", "-m", "pytest", "tests/", "-x", "-q"],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
timeout=120,
)
if result.returncode != 0:
mark_failed(upgrade_id, f"Tests failed: {result.stdout}\n{result.stderr}")
# Switch back to main
subprocess.run(["git", "checkout", "main"], cwd=PROJECT_ROOT, check=False)
return False, "Tests failed"
# 3. Merge to main
result = subprocess.run(
["git", "checkout", "main"],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
)
if result.returncode != 0:
mark_failed(upgrade_id, f"Failed to checkout main: {result.stderr}")
return False, "Failed to checkout main"
result = subprocess.run(
["git", "merge", "--no-ff", upgrade.branch_name, "-m", f"Apply upgrade: {upgrade.description}"],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
)
if result.returncode != 0:
mark_failed(upgrade_id, f"Merge failed: {result.stderr}")
return False, "Merge failed"
# 4. Mark as applied
mark_applied(upgrade_id)
# 5. Clean up branch
subprocess.run(
["git", "branch", "-d", upgrade.branch_name],
cwd=PROJECT_ROOT,
capture_output=True,
check=False,
)
logger.info("Upgrade applied successfully: %s", upgrade_id[:8])
return True, "Upgrade applied successfully"
except subprocess.TimeoutExpired:
mark_failed(upgrade_id, "Tests timed out")
subprocess.run(["git", "checkout", "main"], cwd=PROJECT_ROOT, check=False)
return False, "Tests timed out"
except Exception as exc:
error_msg = str(exc)
mark_failed(upgrade_id, error_msg)
subprocess.run(["git", "checkout", "main"], cwd=PROJECT_ROOT, check=False)
return False, f"Error: {error_msg}"
@staticmethod
def get_full_diff(upgrade_id: str) -> str:
"""Get full git diff for an upgrade.
Args:
upgrade_id: The upgrade to get diff for
Returns:
Git diff output
"""
upgrade = get_upgrade(upgrade_id)
if not upgrade:
return "Upgrade not found"
try:
result = subprocess.run(
["git", "diff", "main..." + upgrade.branch_name],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
)
return result.stdout if result.returncode == 0 else result.stderr
except Exception as exc:
return f"Error getting diff: {exc}"
# Convenience functions for self-modify loop
def propose_upgrade_from_loop(
branch_name: str,
description: str,
files_changed: list[str],
diff: str,
test_output: str = "",
) -> Upgrade:
"""Called by self-modify loop to propose an upgrade.
Tests are expected to have been run by the loop before calling this.
"""
# Check if tests passed from output
test_passed = "passed" in test_output.lower() or " PASSED " in test_output
return UpgradeQueue.propose(
branch_name=branch_name,
description=description,
files_changed=files_changed,
diff_preview=diff[:2000], # First 2000 chars
test_passed=test_passed,
test_output=test_output,
)