Some checks failed
CI / build (push) Failing after 3s
PHASE 2.3 (AUDIT-2026-Q3.md):
Task 1 — Wire-in Wave 3 (1 router mounted, 2 deferred):
- app.routers.unified_scanner_router mounted at /api/v2/scanner/* (2 routes:
POST /api/v2/scanner/token/scan, POST /api/v2/scanner/wallet/scan).
Refactored prefix from /api/v2 -> /api/v2/scanner to avoid future conflicts
with the v1 /api/v1/scanner/ stub.
- app.routers.unified_wallet_scanner DEFERRED (no router APIRouter attribute;
library module consumed by unified_scanner_router via get_wallet_scanner()).
- app.routers.admin_extensions DEFERRED (DORMANT per audit; 25 routes at
/api/v1/admin/* would shadow /api/v1/admin/alerts_webhook).
Task 2 — Archive 136 dead-code files to app/_archive/legacy_2026_07/:
- 73 routers in app/routers/ (reach graph showed zero reach into mount.py).
- 63 flat app/*.py (domain modules never imported by live code).
- 1 file RESTORED post-archive: app/routers/x402_bridge_health.py (caught by
tests/unit/test_bridge_health.py which directly imports it; reach graph
considered tests/ only as transitive reach — to be patched in next cycle).
Forced-LIVE (NOT archived per user directive):
- app/ai_pipeline_v3.py (3 importers in audit window, importers themselves DEAD)
- app/splade_bm25.py (LIVE via app.rag_service)
- app/wallet_manager_v2.py (LIVE via x402_enforcement, x402_tools, sweep_all, sweep_now)
- app/crypto_embeddings.py (NOT in audit ARCHIVE list; heavy import graph)
Verification (forward-import closure from mount.py + main.py + factory.py + lifespan.py):
- imports = 348 app.* modules
- reached = 194 files reachable from roots
- archive set = audit_dead (186) - reached - forced_live (4) - test_live (1) = 136
- Net delta: 136 files moved, 44,932 LOC reduction, 293->295 active routes (+2 from Wave 3)
pyproject.toml updates:
- setuptools.packages.find: added exclude for app._archive*
- ruff.extend-exclude: added "app/_archive/"
- mypy.exclude: added "app/_archive/"
Smoke test: pytest tests/ — 817 passed, 3 pre-existing failures unchanged
(0 new failures; 0 routes lost; all 4 forced-LIVE files still importable).
Restoration: git mv app/_archive/legacy_2026_07/<name>.py <original-path>
and add the import to app/mount.py ROUTER_MODULES.
Refs: AUDIT-2026-Q3.md /home/dev/pry/rmi-final-deadcode-2026-07-06.md
286 lines
9.1 KiB
Python
286 lines
9.1 KiB
Python
"""
|
|
RMI Tool Changelog & Version System
|
|
=====================================
|
|
Tracks tool versions, changes, and deprecations.
|
|
Provides transparency for tool quality and evolution.
|
|
|
|
Endpoints:
|
|
GET /api/v1/tools/changelog - Full changelog
|
|
GET /api/v1/tools/changelog/{tool} - Changelog for specific tool
|
|
GET /api/v1/tools/version/{tool} - Current version for tool
|
|
GET /api/v1/tools/deprecated - List of deprecated tools
|
|
|
|
Author: RMI Development
|
|
Date: 2026-06-05
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from datetime import UTC, datetime
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.core.redis import get_redis
|
|
|
|
logger = logging.getLogger("tool_changelog")
|
|
|
|
router = APIRouter(prefix="/api/v1/tools", tags=["tool-changelog"])
|
|
|
|
|
|
# ── Redis Helper ─────────────────────────────────────────────────
|
|
|
|
|
|
INITIAL_CHANGELOG = [
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "feature",
|
|
"tool": "whale_copy_trade",
|
|
"title": "New Tool: Whale Copy Trade Engine",
|
|
"description": "Real-time copy trade engine - input a smart money wallet, get their exact last 24h trades with entry/exit prices, PnL, and suggested follow trades.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "feature",
|
|
"tool": "rug_predictor_live",
|
|
"title": "New Tool: Live Rug Predictor",
|
|
"description": "Analyzes tokens in their first 5-30 minutes with 6-signal rug probability scoring. Detects low liquidity, unlocked LP, price crashes, mass selling, and more.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "feature",
|
|
"tool": "whale_cluster",
|
|
"title": "New Tool: Whale Cluster Detection",
|
|
"description": "Identify coordinated whale clusters - wallets that move together via same funding source, timing patterns, and token sets. Detects wash trading and insider networks.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "infrastructure",
|
|
"tool": "*",
|
|
"title": "Developer Tier System",
|
|
"description": "Free developer API keys with 100 calls/day, no payment required. Tier system: FREE (100/day), TRIAL (3-5/tool), PAID (pay-per-use), PRO ($29/mo, 5000/day).",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "infrastructure",
|
|
"tool": "*",
|
|
"title": "Facilitator Health Monitoring",
|
|
"description": "60s health check loop for all payment facilitators with latency tracking, success rate monitoring, and auto-failover readiness.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "infrastructure",
|
|
"tool": "*",
|
|
"title": "Public Status Page",
|
|
"description": "Public-facing status page at /api/v1/status showing health of all RMI services - backend, Redis, ClickHouse, MCP, facilitators. 30s monitoring loop.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "infrastructure",
|
|
"tool": "*",
|
|
"title": "Webhook Notification Pipeline",
|
|
"description": "Real-time webhook delivery system with retry, signing, rate limiting, and dead letter queue. Polls every 5 seconds for new alerts.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "infrastructure",
|
|
"tool": "*",
|
|
"title": "Persistent State Layer",
|
|
"description": "User watchlists, portfolios, saved scans, and alert history. Redis-backed with JSON serialization.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-05",
|
|
"version": "3.3.0",
|
|
"type": "improvement",
|
|
"tool": "*",
|
|
"title": "SDK v2.0 Released",
|
|
"description": "Complete rewrite with async/sync dual API, retry with exponential backoff, batch tool calls, webhook management, proper error classes, and type hints.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-01",
|
|
"version": "3.2.0",
|
|
"type": "improvement",
|
|
"tool": "*",
|
|
"title": "Response Enrichment Pipeline",
|
|
"description": "Every tool response now enriched with wallet labels, RAG similarity search, scam pattern detection, risk summary, intel briefing, and confidence scores.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-01",
|
|
"version": "3.2.0",
|
|
"type": "feature",
|
|
"tool": "composite_score",
|
|
"title": "New Tool: Composite Score",
|
|
"description": "One-number buy/sell/avoid score combining reputation, rug probability, market health, narrative sentiment, MEV exposure, and DeFi position.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-06-01",
|
|
"version": "3.2.0",
|
|
"type": "feature",
|
|
"tool": "smart_money",
|
|
"title": "New Tool: Smart Money Finder",
|
|
"description": "Find profitable traders via win rate estimation, token count, balance analysis, and wallet label cross-reference.",
|
|
"breaking": False,
|
|
},
|
|
{
|
|
"date": "2026-05-28",
|
|
"version": "3.1.0",
|
|
"type": "infrastructure",
|
|
"tool": "*",
|
|
"title": "Multi-Facilitator Payment System",
|
|
"description": "Smart router auto-picks best facilitator per chain/token. 7 active facilitators across 11 payment chains.",
|
|
"breaking": False,
|
|
},
|
|
]
|
|
|
|
|
|
# ── Changelog Management ─────────────────────────────────────────
|
|
|
|
|
|
def get_changelog(tool: str | None = None, limit: int = 50) -> list[dict[str, Any]]:
|
|
"""Get changelog entries."""
|
|
r = get_redis()
|
|
|
|
# Load from Redis if available
|
|
entries = []
|
|
if r:
|
|
stored = r.lrange("rmi:changelog", 0, -1)
|
|
entries = [json.loads(e) for e in stored]
|
|
|
|
# If no stored entries, use initial data
|
|
if not entries:
|
|
entries = INITIAL_CHANGELOG.copy()
|
|
if r:
|
|
# Store initial entries
|
|
for entry in reversed(entries):
|
|
r.lpush("rmi:changelog", json.dumps(entry))
|
|
|
|
# Filter by tool
|
|
if tool:
|
|
entries = [e for e in entries if e.get("tool") == tool or e.get("tool") == "*"]
|
|
|
|
# Limit
|
|
return entries[:limit]
|
|
|
|
|
|
def add_changelog_entry(
|
|
tool: str,
|
|
title: str,
|
|
description: str,
|
|
version: str = "",
|
|
change_type: str = "improvement",
|
|
breaking: bool = False,
|
|
):
|
|
"""Add a changelog entry."""
|
|
r = get_redis()
|
|
if not r:
|
|
return
|
|
|
|
entry = {
|
|
"date": datetime.now(UTC).strftime("%Y-%m-%d"),
|
|
"version": version or "current",
|
|
"type": change_type,
|
|
"tool": tool,
|
|
"title": title,
|
|
"description": description,
|
|
"breaking": breaking,
|
|
}
|
|
|
|
r.lpush("rmi:changelog", json.dumps(entry))
|
|
r.ltrim("rmi:changelog", 0, 499) # Keep last 500
|
|
|
|
|
|
def get_tool_version(tool: str) -> dict[str, Any]:
|
|
"""Get current version info for a tool."""
|
|
changelog = get_changelog(tool, limit=1)
|
|
if changelog:
|
|
entry = changelog[0]
|
|
return {
|
|
"tool": tool,
|
|
"version": entry.get("version", "unknown"),
|
|
"last_updated": entry.get("date", ""),
|
|
"last_change": entry.get("title", ""),
|
|
}
|
|
|
|
# Default version
|
|
return {
|
|
"tool": tool,
|
|
"version": "1.0.0",
|
|
"last_updated": "unknown",
|
|
"last_change": "No changelog entries",
|
|
}
|
|
|
|
|
|
def get_deprecated_tools() -> list[dict[str, Any]]:
|
|
"""Get list of deprecated tools."""
|
|
r = get_redis()
|
|
if not r:
|
|
return []
|
|
|
|
deprecated = r.smembers("rmi:tools:deprecated")
|
|
return [json.loads(d) for d in deprecated]
|
|
|
|
|
|
# ── Endpoints ────────────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/changelog")
|
|
async def tool_changelog(tool: str | None = None, limit: int = 50):
|
|
"""Get changelog entries."""
|
|
entries = get_changelog(tool, limit)
|
|
return JSONResponse(
|
|
content={
|
|
"changelog": entries,
|
|
"total": len(entries),
|
|
"filter": tool,
|
|
}
|
|
)
|
|
|
|
|
|
@router.get("/changelog/{tool}")
|
|
async def tool_specific_changelog(tool: str, limit: int = 20):
|
|
"""Get changelog for a specific tool."""
|
|
entries = get_changelog(tool, limit)
|
|
return JSONResponse(
|
|
content={
|
|
"tool": tool,
|
|
"changelog": entries,
|
|
"total": len(entries),
|
|
}
|
|
)
|
|
|
|
|
|
@router.get("/version/{tool}")
|
|
async def tool_version(tool: str):
|
|
"""Get current version for a tool."""
|
|
return JSONResponse(content=get_tool_version(tool))
|
|
|
|
|
|
@router.get("/deprecated")
|
|
async def deprecated_tools():
|
|
"""Get list of deprecated tools."""
|
|
return JSONResponse(
|
|
content={
|
|
"deprecated": get_deprecated_tools(),
|
|
"count": len(get_deprecated_tools()),
|
|
}
|
|
)
|