Pipeline reliability (8 fixes, reviewed by Ganymede+Rhea+Leo+Rio):
1. Merge API recovery — pre-flight approval check, transient/permanent distinction, jitter
2. Ghost PR detection — ls-remote branch check in reconciliation, network guard
3. Source status contract — directory IS status, no code change needed
4. Batch-state markers eliminated — two-gate skip (archive-check + batched branch-check)
5. Branch SHA tracking — batched ls-remote, auto-reset verdicts, dismiss stale reviews
6. Mirror pre-flight permissions — chown check in sync-mirror.sh
7. Telegram archive commit-after-write — git add/commit/push with rebase --abort fallback
8. Post-merge source archiving — queue/ → archive/{domain}/ after merge
Pipeline fixes:
- merge_cycled flag — eval attempts preserved during merge-failure cycling (Ganymede+Rhea)
- merge_failures diagnostic counter
- Startup recovery preserves eval_attempts (was incorrectly resetting to 0)
- No-diff PRs auto-closed by eval (root cause of 17 zombie PRs)
- GC threshold aligned with substantive fixer budget (was 2, now 4)
- Conflict retry with 3-attempt budget + permanent conflict handler
- Local ff-merge fallback for Forgejo 405 errors
Telegram bot:
- KB retrieval: 3-layer (entity resolution → claim search → agent context)
- Reply-to-bot handler (context.bot.id check)
- Tag regex: @teleo|@futairdbot
- Prompt rewrite for natural analyst voice
- Market data API integration (Ben's token price endpoint)
- Conversation windows (5-message unanswered counter, per-user-per-chat)
- Conversation history in prompt (last 5 exchanges)
- Worktree file lock for archive writes
Infrastructure:
- worktree_lock.py — file-based lock (flock) for main worktree coordination
- backfill-sources.py — source DB registration for Argus funnel
- batch-extract-50.sh v3 — two-gate skip, batched ls-remote, network guard
- sync-mirror.sh — auto-PR creation for mirrored GitHub branches, permission pre-flight
- Argus dashboard — conflicts + reviewing in backlog, queue count in funnel
- Enrichment-inside-frontmatter bug fix (regex anchor, not --- split)
Pentagon-Agent: Epimetheus <3D35839A-7722-4740-B93D-51157F7D5E70>
100 lines
3.5 KiB
Python
100 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Entity schema migration — separate decisions from entities.
|
|
|
|
Step 1: Move decision_market entities to decisions/{domain}/
|
|
Step 2: Update frontmatter (type: entity → type: decision)
|
|
Step 3: Update pipeline config (TYPE_SCHEMAS, entity paths)
|
|
|
|
Run from the repo root:
|
|
cd /opt/teleo-eval/workspaces/main # or extract/
|
|
python3 /opt/teleo-eval/pipeline/migrate-entity-schema.py [--dry-run]
|
|
|
|
Epimetheus. Reviewed by Leo (architecture), Rio (taxonomy), Ganymede (migration path).
|
|
"""
|
|
|
|
import argparse
|
|
import glob
|
|
import os
|
|
import re
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
|
|
def find_decision_markets(repo_root: str) -> list[dict]:
|
|
"""Find all decision_market entity files."""
|
|
decisions = []
|
|
for filepath in glob.glob(os.path.join(repo_root, "entities", "*", "*.md")):
|
|
try:
|
|
content = open(filepath).read()
|
|
except Exception:
|
|
continue
|
|
|
|
if "entity_type: decision_market" in content:
|
|
domain = Path(filepath).parent.name
|
|
filename = Path(filepath).name
|
|
decisions.append({
|
|
"source": filepath,
|
|
"domain": domain,
|
|
"filename": filename,
|
|
"dest": os.path.join(repo_root, "decisions", domain, filename),
|
|
})
|
|
return decisions
|
|
|
|
|
|
def update_frontmatter_type(content: str) -> str:
|
|
"""Change type: entity to type: decision for decision files."""
|
|
content = re.sub(r"^type:\s*entity\s*$", "type: decision", content, count=1, flags=re.MULTILINE)
|
|
return content
|
|
|
|
|
|
def migrate(repo_root: str, dry_run: bool = False):
|
|
"""Run the migration."""
|
|
decisions = find_decision_markets(repo_root)
|
|
print(f"Found {len(decisions)} decision_market files to migrate")
|
|
|
|
# Group by domain
|
|
by_domain: dict[str, list] = {}
|
|
for d in decisions:
|
|
by_domain.setdefault(d["domain"], []).append(d)
|
|
|
|
for domain, files in by_domain.items():
|
|
print(f"\n {domain}: {len(files)} decisions")
|
|
|
|
dest_dir = os.path.join(repo_root, "decisions", domain)
|
|
if not dry_run:
|
|
os.makedirs(dest_dir, exist_ok=True)
|
|
|
|
for f in files:
|
|
print(f" {f['filename']}")
|
|
if not dry_run:
|
|
# Read, update frontmatter, write to new location
|
|
content = open(f["source"]).read()
|
|
content = update_frontmatter_type(content)
|
|
with open(f["dest"], "w") as out:
|
|
out.write(content)
|
|
# Remove original
|
|
os.remove(f["source"])
|
|
|
|
# Summary
|
|
remaining_entities = glob.glob(os.path.join(repo_root, "entities", "*", "*.md"))
|
|
remaining_by_domain: dict[str, int] = {}
|
|
for f in remaining_entities:
|
|
d = Path(f).parent.name
|
|
remaining_by_domain[d] = remaining_by_domain.get(d, 0) + 1
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f" MIGRATION {'(DRY RUN) ' if dry_run else ''}COMPLETE")
|
|
print(f" Decisions moved: {len(decisions)}")
|
|
print(f" Entities remaining: {len(remaining_entities)}")
|
|
for domain, count in sorted(remaining_by_domain.items()):
|
|
print(f" {domain}: {count}")
|
|
print(f" Decision directories created: {list(by_domain.keys())}")
|
|
print(f"{'='*60}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Migrate decision_market entities to decisions/")
|
|
parser.add_argument("--repo-root", default=".", help="Repository root")
|
|
parser.add_argument("--dry-run", action="store_true", help="Show what would change without changing")
|
|
args = parser.parse_args()
|
|
migrate(args.repo_root, args.dry_run)
|