fix(system): S61-S62 — API cleanup, drone path routing, naming checker, .backup→.recovery rename (#142)
- API: 18-item P0/P1/P2 cleanup (debug prints→logger, help gate fix, URL injection fix, key masking, unused imports) - Drone: passport walk-up replaces hardcoded src/aipass/<branch> pattern, 13 naming bypasses removed - Seedgo: naming checker fixed (__dunder__ skip + column-0 scope detection) — eliminates 71 false positives system-wide - Spawn: .backup→.recovery rename (pre-change snapshots no longer conflict with backup branch namespace) - .gitignore: .recovery/ added - HERALD.md: Updated through S62 (backup audit, .recovery rename, night shift launch) - README.md: Stats updated (141 PRs, 2900+ tests) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ae459853f0
commit
6772cacce2
@@ -74,6 +74,11 @@
|
||||
"file": "apps/modules/google_client.py",
|
||||
"standard": "unused_function",
|
||||
"reason": "Public API exports for cross-branch consumption (get_drive_service, authenticate_google, validate_google, reauth_google). Tested in test_google_client.py. Backup migration pending."
|
||||
},
|
||||
{
|
||||
"file": "apps/modules/openrouter_client.py",
|
||||
"standard": "modules",
|
||||
"reason": "make_call() is a CLI orchestrator — parses args then delegates to client.get_response(). All business logic lives in handlers/openrouter/client.py."
|
||||
}
|
||||
],
|
||||
"notes": {
|
||||
|
||||
@@ -157,8 +157,15 @@ def print_help():
|
||||
table.add_row("reauth google", "Re-authenticate Google OAuth2")
|
||||
table.add_row("test", "Test OpenRouter connection status")
|
||||
table.add_row("models", "List available models from provider")
|
||||
table.add_row("status", "Check OpenRouter client status")
|
||||
table.add_row("call", "Make API call to model")
|
||||
table.add_row("list-providers", "List available API providers")
|
||||
table.add_row("init", "Initialize .env template")
|
||||
table.add_row("track", "Track API usage metrics")
|
||||
table.add_row("stats", "Display API usage statistics")
|
||||
table.add_row("session", "Show session usage data")
|
||||
table.add_row("caller-usage", "Show usage by caller module")
|
||||
table.add_row("cleanup", "Clean up old usage data")
|
||||
|
||||
console.print(table)
|
||||
console.print()
|
||||
@@ -193,7 +200,7 @@ def print_help():
|
||||
console.print("─" * 70)
|
||||
console.print()
|
||||
|
||||
console.print("[dim]Commands: get-key, validate, test, models, track, stats, help, --help[/dim]")
|
||||
console.print("[dim]Commands: get-key, validate, test, models, status, call, list-providers, init, track, stats, session, caller-usage, cleanup[/dim]")
|
||||
console.print()
|
||||
|
||||
|
||||
|
||||
@@ -42,9 +42,9 @@ def _guard_branch_access():
|
||||
|
||||
import os
|
||||
if os.environ.get("AIPASS_DEBUG_GUARD"):
|
||||
import sys
|
||||
print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr)
|
||||
print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr)
|
||||
from aipass.prax import logger as _guard_logger
|
||||
_guard_logger.info(f"[GUARD] caller_file = {caller_file}")
|
||||
_guard_logger.info(f"[GUARD] import_line = {import_line}")
|
||||
|
||||
if caller_file is None:
|
||||
stack = inspect.stack()
|
||||
|
||||
@@ -17,10 +17,9 @@ Functions:
|
||||
|
||||
# Infrastructure
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# Standard library
|
||||
from typing import Optional, Dict
|
||||
from typing import Optional
|
||||
|
||||
# Logging
|
||||
from aipass.prax import logger
|
||||
|
||||
@@ -10,7 +10,6 @@ import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
# Logging
|
||||
@@ -121,7 +120,7 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool:
|
||||
if validate_json_structure(data, json_type):
|
||||
return True
|
||||
else:
|
||||
pass # Corrupted - regenerating
|
||||
logger.warning(f"Corrupted JSON structure at {json_path}, regenerating")
|
||||
except Exception as e:
|
||||
logger.warning(f"Unreadable JSON at {json_path}, regenerating: {e}")
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
OpenRouter Client Handler
|
||||
|
||||
Business logic for OpenRouter API client creation and request execution.
|
||||
Extracted from archive.temp/openrouter.py following AIPASS standards.
|
||||
OpenRouter API client creation and request execution.
|
||||
|
||||
Functions:
|
||||
- get_response() - Main API call with tracking integration
|
||||
@@ -34,8 +34,6 @@ Standards:
|
||||
"""
|
||||
|
||||
# INFRASTRUCTURE IMPORT PATTERN
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Standard library imports
|
||||
import time
|
||||
@@ -193,6 +191,7 @@ def make_api_request(client: OpenAI, messages: List[Dict], model: str, retries:
|
||||
OpenAI response object or None on failure
|
||||
"""
|
||||
if not client or not messages or not model:
|
||||
logger.warning(f"make_api_request() called with missing params — client={bool(client)}, messages={bool(messages)}, model={bool(model)}")
|
||||
return None
|
||||
|
||||
api_params = {
|
||||
@@ -242,9 +241,11 @@ def extract_response(response: Any) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
# Validate response structure
|
||||
if not hasattr(response, 'choices') or not response.choices:
|
||||
logger.warning("Response missing 'choices' or choices is empty")
|
||||
return None
|
||||
|
||||
if not hasattr(response.choices[0], 'message'):
|
||||
logger.warning("Response choice missing 'message' attribute")
|
||||
return None
|
||||
|
||||
# Extract content
|
||||
|
||||
@@ -25,12 +25,11 @@ COMPLIANT STANDARDS:
|
||||
- Under 300 lines
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, Tuple
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from aipass.prax import logger
|
||||
|
||||
@@ -102,7 +101,7 @@ def get_default_caller_config() -> Dict[str, Any]:
|
||||
Dict with default OpenRouter configuration (model must be set by caller)
|
||||
"""
|
||||
return {
|
||||
"skill_name": "openrouter",
|
||||
"module_name": "openrouter",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"config": {
|
||||
"ai_model": "", # Caller must set their own model
|
||||
@@ -121,7 +120,7 @@ def get_default_caller_data() -> Dict[str, Any]:
|
||||
Dict with initial usage tracking data
|
||||
"""
|
||||
return {
|
||||
"skill_name": "openrouter",
|
||||
"module_name": "openrouter",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"data": {
|
||||
"total_requests": 0,
|
||||
@@ -141,7 +140,7 @@ def get_default_caller_log() -> Dict[str, Any]:
|
||||
Dict with empty log structure
|
||||
"""
|
||||
return {
|
||||
"skill_name": "openrouter",
|
||||
"module_name": "openrouter",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"logs": []
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ Business logic for aggregating usage statistics:
|
||||
- Cost, token, and latency aggregation
|
||||
- Model usage tracking and breakdown
|
||||
|
||||
Extracted from legacy archive (api_usage.py).
|
||||
Functions: get_caller_usage(), get_session_summary()
|
||||
"""
|
||||
|
||||
|
||||
@@ -15,11 +15,9 @@ Business logic for tracking API usage from OpenRouter:
|
||||
- Store generation tracking data with newest-first ordering
|
||||
- Handle HTTP requests with proper error handling
|
||||
|
||||
Extracted from legacy archive (api_usage.py).
|
||||
Functions: track_usage(), _get_generation_metrics(), _store_usage_data()
|
||||
Functions: track_usage(), get_generation_metrics(), store_usage_data()
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Standard library imports
|
||||
@@ -147,7 +145,8 @@ def get_generation_metrics(generation_id: str, api_key: str) -> Optional[Dict[st
|
||||
|
||||
# Query the generation endpoint
|
||||
response = requests.get(
|
||||
f"{GENERATION_ENDPOINT}?id={generation_id}",
|
||||
GENERATION_ENDPOINT,
|
||||
params={"id": generation_id},
|
||||
headers=headers,
|
||||
timeout=30
|
||||
)
|
||||
@@ -158,7 +157,7 @@ def get_generation_metrics(generation_id: str, api_key: str) -> Optional[Dict[st
|
||||
|
||||
# Validate response structure
|
||||
if not data or "data" not in data:
|
||||
# Invalid response structure from OpenRouter
|
||||
logger.warning(f"[{MODULE_NAME}] Invalid response structure from OpenRouter for generation {generation_id}")
|
||||
return None
|
||||
|
||||
# Extract metrics from response
|
||||
@@ -176,7 +175,7 @@ def get_generation_metrics(generation_id: str, api_key: str) -> Optional[Dict[st
|
||||
return result
|
||||
|
||||
else:
|
||||
# OpenRouter API returned non-200 status
|
||||
logger.warning(f"[{MODULE_NAME}] OpenRouter API returned status {response.status_code} for generation {generation_id}")
|
||||
return None
|
||||
|
||||
except requests.exceptions.Timeout as e:
|
||||
|
||||
@@ -20,10 +20,9 @@ from pathlib import Path
|
||||
|
||||
from typing import List
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
from aipass.cli.apps.modules import console, header, success, error, warning, section
|
||||
from aipass.cli.apps.modules import console, header, success, error
|
||||
from aipass.api.apps.handlers.json import json_handler
|
||||
from aipass.api.apps.handlers.auth import keys, env
|
||||
from aipass.api.apps.handlers.config import provider
|
||||
|
||||
|
||||
def print_introspection():
|
||||
@@ -110,7 +109,8 @@ def get_key(args: List[str]):
|
||||
|
||||
if api_key:
|
||||
success(f"API key retrieved for {provider_name}")
|
||||
console.print(f" Key (first 20 chars): {api_key[:20]}...")
|
||||
masked = api_key[:6] + "****" + api_key[-4:] if len(api_key) > 10 else "****"
|
||||
console.print(f" Key: {masked}")
|
||||
else:
|
||||
error(f"Failed to retrieve API key for {provider_name}")
|
||||
|
||||
|
||||
@@ -124,11 +124,6 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
Returns:
|
||||
True if command was handled, False to pass through.
|
||||
"""
|
||||
# Help gate
|
||||
if args and args[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
# NO-ARGS GATE (seedgo standard)
|
||||
if not args:
|
||||
if command == "google":
|
||||
@@ -140,6 +135,12 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
if args[0] != "google":
|
||||
return False
|
||||
|
||||
# Help gate — only fires after confirming this is a google command
|
||||
remaining = args[1:] if len(args) > 1 else []
|
||||
if remaining and remaining[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
if command == "validate":
|
||||
_cmd_validate()
|
||||
return True
|
||||
|
||||
@@ -17,11 +17,10 @@ Orchestrates LLM API client operations:
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from typing import List
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
from aipass.cli.apps.modules import console, header, success, error, warning, section
|
||||
from aipass.cli.apps.modules import console, header, success, error
|
||||
from aipass.api.apps.handlers.json import json_handler
|
||||
from aipass.api.apps.handlers.auth import keys
|
||||
from aipass.api.apps.handlers.openrouter import client, models
|
||||
@@ -188,7 +187,42 @@ def make_call(args: List[str]):
|
||||
header("OpenRouter API Call")
|
||||
console.print()
|
||||
|
||||
warning("API call workflow - TODO")
|
||||
if not args:
|
||||
error("Prompt required", suggestion="drone @api call \"your prompt\" --model MODEL")
|
||||
return
|
||||
|
||||
# Parse args: first non-flag arg is prompt, --model MODEL is optional
|
||||
prompt = None
|
||||
model = None
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i] == "--model" and i + 1 < len(args):
|
||||
model = args[i + 1]
|
||||
i += 2
|
||||
elif prompt is None:
|
||||
prompt = args[i]
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if not prompt:
|
||||
error("Prompt required", suggestion="drone @api call \"your prompt\" --model MODEL")
|
||||
return
|
||||
|
||||
if not model:
|
||||
error("Model required", suggestion="drone @api call \"your prompt\" --model anthropic/claude-3.5-sonnet")
|
||||
return
|
||||
|
||||
console.print(f"[dim]Calling {model}...[/dim]")
|
||||
|
||||
response = client.get_response(prompt, caller="cli", model=model)
|
||||
|
||||
if response:
|
||||
success(f"Response received ({len(response['content'])} chars)")
|
||||
console.print()
|
||||
console.print(response["content"])
|
||||
else:
|
||||
error("API call failed")
|
||||
|
||||
|
||||
def list_models(args: List[str] | None = None):
|
||||
|
||||
@@ -21,7 +21,7 @@ from pathlib import Path
|
||||
|
||||
from typing import List
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
from aipass.cli.apps.modules import console, header, success, error, warning, section
|
||||
from aipass.cli.apps.modules import console, header, success, error, warning
|
||||
from aipass.api.apps.handlers.json import json_handler
|
||||
from aipass.api.apps.handlers.usage import tracking, aggregation, cleanup
|
||||
|
||||
@@ -174,7 +174,22 @@ def track_usage(args: List[str]):
|
||||
header("Track API Usage")
|
||||
console.print()
|
||||
|
||||
warning("Usage tracking workflow - TODO")
|
||||
if not args:
|
||||
error("Generation ID required", suggestion="drone @api track <generation_id> [caller]")
|
||||
return
|
||||
|
||||
generation_id = args[0]
|
||||
caller = args[1] if len(args) > 1 else "manual"
|
||||
|
||||
console.print(f"[dim]Tracking generation {generation_id}...[/dim]")
|
||||
|
||||
result = tracking.track_usage(generation_id, caller)
|
||||
|
||||
if result.get("success"):
|
||||
metrics = result.get("metrics", {})
|
||||
success(f"Tracked: {metrics.get('tokens_prompt', 0)} prompt + {metrics.get('tokens_completion', 0)} completion tokens, ${metrics.get('total_cost', 0):.6f}")
|
||||
else:
|
||||
error(f"Tracking failed: {result.get('error', 'unknown')}")
|
||||
|
||||
|
||||
def show_stats():
|
||||
|
||||
@@ -137,10 +137,10 @@ def test_handle_command_routes_reauth_google(
|
||||
def test_handle_command_help_gate(
|
||||
_warn, _err, _succ, _hdr, _json, _retry, _factory, _auth, mock_console
|
||||
):
|
||||
"""handle_command prints help when args=['--help'] and returns True."""
|
||||
"""handle_command prints help when args=['google', '--help'] and returns True."""
|
||||
from aipass.api.apps.modules import google_client
|
||||
|
||||
result = google_client.handle_command("validate", ["--help"])
|
||||
result = google_client.handle_command("validate", ["google", "--help"])
|
||||
|
||||
assert result is True
|
||||
# print_help calls console.print with the argparse output
|
||||
|
||||
@@ -596,17 +596,17 @@ def test_list_models_formats_free_pricing(mock_console, mock_header, mock_keys,
|
||||
# =============================================
|
||||
|
||||
|
||||
@patch(f"{_MOD}.warning")
|
||||
@patch(f"{_MOD}.error")
|
||||
@patch(f"{_MOD}.header")
|
||||
@patch(f"{_MOD}.console")
|
||||
def test_make_call_shows_todo_warning(mock_console, mock_header, mock_warning):
|
||||
"""make_call currently shows a TODO warning."""
|
||||
def test_make_call_no_model_shows_error(mock_console, mock_header, mock_error):
|
||||
"""make_call without --model shows error."""
|
||||
from aipass.api.apps.modules import openrouter_client
|
||||
|
||||
openrouter_client.make_call(["What is AI?"])
|
||||
|
||||
mock_warning.assert_called_once()
|
||||
assert "TODO" in mock_warning.call_args[0][0]
|
||||
mock_error.assert_called_once()
|
||||
assert "Model required" in mock_error.call_args[0][0]
|
||||
|
||||
|
||||
# =============================================
|
||||
|
||||
@@ -60,7 +60,7 @@ def test_create_caller_config_defaults(tmp_path: Path):
|
||||
assert config["config"]["ai_max_tokens"] == 4000
|
||||
assert config["config"]["enabled"] is True
|
||||
assert config["config"]["ai_model"] == ""
|
||||
assert config["skill_name"] == "openrouter"
|
||||
assert config["module_name"] == "openrouter"
|
||||
|
||||
|
||||
def test_create_caller_config_data_defaults(tmp_path: Path):
|
||||
@@ -87,7 +87,7 @@ def test_create_caller_config_log_defaults(tmp_path: Path):
|
||||
log = read_json(json_folder / "openrouter_skill_log.json")
|
||||
assert log is not None
|
||||
assert log["logs"] == []
|
||||
assert log["skill_name"] == "openrouter"
|
||||
assert log["module_name"] == "openrouter"
|
||||
|
||||
|
||||
def test_create_caller_config_creates_folder(tmp_path: Path):
|
||||
@@ -187,7 +187,7 @@ def test_get_default_caller_config_structure():
|
||||
"""Default config should have expected structure."""
|
||||
config = get_default_caller_config()
|
||||
|
||||
assert "skill_name" in config
|
||||
assert "module_name" in config
|
||||
assert "timestamp" in config
|
||||
assert "config" in config
|
||||
assert isinstance(config["config"], dict)
|
||||
|
||||
Reference in New Issue
Block a user