Tick #1487 - Timmy rests. The LED pulses steadily. | Bezalel returns to the Forge. Picks up the hammer. | Allegro crosses to the Garden. Listens to the wind. (+5 more)
This commit is contained in:
197
cross_audit.py
Normal file
197
cross_audit.py
Normal file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Full cross-audit of Timmy Foundation team and system.
|
||||
Scans all repos, all agents, all cron jobs, all VPS health, all local state.
|
||||
Produces actionable issues with clear acceptance criteria."""
|
||||
import subprocess, json, os
|
||||
|
||||
GITEA_TOK = open(os.path.expanduser('~/.hermes/gitea_token_vps')).read().strip()
|
||||
FORGE = 'https://forge.alexanderwhitestone.com/api/v1'
|
||||
REPOS = ['timmy-config', 'timmy-home', 'the-nexus', 'hermes-agent', 'wolf', 'the-door', 'turboquant', 'timmy-academy']
|
||||
|
||||
def curl(url):
|
||||
r = subprocess.run(
|
||||
['curl', '-s', url, '-H', f'Authorization: token {GITEA_TOK}'],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
return json.loads(r.stdout)
|
||||
|
||||
def api(method, path, data=None):
|
||||
r = subprocess.run(
|
||||
['curl', '-s', '-X', method, f'{FORGE}/{path}',
|
||||
'-H', f'Authorization: token {GITEA_TOK}',
|
||||
'-H', 'Content-Type: application/json']
|
||||
+ (['-d', json.dumps(data)] if data else []),
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
return json.loads(r.stdout)
|
||||
|
||||
# ============================================================
|
||||
# 1. INVENTORY: Every repo, every issue, every agent
|
||||
# ============================================================
|
||||
|
||||
print("=" * 60)
|
||||
print("CROSS AUDIT — Timmy Foundation")
|
||||
print("=" * 60)
|
||||
|
||||
# All open issues
|
||||
all_issues = []
|
||||
repos_state = {}
|
||||
for repo in REPOS:
|
||||
issues = curl(f'{FORGE}/repos/Timmy_Foundation/{repo}/issues?state=open&limit=50')
|
||||
if not isinstance(issues, list):
|
||||
issues = []
|
||||
|
||||
pr_count = 0
|
||||
issue_count = 0
|
||||
unassigned = 0
|
||||
timmy_assigned = 0
|
||||
|
||||
for iss in issues:
|
||||
if 'pull_request' in iss:
|
||||
pr_count += 1
|
||||
continue
|
||||
issue_count += 1
|
||||
a = iss.get('assignee', {})
|
||||
login = a.get('login', 'unassigned') if a else 'unassigned'
|
||||
if login == 'unassigned':
|
||||
unassigned += 1
|
||||
elif login == 'Timmy':
|
||||
timmy_assigned += 1
|
||||
labels = [l['name'] for l in iss.get('labels', [])]
|
||||
all_issues.append({
|
||||
'repo': repo,
|
||||
'num': iss['number'],
|
||||
'title': iss['title'][:80],
|
||||
'assignee': login,
|
||||
'labels': labels,
|
||||
'created': iss.get('created_at', '')[:10],
|
||||
})
|
||||
repos_state[repo] = {
|
||||
'open_issues': issue_count,
|
||||
'open_prs': pr_count,
|
||||
'unassigned': unassigned,
|
||||
'timmy_assigned': timmy_assigned,
|
||||
}
|
||||
|
||||
print(f"\n=== GITEA REPO AUDIT ===")
|
||||
print(f"{'repo':<20} {'issues':>6} {'prs':>4} {'unassign':>8} {'timmy':>5}")
|
||||
for repo, state in repos_state.items():
|
||||
print(f"{repo:<20} {state['open_issues']:>6} {state['open_prs']:>4} {state['unassigned']:>8} {state['timmy_assigned']:>5}")
|
||||
|
||||
total_issues = sum(s['open_issues'] for s in repos_state.values())
|
||||
total_prs = sum(s['open_prs'] for s in repos_state.values())
|
||||
total_unassigned = sum(s['unassigned'] for s in repos_state.values())
|
||||
total_timmy = sum(s['timmy_assigned'] for s in repos_state.values())
|
||||
print(f"{'TOTAL':<20} {total_issues:>6} {total_prs:>4} {total_unassigned:>8} {total_timmy:>5}")
|
||||
|
||||
# Issues by assignee
|
||||
by_assignee = {}
|
||||
for iss in all_issues:
|
||||
by_assignee.setdefault(iss['assignee'], []).append(iss)
|
||||
|
||||
print(f"\n=== ISSUES BY ASSIGNEE ===")
|
||||
for assignee in sorted(by_assignee.keys()):
|
||||
issues = by_assignee[assignee]
|
||||
print(f" {assignee}: {len(issues)}")
|
||||
for iss in issues[:5]:
|
||||
print(f" {iss['repo']}/#{iss['num']}: {iss['title']}")
|
||||
|
||||
# Issues older than 30 days
|
||||
old_issues = [i for i in all_issues if i['created'] < '2026-03-07']
|
||||
print(f"\n=== STALE ISSUES (>30 days old): {len(old_issues)} ===")
|
||||
for iss in old_issues[:10]:
|
||||
print(f" {iss['repo']}/#{iss['num']} (created {iss['created']}) @{iss['assignee']}: {iss['title']}")
|
||||
|
||||
# ============================================================
|
||||
# 2. CRON JOB AUDIT
|
||||
# ============================================================
|
||||
print(f"\n=== CRON JOBS ===")
|
||||
import subprocess
|
||||
r = subprocess.run(
|
||||
['hermes', 'cron', 'list'],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
cron_output = r.stdout + r.stderr
|
||||
print(cron_output[:2000])
|
||||
|
||||
# ============================================================
|
||||
# 3. VPS HEALTH
|
||||
# ============================================================
|
||||
print(f"\n=== VPS HEALTH ===")
|
||||
for vps_name, vps_ip in [('Hermes VPS', '143.198.27.163'), ('TestBed VPS', '67.205.155.108')]:
|
||||
r = subprocess.run(
|
||||
['ssh', '-o', 'ConnectTimeout=5', 'root@' + vps_ip,
|
||||
'echo "uptime: $(uptime)"; echo "disk:"; df -h / | tail -1; echo "memory:"; free -h | head -2; echo "services:"; systemctl list-units --type=service --state=running --no-pager 2>/dev/null | grep -c running; echo "hermes:"; systemctl list-units --state=running --no-pager 2>/dev/null | grep -c hermes'],
|
||||
capture_output=True, text=True, timeout=15
|
||||
)
|
||||
status = r.stdout.strip() if r.returncode == 0 else "UNREACHABLE"
|
||||
print(f"\n {vps_name} ({vps_ip}):")
|
||||
if status == "UNREACHABLE":
|
||||
print(f" SSH FAILED - VPS may be down")
|
||||
else:
|
||||
for line in status.split('\n'):
|
||||
print(f" {line.strip()}")
|
||||
|
||||
# ============================================================
|
||||
# 4. LOCAL MAC HEALTH
|
||||
# ============================================================
|
||||
print(f"\n=== MAC HEALTH ===")
|
||||
r = subprocess.run(['ps', 'aux'], capture_output=True, text=True)
|
||||
hermes_procs = [l for l in r.stdout.split('\n') if 'hermes' in l or 'evennia' in l or 'twistd' in l]
|
||||
print(f" Hermes/Evennia processes: {len(hermes_procs)}")
|
||||
for p in hermes_procs[:5]:
|
||||
print(f" {p[:100]}...")
|
||||
|
||||
r = subprocess.run(['ollama', 'list'], capture_output=True, text=True, timeout=10)
|
||||
print(f"\n Ollama models:")
|
||||
print(r.stdout.strip()[:500])
|
||||
|
||||
import pathlib
|
||||
worktrees = pathlib.Path(os.path.expanduser('~/worktrees')).glob('*')
|
||||
wt_count = len(list(worktrees))
|
||||
print(f"\n Worktrees: {wt_count}")
|
||||
|
||||
# ============================================================
|
||||
# 5. IDENTIFIED GAPS
|
||||
# ============================================================
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"IDENTIFIED GAPS & GAPS TO FILE")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
# The cross-audit results will be used to file issues
|
||||
gaps = []
|
||||
|
||||
# Always-present gaps
|
||||
if total_unassigned > 0:
|
||||
gaps.append(f"{total_unassigned} unassigned issues exist — need assignment or closing")
|
||||
if total_timmy > 10:
|
||||
gaps.append(f"Timmy has {total_timmy} assigned issues — likely overloaded")
|
||||
if len(old_issues) > 0:
|
||||
gaps.append(f"{len(old_issues)} issues older than 30 days — stale, needs triage")
|
||||
|
||||
# Known gaps from previous RCA (Tower Game)
|
||||
gaps.append("Tower Game: No contextual dialogue (NPCs repeat lines)")
|
||||
gaps.append("Tower Game: No meaningful conflict/trust system")
|
||||
gaps.append("Tower Game: World events exist but have no gameplay impact")
|
||||
gaps.append("Tower Game: Energy system doesn't constrain")
|
||||
gaps.append("Tower Game: No narrative arc (tick 200 = tick 20)")
|
||||
gaps.append("Tower Game: No item system")
|
||||
gaps.append("Tower Game: No NPC-NPC relationships")
|
||||
gaps.append("Tower Game: Chronicle is tick data, not narrative")
|
||||
|
||||
# System gaps (discovered during this audit)
|
||||
gaps.append("No comms audit: Telegram deprecated? Nostr operational?")
|
||||
gaps.append("Sonnet workforce: loop created but not tested end-to-end")
|
||||
gaps.append("No cross-agent quality audit: which agents produce mergeable PRs?")
|
||||
gaps.append("No burn-down velocity tracking: how many issues closed per day?")
|
||||
gaps.append("No fleet cost tracking: how much does each agent cost per day?")
|
||||
|
||||
print(f"\nTotal gaps identified: {len(gaps)}")
|
||||
for i, gap in enumerate(gaps, 1):
|
||||
print(f" {i}. {gap}")
|
||||
|
||||
# Save for issue filing
|
||||
with open(f'/tmp/cross_audit_gaps.json', 'w') as f:
|
||||
json.dump(gaps, f, indent=2)
|
||||
|
||||
print(f"\nAudit complete. Gaps saved to /tmp/cross_audit_gaps.json")
|
||||
Reference in New Issue
Block a user